• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "src/objects/string-table.h"
6 
7 #include <atomic>
8 
9 #include "src/base/atomicops.h"
10 #include "src/base/macros.h"
11 #include "src/common/assert-scope.h"
12 #include "src/common/globals.h"
13 #include "src/common/ptr-compr-inl.h"
14 #include "src/execution/isolate-utils-inl.h"
15 #include "src/heap/safepoint.h"
16 #include "src/objects/internal-index.h"
17 #include "src/objects/object-list-macros.h"
18 #include "src/objects/slots-inl.h"
19 #include "src/objects/slots.h"
20 #include "src/objects/string-inl.h"
21 #include "src/objects/string-table-inl.h"
22 #include "src/snapshot/deserializer.h"
23 #include "src/utils/allocation.h"
24 #include "src/utils/ostreams.h"
25 
26 namespace v8 {
27 namespace internal {
28 
29 namespace {
30 
31 static constexpr int kStringTableMaxEmptyFactor = 4;
32 static constexpr int kStringTableMinCapacity = 2048;
33 
StringTableHasSufficientCapacityToAdd(int capacity,int number_of_elements,int number_of_deleted_elements,int number_of_additional_elements)34 bool StringTableHasSufficientCapacityToAdd(int capacity, int number_of_elements,
35                                            int number_of_deleted_elements,
36                                            int number_of_additional_elements) {
37   int nof = number_of_elements + number_of_additional_elements;
38   // Return true if:
39   //   50% is still free after adding number_of_additional_elements elements and
40   //   at most 50% of the free elements are deleted elements.
41   if ((nof < capacity) &&
42       ((number_of_deleted_elements <= (capacity - nof) / 2))) {
43     int needed_free = nof / 2;
44     if (nof + needed_free <= capacity) return true;
45   }
46   return false;
47 }
48 
ComputeStringTableCapacity(int at_least_space_for)49 int ComputeStringTableCapacity(int at_least_space_for) {
50   // Add 50% slack to make slot collisions sufficiently unlikely.
51   // See matching computation in StringTableHasSufficientCapacityToAdd().
52   int raw_capacity = at_least_space_for + (at_least_space_for >> 1);
53   int capacity = base::bits::RoundUpToPowerOfTwo32(raw_capacity);
54   return std::max(capacity, kStringTableMinCapacity);
55 }
56 
ComputeStringTableCapacityWithShrink(int current_capacity,int at_least_room_for)57 int ComputeStringTableCapacityWithShrink(int current_capacity,
58                                          int at_least_room_for) {
59   // Only shrink if the table is very empty to avoid performance penalty.
60   DCHECK_GE(current_capacity, kStringTableMinCapacity);
61   if (at_least_room_for > (current_capacity / kStringTableMaxEmptyFactor))
62     return current_capacity;
63 
64   // Recalculate the smaller capacity actually needed.
65   int new_capacity = ComputeStringTableCapacity(at_least_room_for);
66   DCHECK_GE(new_capacity, at_least_room_for);
67   // Don't go lower than room for {kStringTableMinCapacity} elements.
68   if (new_capacity < kStringTableMinCapacity) return current_capacity;
69   return new_capacity;
70 }
71 
72 template <typename IsolateT, typename StringTableKey>
KeyIsMatch(IsolateT * isolate,StringTableKey * key,String string)73 bool KeyIsMatch(IsolateT* isolate, StringTableKey* key, String string) {
74   if (string.hash() != key->hash()) return false;
75   if (string.length() != key->length()) return false;
76   return key->IsMatch(isolate, string);
77 }
78 
79 }  // namespace
80 
81 // Data holds the actual data of the string table, including capacity and number
82 // of elements.
83 //
84 // It is a variable sized structure, with a "header" followed directly in memory
85 // by the elements themselves. These are accessed as offsets from the elements_
86 // field, which itself provides storage for the first element.
87 //
88 // The elements themselves are stored as an open-addressed hash table, with
89 // quadratic probing and Smi 0 and Smi 1 as the empty and deleted sentinels,
90 // respectively.
91 class StringTable::Data {
92  public:
93   static std::unique_ptr<Data> New(int capacity);
94   static std::unique_ptr<Data> Resize(PtrComprCageBase cage_base,
95                                       std::unique_ptr<Data> data, int capacity);
96 
slot(InternalIndex index) const97   OffHeapObjectSlot slot(InternalIndex index) const {
98     return OffHeapObjectSlot(&elements_[index.as_uint32()]);
99   }
100 
Get(PtrComprCageBase cage_base,InternalIndex index) const101   Object Get(PtrComprCageBase cage_base, InternalIndex index) const {
102     return slot(index).Acquire_Load(cage_base);
103   }
104 
Set(InternalIndex index,String entry)105   void Set(InternalIndex index, String entry) {
106     slot(index).Release_Store(entry);
107   }
108 
ElementAdded()109   void ElementAdded() {
110     DCHECK_LT(number_of_elements_ + 1, capacity());
111     DCHECK(StringTableHasSufficientCapacityToAdd(
112         capacity(), number_of_elements(), number_of_deleted_elements(), 1));
113 
114     number_of_elements_++;
115   }
DeletedElementOverwritten()116   void DeletedElementOverwritten() {
117     DCHECK_LT(number_of_elements_ + 1, capacity());
118     DCHECK(StringTableHasSufficientCapacityToAdd(
119         capacity(), number_of_elements(), number_of_deleted_elements() - 1, 1));
120 
121     number_of_elements_++;
122     number_of_deleted_elements_--;
123   }
ElementsRemoved(int count)124   void ElementsRemoved(int count) {
125     DCHECK_LE(count, number_of_elements_);
126     number_of_elements_ -= count;
127     number_of_deleted_elements_ += count;
128   }
129 
130   void* operator new(size_t size, int capacity);
131   void* operator new(size_t size) = delete;
132   void operator delete(void* description);
133 
capacity() const134   int capacity() const { return capacity_; }
number_of_elements() const135   int number_of_elements() const { return number_of_elements_; }
number_of_deleted_elements() const136   int number_of_deleted_elements() const { return number_of_deleted_elements_; }
137 
138   template <typename IsolateT, typename StringTableKey>
139   InternalIndex FindEntry(IsolateT* isolate, StringTableKey* key,
140                           uint32_t hash) const;
141 
142   InternalIndex FindInsertionEntry(PtrComprCageBase cage_base,
143                                    uint32_t hash) const;
144 
145   template <typename IsolateT, typename StringTableKey>
146   InternalIndex FindEntryOrInsertionEntry(IsolateT* isolate,
147                                           StringTableKey* key,
148                                           uint32_t hash) const;
149 
150   // Helper method for StringTable::TryStringToIndexOrLookupExisting.
151   template <typename Char>
152   static Address TryStringToIndexOrLookupExisting(Isolate* isolate,
153                                                   String string, String source,
154                                                   size_t start);
155 
156   void IterateElements(RootVisitor* visitor);
157 
PreviousData()158   Data* PreviousData() { return previous_data_.get(); }
DropPreviousData()159   void DropPreviousData() { previous_data_.reset(); }
160 
161   void Print(PtrComprCageBase cage_base) const;
162   size_t GetCurrentMemoryUsage() const;
163 
164  private:
165   explicit Data(int capacity);
166 
167   // Returns probe entry.
FirstProbe(uint32_t hash,uint32_t size)168   inline static InternalIndex FirstProbe(uint32_t hash, uint32_t size) {
169     return InternalIndex(hash & (size - 1));
170   }
171 
NextProbe(InternalIndex last,uint32_t number,uint32_t size)172   inline static InternalIndex NextProbe(InternalIndex last, uint32_t number,
173                                         uint32_t size) {
174     return InternalIndex((last.as_uint32() + number) & (size - 1));
175   }
176 
177  private:
178   std::unique_ptr<Data> previous_data_;
179   int number_of_elements_;
180   int number_of_deleted_elements_;
181   const int capacity_;
182   Tagged_t elements_[1];
183 };
184 
operator new(size_t size,int capacity)185 void* StringTable::Data::operator new(size_t size, int capacity) {
186   // Make sure the size given is the size of the Data structure.
187   DCHECK_EQ(size, sizeof(StringTable::Data));
188   // Make sure that the elements_ array is at the end of Data, with no padding,
189   // so that subsequent elements can be accessed as offsets from elements_.
190   STATIC_ASSERT(offsetof(StringTable::Data, elements_) ==
191                 sizeof(StringTable::Data) - sizeof(Tagged_t));
192   // Make sure that elements_ is aligned when StringTable::Data is aligned.
193   STATIC_ASSERT(
194       (alignof(StringTable::Data) + offsetof(StringTable::Data, elements_)) %
195           kTaggedSize ==
196       0);
197 
198   // Subtract 1 from capacity, as the member elements_ already supplies the
199   // storage for the first element.
200   return AlignedAlloc(size + (capacity - 1) * sizeof(Tagged_t),
201                       alignof(StringTable::Data));
202 }
203 
operator delete(void * table)204 void StringTable::Data::operator delete(void* table) { AlignedFree(table); }
205 
GetCurrentMemoryUsage() const206 size_t StringTable::Data::GetCurrentMemoryUsage() const {
207   size_t usage = sizeof(*this) + (capacity_ - 1) * sizeof(Tagged_t);
208   if (previous_data_) {
209     usage += previous_data_->GetCurrentMemoryUsage();
210   }
211   return usage;
212 }
213 
Data(int capacity)214 StringTable::Data::Data(int capacity)
215     : previous_data_(nullptr),
216       number_of_elements_(0),
217       number_of_deleted_elements_(0),
218       capacity_(capacity) {
219   OffHeapObjectSlot first_slot = slot(InternalIndex(0));
220   MemsetTagged(first_slot, empty_element(), capacity);
221 }
222 
New(int capacity)223 std::unique_ptr<StringTable::Data> StringTable::Data::New(int capacity) {
224   return std::unique_ptr<Data>(new (capacity) Data(capacity));
225 }
226 
Resize(PtrComprCageBase cage_base,std::unique_ptr<Data> data,int capacity)227 std::unique_ptr<StringTable::Data> StringTable::Data::Resize(
228     PtrComprCageBase cage_base, std::unique_ptr<Data> data, int capacity) {
229   std::unique_ptr<Data> new_data(new (capacity) Data(capacity));
230 
231   DCHECK_LT(data->number_of_elements(), new_data->capacity());
232   DCHECK(StringTableHasSufficientCapacityToAdd(
233       new_data->capacity(), new_data->number_of_elements(),
234       new_data->number_of_deleted_elements(), data->number_of_elements()));
235 
236   // Rehash the elements.
237   for (InternalIndex i : InternalIndex::Range(data->capacity())) {
238     Object element = data->Get(cage_base, i);
239     if (element == empty_element() || element == deleted_element()) continue;
240     String string = String::cast(element);
241     uint32_t hash = string.hash();
242     InternalIndex insertion_index =
243         new_data->FindInsertionEntry(cage_base, hash);
244     new_data->Set(insertion_index, string);
245   }
246   new_data->number_of_elements_ = data->number_of_elements();
247 
248   new_data->previous_data_ = std::move(data);
249   return new_data;
250 }
251 
252 template <typename IsolateT, typename StringTableKey>
FindEntry(IsolateT * isolate,StringTableKey * key,uint32_t hash) const253 InternalIndex StringTable::Data::FindEntry(IsolateT* isolate,
254                                            StringTableKey* key,
255                                            uint32_t hash) const {
256   uint32_t count = 1;
257   // EnsureCapacity will guarantee the hash table is never full.
258   for (InternalIndex entry = FirstProbe(hash, capacity_);;
259        entry = NextProbe(entry, count++, capacity_)) {
260     // TODO(leszeks): Consider delaying the decompression until after the
261     // comparisons against empty/deleted.
262     Object element = Get(isolate, entry);
263     if (element == empty_element()) return InternalIndex::NotFound();
264     if (element == deleted_element()) continue;
265     String string = String::cast(element);
266     if (KeyIsMatch(isolate, key, string)) return entry;
267   }
268 }
269 
FindInsertionEntry(PtrComprCageBase cage_base,uint32_t hash) const270 InternalIndex StringTable::Data::FindInsertionEntry(PtrComprCageBase cage_base,
271                                                     uint32_t hash) const {
272   uint32_t count = 1;
273   // EnsureCapacity will guarantee the hash table is never full.
274   for (InternalIndex entry = FirstProbe(hash, capacity_);;
275        entry = NextProbe(entry, count++, capacity_)) {
276     // TODO(leszeks): Consider delaying the decompression until after the
277     // comparisons against empty/deleted.
278     Object element = Get(cage_base, entry);
279     if (element == empty_element() || element == deleted_element())
280       return entry;
281   }
282 }
283 
284 template <typename IsolateT, typename StringTableKey>
FindEntryOrInsertionEntry(IsolateT * isolate,StringTableKey * key,uint32_t hash) const285 InternalIndex StringTable::Data::FindEntryOrInsertionEntry(
286     IsolateT* isolate, StringTableKey* key, uint32_t hash) const {
287   InternalIndex insertion_entry = InternalIndex::NotFound();
288   uint32_t count = 1;
289   // EnsureCapacity will guarantee the hash table is never full.
290   for (InternalIndex entry = FirstProbe(hash, capacity_);;
291        entry = NextProbe(entry, count++, capacity_)) {
292     // TODO(leszeks): Consider delaying the decompression until after the
293     // comparisons against empty/deleted.
294     Object element = Get(isolate, entry);
295     if (element == empty_element()) {
296       // Empty entry, it's our insertion entry if there was no previous Hole.
297       if (insertion_entry.is_not_found()) return entry;
298       return insertion_entry;
299     }
300 
301     if (element == deleted_element()) {
302       // Holes are potential insertion candidates, but we continue the search
303       // in case we find the actual matching entry.
304       if (insertion_entry.is_not_found()) insertion_entry = entry;
305       continue;
306     }
307 
308     String string = String::cast(element);
309     if (KeyIsMatch(isolate, key, string)) return entry;
310   }
311 }
312 
IterateElements(RootVisitor * visitor)313 void StringTable::Data::IterateElements(RootVisitor* visitor) {
314   OffHeapObjectSlot first_slot = slot(InternalIndex(0));
315   OffHeapObjectSlot end_slot = slot(InternalIndex(capacity_));
316   visitor->VisitRootPointers(Root::kStringTable, nullptr, first_slot, end_slot);
317 }
318 
Print(PtrComprCageBase cage_base) const319 void StringTable::Data::Print(PtrComprCageBase cage_base) const {
320   OFStream os(stdout);
321   os << "StringTable {" << std::endl;
322   for (InternalIndex i : InternalIndex::Range(capacity_)) {
323     os << "  " << i.as_uint32() << ": " << Brief(Get(cage_base, i))
324        << std::endl;
325   }
326   os << "}" << std::endl;
327 }
328 
StringTable(Isolate * isolate)329 StringTable::StringTable(Isolate* isolate)
330     : data_(Data::New(kStringTableMinCapacity).release()), isolate_(isolate) {}
331 
~StringTable()332 StringTable::~StringTable() { delete data_; }
333 
Capacity() const334 int StringTable::Capacity() const {
335   return data_.load(std::memory_order_acquire)->capacity();
336 }
NumberOfElements() const337 int StringTable::NumberOfElements() const {
338   {
339     base::MutexGuard table_write_guard(&write_mutex_);
340     return data_.load(std::memory_order_relaxed)->number_of_elements();
341   }
342 }
343 
344 // InternalizedStringKey carries a string/internalized-string object as key.
345 class InternalizedStringKey final : public StringTableKey {
346  public:
InternalizedStringKey(Handle<String> string)347   explicit InternalizedStringKey(Handle<String> string)
348       : StringTableKey(0, string->length()), string_(string) {
349     // When sharing the string table, it's possible that another thread already
350     // internalized the key, in which case StringTable::LookupKey will perform a
351     // redundant lookup and return the already internalized copy.
352     DCHECK_IMPLIES(!FLAG_shared_string_table, !string->IsInternalizedString());
353     DCHECK(string->IsFlat());
354     // Make sure hash_field is computed.
355     string->EnsureHash();
356     set_raw_hash_field(string->raw_hash_field());
357   }
358 
IsMatch(Isolate * isolate,String string)359   bool IsMatch(Isolate* isolate, String string) {
360     DCHECK(!SharedStringAccessGuardIfNeeded::IsNeeded(string));
361     return string_->SlowEquals(string);
362   }
363 
PrepareForInsertion(Isolate * isolate)364   void PrepareForInsertion(Isolate* isolate) {
365     StringTransitionStrategy strategy =
366         isolate->factory()->ComputeInternalizationStrategyForString(
367             string_, &maybe_internalized_map_);
368     switch (strategy) {
369       case StringTransitionStrategy::kCopy:
370         break;
371       case StringTransitionStrategy::kInPlace:
372         // In-place transition will be done in GetHandleForInsertion, when we
373         // are sure that we are going to insert the string into the table.
374         return;
375       case StringTransitionStrategy::kAlreadyTransitioned:
376         // We can see already internalized strings here only when sharing the
377         // string table and allowing concurrent internalization.
378         DCHECK(FLAG_shared_string_table);
379         return;
380     }
381 
382     // Copying the string here is always threadsafe, as no instance type
383     // requiring a copy can transition any further.
384     StringShape shape(*string_);
385     // External strings get special treatment, to avoid copying their
386     // contents as long as they are not uncached.
387     if (shape.IsExternalOneByte() && !shape.IsUncachedExternal()) {
388       // TODO(syg): External strings not yet supported.
389       DCHECK(!FLAG_shared_string_table);
390       string_ =
391           isolate->factory()->InternalizeExternalString<ExternalOneByteString>(
392               string_);
393     } else if (shape.IsExternalTwoByte() && !shape.IsUncachedExternal()) {
394       // TODO(syg): External strings not yet supported.
395       DCHECK(!FLAG_shared_string_table);
396       string_ =
397           isolate->factory()->InternalizeExternalString<ExternalTwoByteString>(
398               string_);
399     } else {
400       // Otherwise allocate a new internalized string.
401       string_ = isolate->factory()->NewInternalizedStringImpl(
402           string_, string_->length(), string_->raw_hash_field());
403     }
404   }
405 
GetHandleForInsertion()406   Handle<String> GetHandleForInsertion() {
407     Handle<Map> internalized_map;
408     // When preparing the string, the strategy was to in-place migrate it.
409     if (maybe_internalized_map_.ToHandle(&internalized_map)) {
410       // It is always safe to overwrite the map. The only transition possible
411       // is another thread migrated the string to internalized already.
412       // Migrations to thin are impossible, as we only call this method on table
413       // misses inside the critical section.
414       string_->set_map_no_write_barrier(*internalized_map);
415       DCHECK(string_->IsInternalizedString());
416       return string_;
417     }
418     // We prepared an internalized copy for the string or the string was already
419     // internalized.
420     // In theory we could have created a copy of a SeqString in young generation
421     // that has been promoted to old space by now. In that case we could
422     // in-place migrate the original string instead of internalizing the copy
423     // and migrating the original string to a ThinString. This scenario doesn't
424     // seem to be common enough to justify re-computing the strategy here.
425     return string_;
426   }
427 
428  private:
429   Handle<String> string_;
430   MaybeHandle<Map> maybe_internalized_map_;
431 };
432 
LookupString(Isolate * isolate,Handle<String> string)433 Handle<String> StringTable::LookupString(Isolate* isolate,
434                                          Handle<String> string) {
435   // When sharing the string table, internalization is allowed to be concurrent
436   // from multiple Isolates, assuming that:
437   //
438   //  - All in-place internalizable strings (i.e. old-generation flat strings)
439   //    and internalized strings are in the shared heap.
440   //  - LookupKey supports concurrent access (see comment below).
441   //
442   // These assumptions guarantee the following properties:
443   //
444   //  - String::Flatten is not threadsafe but is only called on non-shared
445   //    strings, since non-flat strings are not shared.
446   //
447   //  - String::ComputeAndSetHash is threadsafe on flat strings. This is safe
448   //    because the characters are immutable and the same hash will be
449   //    computed. The hash field is set with relaxed memory order. A thread that
450   //    doesn't see the hash may do redundant work but will not be incorrect.
451   //
452   //  - In-place internalizable strings do not incur a copy regardless of string
453   //    table sharing. The map mutation is threadsafe even with relaxed memory
454   //    order, because for concurrent table lookups, the "losing" thread will be
455   //    correctly ordered by LookupKey's write mutex and see the updated map
456   //    during the re-lookup.
457   //
458   // For lookup misses, the internalized string map is the same map in RO space
459   // regardless of which thread is doing the lookup.
460   //
461   // For lookup hits, String::MakeThin is threadsafe and spinlocks on
462   // migrating into a ThinString.
463 
464   string = String::Flatten(isolate, string);
465   if (string->IsInternalizedString()) return string;
466 
467   InternalizedStringKey key(string);
468   Handle<String> result = LookupKey(isolate, &key);
469 
470   if (!string->IsInternalizedString()) {
471     string->MakeThin(isolate, *result);
472   }
473 
474   return result;
475 }
476 
477 template <typename StringTableKey, typename IsolateT>
LookupKey(IsolateT * isolate,StringTableKey * key)478 Handle<String> StringTable::LookupKey(IsolateT* isolate, StringTableKey* key) {
479   // String table lookups are allowed to be concurrent, assuming that:
480   //
481   //   - The Heap access is allowed to be concurrent (using LocalHeap or
482   //     similar),
483   //   - All writes to the string table are guarded by the Isolate string table
484   //     mutex,
485   //   - Resizes of the string table first copies the old contents to the new
486   //     table, and only then sets the new string table pointer to the new
487   //     table,
488   //   - Only GCs can remove elements from the string table.
489   //
490   // These assumptions allow us to make the following statement:
491   //
492   //   "Reads are allowed when not holding the lock, as long as false negatives
493   //    (misses) are ok. We will never get a false positive (hit of an entry no
494   //    longer in the table)"
495   //
496   // This is because we _know_ that if we find an entry in the string table, any
497   // entry will also be in all reallocations of that tables. This is required
498   // for strong consistency of internalized string equality implying reference
499   // equality.
500   //
501   // We therefore try to optimistically read from the string table without
502   // taking the lock (both here and in the NoAllocate version of the lookup),
503   // and on a miss we take the lock and try to write the entry, with a second
504   // read lookup in case the non-locked read missed a write.
505   //
506   // One complication is allocation -- we don't want to allocate while holding
507   // the string table lock. This applies to both allocation of new strings, and
508   // re-allocation of the string table on resize. So, we optimistically allocate
509   // (without copying values) outside the lock, and potentially discard the
510   // allocation if another write also did an allocation. This assumes that
511   // writes are rarer than reads.
512 
513   // Load the current string table data, in case another thread updates the
514   // data while we're reading.
515   const Data* current_data = data_.load(std::memory_order_acquire);
516 
517   // First try to find the string in the table. This is safe to do even if the
518   // table is now reallocated; we won't find a stale entry in the old table
519   // because the new table won't delete it's corresponding entry until the
520   // string is dead, in which case it will die in this table too and worst
521   // case we'll have a false miss.
522   InternalIndex entry = current_data->FindEntry(isolate, key, key->hash());
523   if (entry.is_found()) {
524     Handle<String> result(String::cast(current_data->Get(isolate, entry)),
525                           isolate);
526     DCHECK_IMPLIES(FLAG_shared_string_table, result->InSharedHeap());
527     return result;
528   }
529 
530   // No entry found, so adding new string.
531   key->PrepareForInsertion(isolate);
532   {
533     base::MutexGuard table_write_guard(&write_mutex_);
534 
535     Data* data = EnsureCapacity(isolate, 1);
536 
537     // Check one last time if the key is present in the table, in case it was
538     // added after the check.
539     entry = data->FindEntryOrInsertionEntry(isolate, key, key->hash());
540 
541     Object element = data->Get(isolate, entry);
542     if (element == empty_element()) {
543       // This entry is empty, so write it and register that we added an
544       // element.
545       Handle<String> new_string = key->GetHandleForInsertion();
546       DCHECK_IMPLIES(FLAG_shared_string_table, new_string->IsShared());
547       data->Set(entry, *new_string);
548       data->ElementAdded();
549       return new_string;
550     } else if (element == deleted_element()) {
551       // This entry was deleted, so overwrite it and register that we
552       // overwrote a deleted element.
553       Handle<String> new_string = key->GetHandleForInsertion();
554       DCHECK_IMPLIES(FLAG_shared_string_table, new_string->IsShared());
555       data->Set(entry, *new_string);
556       data->DeletedElementOverwritten();
557       return new_string;
558     } else {
559       // Return the existing string as a handle.
560       return handle(String::cast(element), isolate);
561     }
562   }
563 }
564 
565 template Handle<String> StringTable::LookupKey(Isolate* isolate,
566                                                OneByteStringKey* key);
567 template Handle<String> StringTable::LookupKey(Isolate* isolate,
568                                                TwoByteStringKey* key);
569 template Handle<String> StringTable::LookupKey(Isolate* isolate,
570                                                SeqOneByteSubStringKey* key);
571 template Handle<String> StringTable::LookupKey(Isolate* isolate,
572                                                SeqTwoByteSubStringKey* key);
573 
574 template Handle<String> StringTable::LookupKey(LocalIsolate* isolate,
575                                                OneByteStringKey* key);
576 template Handle<String> StringTable::LookupKey(LocalIsolate* isolate,
577                                                TwoByteStringKey* key);
578 
579 template Handle<String> StringTable::LookupKey(Isolate* isolate,
580                                                StringTableInsertionKey* key);
581 template Handle<String> StringTable::LookupKey(LocalIsolate* isolate,
582                                                StringTableInsertionKey* key);
583 
EnsureCapacity(PtrComprCageBase cage_base,int additional_elements)584 StringTable::Data* StringTable::EnsureCapacity(PtrComprCageBase cage_base,
585                                                int additional_elements) {
586   // This call is only allowed while the write mutex is held.
587   write_mutex_.AssertHeld();
588 
589   // This load can be relaxed as the table pointer can only be modified while
590   // the lock is held.
591   Data* data = data_.load(std::memory_order_relaxed);
592 
593   // Grow or shrink table if needed. We first try to shrink the table, if it
594   // is sufficiently empty; otherwise we make sure to grow it so that it has
595   // enough space.
596   int current_capacity = data->capacity();
597   int current_nof = data->number_of_elements();
598   int capacity_after_shrinking =
599       ComputeStringTableCapacityWithShrink(current_capacity, current_nof + 1);
600 
601   int new_capacity = -1;
602   if (capacity_after_shrinking < current_capacity) {
603     DCHECK(StringTableHasSufficientCapacityToAdd(capacity_after_shrinking,
604                                                  current_nof, 0, 1));
605     new_capacity = capacity_after_shrinking;
606   } else if (!StringTableHasSufficientCapacityToAdd(
607                  current_capacity, current_nof,
608                  data->number_of_deleted_elements(), 1)) {
609     new_capacity = ComputeStringTableCapacity(current_nof + 1);
610   }
611 
612   if (new_capacity != -1) {
613     std::unique_ptr<Data> new_data =
614         Data::Resize(cage_base, std::unique_ptr<Data>(data), new_capacity);
615     // `new_data` is the new owner of `data`.
616     DCHECK_EQ(new_data->PreviousData(), data);
617     // Release-store the new data pointer as `data_`, so that it can be
618     // acquire-loaded by other threads. This string table becomes the owner of
619     // the pointer.
620     data = new_data.release();
621     data_.store(data, std::memory_order_release);
622   }
623 
624   return data;
625 }
626 
627 // static
628 template <typename Char>
TryStringToIndexOrLookupExisting(Isolate * isolate,String string,String source,size_t start)629 Address StringTable::Data::TryStringToIndexOrLookupExisting(Isolate* isolate,
630                                                             String string,
631                                                             String source,
632                                                             size_t start) {
633   // TODO(leszeks): This method doesn't really belong on StringTable::Data.
634   // Ideally it would be a free function in an anonymous namespace, but that
635   // causes issues around method and class visibility.
636 
637   DisallowGarbageCollection no_gc;
638   uint64_t seed = HashSeed(isolate);
639 
640   int length = string.length();
641 
642   std::unique_ptr<Char[]> buffer;
643   const Char* chars;
644 
645   SharedStringAccessGuardIfNeeded access_guard(isolate);
646   if (source.IsConsString(isolate)) {
647     DCHECK(!source.IsFlat(isolate));
648     buffer.reset(new Char[length]);
649     String::WriteToFlat(source, buffer.get(), 0, length, isolate, access_guard);
650     chars = buffer.get();
651   } else {
652     chars = source.GetChars<Char>(isolate, no_gc, access_guard) + start;
653   }
654   // TODO(verwaest): Internalize to one-byte when possible.
655   SequentialStringKey<Char> key(base::Vector<const Char>(chars, length), seed);
656 
657   // String could be an array index.
658   uint32_t raw_hash_field = key.raw_hash_field();
659 
660   if (Name::ContainsCachedArrayIndex(raw_hash_field)) {
661     return Smi::FromInt(String::ArrayIndexValueBits::decode(raw_hash_field))
662         .ptr();
663   }
664 
665   if (Name::IsIntegerIndex(raw_hash_field)) {
666     // It is an index, but it's not cached.
667     return Smi::FromInt(ResultSentinel::kUnsupported).ptr();
668   }
669 
670   Data* string_table_data =
671       isolate->string_table()->data_.load(std::memory_order_acquire);
672 
673   InternalIndex entry = string_table_data->FindEntry(isolate, &key, key.hash());
674   if (entry.is_not_found()) {
675     // A string that's not an array index, and not in the string table,
676     // cannot have been used as a property name before.
677     return Smi::FromInt(ResultSentinel::kNotFound).ptr();
678   }
679 
680   String internalized = String::cast(string_table_data->Get(isolate, entry));
681   // string can be internalized here, if another thread internalized it.
682   // If we found and entry in the string table and string is not internalized,
683   // there is no way that it can transition to internalized later on. So a last
684   // check here is sufficient.
685   if (!string.IsInternalizedString()) {
686     string.MakeThin(isolate, internalized);
687   } else {
688     DCHECK(FLAG_shared_string_table);
689   }
690   return internalized.ptr();
691 }
692 
693 // static
TryStringToIndexOrLookupExisting(Isolate * isolate,Address raw_string)694 Address StringTable::TryStringToIndexOrLookupExisting(Isolate* isolate,
695                                                       Address raw_string) {
696   String string = String::cast(Object(raw_string));
697   if (string.IsInternalizedString()) {
698     // string could be internalized, if the string table is shared and another
699     // thread internalized it.
700     DCHECK(FLAG_shared_string_table);
701     return raw_string;
702   }
703 
704   // Valid array indices are >= 0, so they cannot be mixed up with any of
705   // the result sentinels, which are negative.
706   STATIC_ASSERT(
707       !String::ArrayIndexValueBits::is_valid(ResultSentinel::kUnsupported));
708   STATIC_ASSERT(
709       !String::ArrayIndexValueBits::is_valid(ResultSentinel::kNotFound));
710 
711   size_t start = 0;
712   String source = string;
713   if (source.IsSlicedString()) {
714     SlicedString sliced = SlicedString::cast(source);
715     start = sliced.offset();
716     source = sliced.parent();
717   } else if (source.IsConsString() && source.IsFlat()) {
718     source = ConsString::cast(source).first();
719   }
720   if (source.IsThinString()) {
721     source = ThinString::cast(source).actual();
722     if (string.length() == source.length()) {
723       return source.ptr();
724     }
725   }
726 
727   if (source.IsOneByteRepresentation()) {
728     return StringTable::Data::TryStringToIndexOrLookupExisting<uint8_t>(
729         isolate, string, source, start);
730   }
731   return StringTable::Data::TryStringToIndexOrLookupExisting<uint16_t>(
732       isolate, string, source, start);
733 }
734 
Print(PtrComprCageBase cage_base) const735 void StringTable::Print(PtrComprCageBase cage_base) const {
736   data_.load(std::memory_order_acquire)->Print(cage_base);
737 }
738 
GetCurrentMemoryUsage() const739 size_t StringTable::GetCurrentMemoryUsage() const {
740   return sizeof(*this) +
741          data_.load(std::memory_order_acquire)->GetCurrentMemoryUsage();
742 }
743 
IterateElements(RootVisitor * visitor)744 void StringTable::IterateElements(RootVisitor* visitor) {
745   // This should only happen during garbage collection when background threads
746   // are paused, so the load can be relaxed.
747   isolate_->heap()->safepoint()->AssertActive();
748   data_.load(std::memory_order_relaxed)->IterateElements(visitor);
749 }
750 
DropOldData()751 void StringTable::DropOldData() {
752   // This should only happen during garbage collection when background threads
753   // are paused, so the load can be relaxed.
754   isolate_->heap()->safepoint()->AssertActive();
755   DCHECK_NE(isolate_->heap()->gc_state(), Heap::NOT_IN_GC);
756   data_.load(std::memory_order_relaxed)->DropPreviousData();
757 }
758 
NotifyElementsRemoved(int count)759 void StringTable::NotifyElementsRemoved(int count) {
760   // This should only happen during garbage collection when background threads
761   // are paused, so the load can be relaxed.
762   isolate_->heap()->safepoint()->AssertActive();
763   DCHECK_NE(isolate_->heap()->gc_state(), Heap::NOT_IN_GC);
764   data_.load(std::memory_order_relaxed)->ElementsRemoved(count);
765 }
766 
UpdateCountersIfOwnedBy(Isolate * isolate)767 void StringTable::UpdateCountersIfOwnedBy(Isolate* isolate) {
768   DCHECK_EQ(isolate->string_table(), this);
769   if (!isolate->OwnsStringTable()) return;
770   isolate->counters()->string_table_capacity()->Set(Capacity());
771   isolate->counters()->number_of_symbols()->Set(NumberOfElements());
772 }
773 
774 }  // namespace internal
775 }  // namespace v8
776