• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2018 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 #ifndef V8_OBJECTS_ORDERED_HASH_TABLE_H_
6 #define V8_OBJECTS_ORDERED_HASH_TABLE_H_
7 
8 #include "src/globals.h"
9 #include "src/objects/fixed-array.h"
10 
11 // Has to be the last include (doesn't have include guards):
12 #include "src/objects/object-macros.h"
13 
14 namespace v8 {
15 namespace internal {
16 
17 // Non-templatized base class for {OrderedHashTable}s.
18 // TODO(hash): Unify this with the HashTableBase above.
19 class OrderedHashTableBase : public FixedArray {
20  public:
21   static const int kNotFound = -1;
22   static const int kMinCapacity = 4;
23 
24   static const int kNumberOfElementsIndex = 0;
25   // The next table is stored at the same index as the nof elements.
26   static const int kNextTableIndex = kNumberOfElementsIndex;
27   static const int kNumberOfDeletedElementsIndex = kNumberOfElementsIndex + 1;
28   static const int kNumberOfBucketsIndex = kNumberOfDeletedElementsIndex + 1;
29   static const int kHashTableStartIndex = kNumberOfBucketsIndex + 1;
30   static const int kRemovedHolesIndex = kHashTableStartIndex;
31 
32   static constexpr const int kNumberOfElementsOffset =
33       FixedArray::OffsetOfElementAt(kNumberOfElementsIndex);
34   static constexpr const int kNextTableOffset =
35       FixedArray::OffsetOfElementAt(kNextTableIndex);
36   static constexpr const int kNumberOfDeletedElementsOffset =
37       FixedArray::OffsetOfElementAt(kNumberOfDeletedElementsIndex);
38   static constexpr const int kNumberOfBucketsOffset =
39       FixedArray::OffsetOfElementAt(kNumberOfBucketsIndex);
40   static constexpr const int kHashTableStartOffset =
41       FixedArray::OffsetOfElementAt(kHashTableStartIndex);
42 
43   static const int kLoadFactor = 2;
44 
45   // NumberOfDeletedElements is set to kClearedTableSentinel when
46   // the table is cleared, which allows iterator transitions to
47   // optimize that case.
48   static const int kClearedTableSentinel = -1;
49 };
50 
51 // OrderedHashTable is a HashTable with Object keys that preserves
52 // insertion order. There are Map and Set interfaces (OrderedHashMap
53 // and OrderedHashTable, below). It is meant to be used by JSMap/JSSet.
54 //
55 // Only Object* keys are supported, with Object::SameValueZero() used as the
56 // equality operator and Object::GetHash() for the hash function.
57 //
58 // Based on the "Deterministic Hash Table" as described by Jason Orendorff at
59 // https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables
60 // Originally attributed to Tyler Close.
61 //
62 // Memory layout:
63 //   [0]: element count
64 //   [1]: deleted element count
65 //   [2]: bucket count
66 //   [3..(3 + NumberOfBuckets() - 1)]: "hash table", where each item is an
67 //                            offset into the data table (see below) where the
68 //                            first item in this bucket is stored.
69 //   [3 + NumberOfBuckets()..length]: "data table", an array of length
70 //                            Capacity() * kEntrySize, where the first entrysize
71 //                            items are handled by the derived class and the
72 //                            item at kChainOffset is another entry into the
73 //                            data table indicating the next entry in this hash
74 //                            bucket.
75 //
76 // When we transition the table to a new version we obsolete it and reuse parts
77 // of the memory to store information how to transition an iterator to the new
78 // table:
79 //
80 // Memory layout for obsolete table:
81 //   [0]: bucket count
82 //   [1]: Next newer table
83 //   [2]: Number of removed holes or -1 when the table was cleared.
84 //   [3..(3 + NumberOfRemovedHoles() - 1)]: The indexes of the removed holes.
85 //   [3 + NumberOfRemovedHoles()..length]: Not used
86 //
87 template <class Derived, int entrysize>
88 class OrderedHashTable : public OrderedHashTableBase {
89  public:
90   // Returns an OrderedHashTable with a capacity of at least |capacity|.
91   static Handle<Derived> Allocate(Isolate* isolate, int capacity,
92                                   PretenureFlag pretenure = NOT_TENURED);
93 
94   // Returns an OrderedHashTable (possibly |table|) with enough space
95   // to add at least one new element.
96   static Handle<Derived> EnsureGrowable(Isolate* isolate,
97                                         Handle<Derived> table);
98 
99   // Returns an OrderedHashTable (possibly |table|) that's shrunken
100   // if possible.
101   static Handle<Derived> Shrink(Isolate* isolate, Handle<Derived> table);
102 
103   // Returns a new empty OrderedHashTable and records the clearing so that
104   // existing iterators can be updated.
105   static Handle<Derived> Clear(Isolate* isolate, Handle<Derived> table);
106 
107   // Returns true if the OrderedHashTable contains the key
108   static bool HasKey(Isolate* isolate, Derived* table, Object* key);
109 
110   // Returns a true value if the OrderedHashTable contains the key and
111   // the key has been deleted. This does not shrink the table.
112   static bool Delete(Isolate* isolate, Derived* table, Object* key);
113 
NumberOfElements()114   int NumberOfElements() const {
115     return Smi::ToInt(get(kNumberOfElementsIndex));
116   }
117 
NumberOfDeletedElements()118   int NumberOfDeletedElements() const {
119     return Smi::ToInt(get(kNumberOfDeletedElementsIndex));
120   }
121 
122   // Returns the number of contiguous entries in the data table, starting at 0,
123   // that either are real entries or have been deleted.
UsedCapacity()124   int UsedCapacity() const {
125     return NumberOfElements() + NumberOfDeletedElements();
126   }
127 
NumberOfBuckets()128   int NumberOfBuckets() const { return Smi::ToInt(get(kNumberOfBucketsIndex)); }
129 
130   // Returns an index into |this| for the given entry.
EntryToIndex(int entry)131   int EntryToIndex(int entry) {
132     return kHashTableStartIndex + NumberOfBuckets() + (entry * kEntrySize);
133   }
134 
HashToBucket(int hash)135   int HashToBucket(int hash) { return hash & (NumberOfBuckets() - 1); }
136 
HashToEntry(int hash)137   int HashToEntry(int hash) {
138     int bucket = HashToBucket(hash);
139     Object* entry = this->get(kHashTableStartIndex + bucket);
140     return Smi::ToInt(entry);
141   }
142 
KeyToFirstEntry(Isolate * isolate,Object * key)143   int KeyToFirstEntry(Isolate* isolate, Object* key) {
144     // This special cases for Smi, so that we avoid the HandleScope
145     // creation below.
146     if (key->IsSmi()) {
147       uint32_t hash = ComputeIntegerHash(Smi::ToInt(key));
148       return HashToEntry(hash & Smi::kMaxValue);
149     }
150     HandleScope scope(isolate);
151     Object* hash = key->GetHash();
152     // If the object does not have an identity hash, it was never used as a key
153     if (hash->IsUndefined(isolate)) return kNotFound;
154     return HashToEntry(Smi::ToInt(hash));
155   }
156 
FindEntry(Isolate * isolate,Object * key)157   int FindEntry(Isolate* isolate, Object* key) {
158     int entry = KeyToFirstEntry(isolate, key);
159     // Walk the chain in the bucket to find the key.
160     while (entry != kNotFound) {
161       Object* candidate_key = KeyAt(entry);
162       if (candidate_key->SameValueZero(key)) break;
163       entry = NextChainEntry(entry);
164     }
165 
166     return entry;
167   }
168 
NextChainEntry(int entry)169   int NextChainEntry(int entry) {
170     Object* next_entry = get(EntryToIndex(entry) + kChainOffset);
171     return Smi::ToInt(next_entry);
172   }
173 
174   // use KeyAt(i)->IsTheHole(isolate) to determine if this is a deleted entry.
KeyAt(int entry)175   Object* KeyAt(int entry) {
176     DCHECK_LT(entry, this->UsedCapacity());
177     return get(EntryToIndex(entry));
178   }
179 
IsObsolete()180   bool IsObsolete() { return !get(kNextTableIndex)->IsSmi(); }
181 
182   // The next newer table. This is only valid if the table is obsolete.
NextTable()183   Derived* NextTable() { return Derived::cast(get(kNextTableIndex)); }
184 
185   // When the table is obsolete we store the indexes of the removed holes.
RemovedIndexAt(int index)186   int RemovedIndexAt(int index) {
187     return Smi::ToInt(get(kRemovedHolesIndex + index));
188   }
189 
190   static const int kEntrySize = entrysize + 1;
191   static const int kChainOffset = entrysize;
192 
193   static const int kMaxCapacity =
194       (FixedArray::kMaxLength - kHashTableStartIndex) /
195       (1 + (kEntrySize * kLoadFactor));
196 
197  protected:
198   static Handle<Derived> Rehash(Isolate* isolate, Handle<Derived> table,
199                                 int new_capacity);
200 
SetNumberOfBuckets(int num)201   void SetNumberOfBuckets(int num) {
202     set(kNumberOfBucketsIndex, Smi::FromInt(num));
203   }
204 
SetNumberOfElements(int num)205   void SetNumberOfElements(int num) {
206     set(kNumberOfElementsIndex, Smi::FromInt(num));
207   }
208 
SetNumberOfDeletedElements(int num)209   void SetNumberOfDeletedElements(int num) {
210     set(kNumberOfDeletedElementsIndex, Smi::FromInt(num));
211   }
212 
213   // Returns the number elements that can fit into the allocated buffer.
Capacity()214   int Capacity() { return NumberOfBuckets() * kLoadFactor; }
215 
SetNextTable(Derived * next_table)216   void SetNextTable(Derived* next_table) { set(kNextTableIndex, next_table); }
217 
SetRemovedIndexAt(int index,int removed_index)218   void SetRemovedIndexAt(int index, int removed_index) {
219     return set(kRemovedHolesIndex + index, Smi::FromInt(removed_index));
220   }
221 };
222 
223 class OrderedHashSet : public OrderedHashTable<OrderedHashSet, 1> {
224  public:
225   DECL_CAST(OrderedHashSet)
226 
227   static Handle<OrderedHashSet> Add(Isolate* isolate,
228                                     Handle<OrderedHashSet> table,
229                                     Handle<Object> value);
230   static Handle<FixedArray> ConvertToKeysArray(Isolate* isolate,
231                                                Handle<OrderedHashSet> table,
232                                                GetKeysConversion convert);
233   static HeapObject* GetEmpty(ReadOnlyRoots ro_roots);
234   static inline int GetMapRootIndex();
235   static inline bool Is(Handle<HeapObject> table);
236 };
237 
238 class OrderedHashMap : public OrderedHashTable<OrderedHashMap, 2> {
239  public:
240   DECL_CAST(OrderedHashMap)
241 
242   // Returns a value if the OrderedHashMap contains the key, otherwise
243   // returns undefined.
244   static Handle<OrderedHashMap> Add(Isolate* isolate,
245                                     Handle<OrderedHashMap> table,
246                                     Handle<Object> key, Handle<Object> value);
247   Object* ValueAt(int entry);
248 
249   static Object* GetHash(Isolate* isolate, Object* key);
250 
251   static HeapObject* GetEmpty(ReadOnlyRoots ro_roots);
252   static inline int GetMapRootIndex();
253   static inline bool Is(Handle<HeapObject> table);
254 
255   static const int kValueOffset = 1;
256 };
257 
258 // This is similar to the OrderedHashTable, except for the memory
259 // layout where we use byte instead of Smi. The max capacity of this
260 // is only 254, we transition to an OrderedHashTable beyond that
261 // limit.
262 //
263 // Each bucket and chain value is a byte long. The padding exists so
264 // that the DataTable entries start aligned. A bucket or chain value
265 // of 255 is used to denote an unknown entry.
266 //
267 // Memory layout: [ Header ]  [ Padding ] [ DataTable ] [ HashTable ] [ Chains ]
268 //
269 // The index are represented as bytes, on a 64 bit machine with
270 // kEntrySize = 1, capacity = 4 and entries = 2:
271 //
272 // [ Header ]  :
273 //    [0] : Number of elements
274 //    [1] : Number of deleted elements
275 //    [2] : Number of buckets
276 //
277 // [ Padding ] :
278 //    [3 .. 7] : Padding
279 //
280 // [ DataTable ] :
281 //    [8  .. 15] : Entry 1
282 //    [16 .. 23] : Entry 2
283 //    [24 .. 31] : empty
284 //    [32 .. 39] : empty
285 //
286 // [ HashTable ] :
287 //    [40] : First chain-link for bucket 1
288 //    [41] : empty
289 //
290 // [ Chains ] :
291 //    [42] : Next chain link for bucket 1
292 //    [43] : empty
293 //    [44] : empty
294 //    [45] : empty
295 //
296 template <class Derived>
297 class SmallOrderedHashTable : public HeapObject {
298  public:
299   // Offset points to a relative location in the table
300   typedef int Offset;
301 
302   // ByteIndex points to a index in the table that needs to be
303   // converted to an Offset.
304   typedef int ByteIndex;
305 
306   void Initialize(Isolate* isolate, int capacity);
307 
308   static Handle<Derived> Allocate(Isolate* isolate, int capacity,
309                                   PretenureFlag pretenure = NOT_TENURED);
310 
311   // Returns a true if the OrderedHashTable contains the key
312   bool HasKey(Isolate* isolate, Handle<Object> key);
313 
314   // Returns a true value if the table contains the key and
315   // the key has been deleted. This does not shrink the table.
316   static bool Delete(Isolate* isolate, Derived* table, Object* key);
317 
318   // Returns an SmallOrderedHashTable (possibly |table|) with enough
319   // space to add at least one new element. Returns empty handle if
320   // we've already reached MaxCapacity.
321   static MaybeHandle<Derived> Grow(Isolate* isolate, Handle<Derived> table);
322 
323   static Handle<Derived> Rehash(Isolate* isolate, Handle<Derived> table,
324                                 int new_capacity);
325 
326   // Iterates only fields in the DataTable.
327   class BodyDescriptor;
328 
329   // No weak fields.
330   typedef BodyDescriptor BodyDescriptorWeak;
331 
332   // Returns total size in bytes required for a table of given
333   // capacity.
SizeFor(int capacity)334   static int SizeFor(int capacity) {
335     DCHECK_GE(capacity, kMinCapacity);
336     DCHECK_LE(capacity, kMaxCapacity);
337 
338     int data_table_size = DataTableSizeFor(capacity);
339     int hash_table_size = capacity / kLoadFactor;
340     int chain_table_size = capacity;
341     int total_size = kDataTableStartOffset + data_table_size + hash_table_size +
342                      chain_table_size;
343 
344     return RoundUp(total_size, kPointerSize);
345   }
346 
347   // Returns the number elements that can fit into the allocated table.
Capacity()348   int Capacity() const {
349     int capacity = NumberOfBuckets() * kLoadFactor;
350     DCHECK_GE(capacity, kMinCapacity);
351     DCHECK_LE(capacity, kMaxCapacity);
352 
353     return capacity;
354   }
355 
356   // Returns the number elements that are present in the table.
NumberOfElements()357   int NumberOfElements() const {
358     int nof_elements = getByte(kNumberOfElementsOffset, 0);
359     DCHECK_LE(nof_elements, Capacity());
360 
361     return nof_elements;
362   }
363 
NumberOfDeletedElements()364   int NumberOfDeletedElements() const {
365     int nof_deleted_elements = getByte(kNumberOfDeletedElementsOffset, 0);
366     DCHECK_LE(nof_deleted_elements, Capacity());
367 
368     return nof_deleted_elements;
369   }
370 
NumberOfBuckets()371   int NumberOfBuckets() const { return getByte(kNumberOfBucketsOffset, 0); }
372 
373   DECL_VERIFIER(SmallOrderedHashTable)
374 
375   static const int kMinCapacity = 4;
376   static const byte kNotFound = 0xFF;
377 
378   // We use the value 255 to indicate kNotFound for chain and bucket
379   // values, which means that this value can't be used a valid
380   // index.
381   static const int kMaxCapacity = 254;
382   STATIC_ASSERT(kMaxCapacity < kNotFound);
383 
384   // The load factor is used to derive the number of buckets from
385   // capacity during Allocation. We also depend on this to calaculate
386   // the capacity from number of buckets after allocation. If we
387   // decide to change kLoadFactor to something other than 2, capacity
388   // should be stored as another field of this object.
389   static const int kLoadFactor = 2;
390 
391   // Our growth strategy involves doubling the capacity until we reach
392   // kMaxCapacity, but since the kMaxCapacity is always less than 256,
393   // we will never fully utilize this table. We special case for 256,
394   // by changing the new capacity to be kMaxCapacity in
395   // SmallOrderedHashTable::Grow.
396   static const int kGrowthHack = 256;
397 
398  protected:
399   void SetDataEntry(int entry, int relative_index, Object* value);
400 
401   // TODO(gsathya): Calculate all the various possible values for this
402   // at compile time since capacity can only be 4 different values.
GetBucketsStartOffset()403   Offset GetBucketsStartOffset() const {
404     int capacity = Capacity();
405     int data_table_size = DataTableSizeFor(capacity);
406     return kDataTableStartOffset + data_table_size;
407   }
408 
GetHashTableStartAddress(int capacity)409   Address GetHashTableStartAddress(int capacity) const {
410     return FIELD_ADDR(this, kDataTableStartOffset + DataTableSizeFor(capacity));
411   }
412 
SetFirstEntry(int bucket,byte value)413   void SetFirstEntry(int bucket, byte value) {
414     DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets());
415     setByte(GetBucketsStartOffset(), bucket, value);
416   }
417 
GetFirstEntry(int bucket)418   int GetFirstEntry(int bucket) const {
419     DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets());
420     return getByte(GetBucketsStartOffset(), bucket);
421   }
422 
423   // TODO(gsathya): Calculate all the various possible values for this
424   // at compile time since capacity can only be 4 different values.
GetChainTableOffset()425   Offset GetChainTableOffset() const {
426     int nof_buckets = NumberOfBuckets();
427     int capacity = nof_buckets * kLoadFactor;
428     DCHECK_EQ(Capacity(), capacity);
429 
430     int data_table_size = DataTableSizeFor(capacity);
431     int hash_table_size = nof_buckets;
432     return kDataTableStartOffset + data_table_size + hash_table_size;
433   }
434 
SetNextEntry(int entry,int next_entry)435   void SetNextEntry(int entry, int next_entry) {
436     DCHECK_LT(static_cast<unsigned>(entry), Capacity());
437     DCHECK_GE(static_cast<unsigned>(next_entry), 0);
438     DCHECK(next_entry <= Capacity() || next_entry == kNotFound);
439     setByte(GetChainTableOffset(), entry, next_entry);
440   }
441 
GetNextEntry(int entry)442   int GetNextEntry(int entry) const {
443     DCHECK_LT(entry, Capacity());
444     return getByte(GetChainTableOffset(), entry);
445   }
446 
GetDataEntry(int entry,int relative_index)447   Object* GetDataEntry(int entry, int relative_index) {
448     DCHECK_LT(entry, Capacity());
449     DCHECK_LE(static_cast<unsigned>(relative_index), Derived::kEntrySize);
450     Offset entry_offset = GetDataEntryOffset(entry, relative_index);
451     return READ_FIELD(this, entry_offset);
452   }
453 
KeyAt(int entry)454   Object* KeyAt(int entry) const {
455     DCHECK_LT(entry, Capacity());
456     Offset entry_offset = GetDataEntryOffset(entry, Derived::kKeyIndex);
457     return READ_FIELD(this, entry_offset);
458   }
459 
HashToBucket(int hash)460   int HashToBucket(int hash) const { return hash & (NumberOfBuckets() - 1); }
461 
HashToFirstEntry(int hash)462   int HashToFirstEntry(int hash) const {
463     int bucket = HashToBucket(hash);
464     int entry = GetFirstEntry(bucket);
465     DCHECK(entry < Capacity() || entry == kNotFound);
466     return entry;
467   }
468 
SetNumberOfBuckets(int num)469   void SetNumberOfBuckets(int num) { setByte(kNumberOfBucketsOffset, 0, num); }
470 
SetNumberOfElements(int num)471   void SetNumberOfElements(int num) {
472     DCHECK_LE(static_cast<unsigned>(num), Capacity());
473     setByte(kNumberOfElementsOffset, 0, num);
474   }
475 
SetNumberOfDeletedElements(int num)476   void SetNumberOfDeletedElements(int num) {
477     DCHECK_LE(static_cast<unsigned>(num), Capacity());
478     setByte(kNumberOfDeletedElementsOffset, 0, num);
479   }
480 
FindEntry(Isolate * isolate,Object * key)481   int FindEntry(Isolate* isolate, Object* key) {
482     DisallowHeapAllocation no_gc;
483     Object* hash = key->GetHash();
484 
485     if (hash->IsUndefined(isolate)) return kNotFound;
486     int entry = HashToFirstEntry(Smi::ToInt(hash));
487 
488     // Walk the chain in the bucket to find the key.
489     while (entry != kNotFound) {
490       Object* candidate_key = KeyAt(entry);
491       if (candidate_key->SameValueZero(key)) return entry;
492       entry = GetNextEntry(entry);
493     }
494     return kNotFound;
495   }
496 
497   static const Offset kNumberOfElementsOffset = kHeaderSize;
498   static const Offset kNumberOfDeletedElementsOffset =
499       kNumberOfElementsOffset + kOneByteSize;
500   static const Offset kNumberOfBucketsOffset =
501       kNumberOfDeletedElementsOffset + kOneByteSize;
502   static const constexpr Offset kDataTableStartOffset =
503       RoundUp<kPointerSize>(kNumberOfBucketsOffset);
504 
DataTableSizeFor(int capacity)505   static constexpr int DataTableSizeFor(int capacity) {
506     return capacity * Derived::kEntrySize * kPointerSize;
507   }
508 
509   // This is used for accessing the non |DataTable| part of the
510   // structure.
getByte(Offset offset,ByteIndex index)511   byte getByte(Offset offset, ByteIndex index) const {
512     DCHECK(offset < kDataTableStartOffset || offset >= GetBucketsStartOffset());
513     return READ_BYTE_FIELD(this, offset + (index * kOneByteSize));
514   }
515 
setByte(Offset offset,ByteIndex index,byte value)516   void setByte(Offset offset, ByteIndex index, byte value) {
517     DCHECK(offset < kDataTableStartOffset || offset >= GetBucketsStartOffset());
518     WRITE_BYTE_FIELD(this, offset + (index * kOneByteSize), value);
519   }
520 
GetDataEntryOffset(int entry,int relative_index)521   Offset GetDataEntryOffset(int entry, int relative_index) const {
522     DCHECK_LT(entry, Capacity());
523     int offset_in_datatable = entry * Derived::kEntrySize * kPointerSize;
524     int offset_in_entry = relative_index * kPointerSize;
525     return kDataTableStartOffset + offset_in_datatable + offset_in_entry;
526   }
527 
UsedCapacity()528   int UsedCapacity() const {
529     int used = NumberOfElements() + NumberOfDeletedElements();
530     DCHECK_LE(used, Capacity());
531 
532     return used;
533   }
534 
535  private:
536   friend class OrderedHashMapHandler;
537   friend class OrderedHashSetHandler;
538   friend class CodeStubAssembler;
539 };
540 
541 class SmallOrderedHashSet : public SmallOrderedHashTable<SmallOrderedHashSet> {
542  public:
543   DECL_CAST(SmallOrderedHashSet)
544 
545   DECL_PRINTER(SmallOrderedHashSet)
546 
547   static const int kKeyIndex = 0;
548   static const int kEntrySize = 1;
549 
550   // Adds |value| to |table|, if the capacity isn't enough, a new
551   // table is created. The original |table| is returned if there is
552   // capacity to store |value| otherwise the new table is returned.
553   static MaybeHandle<SmallOrderedHashSet> Add(Isolate* isolate,
554                                               Handle<SmallOrderedHashSet> table,
555                                               Handle<Object> key);
556   static inline bool Is(Handle<HeapObject> table);
557   static inline int GetMapRootIndex();
558 };
559 
560 class SmallOrderedHashMap : public SmallOrderedHashTable<SmallOrderedHashMap> {
561  public:
562   DECL_CAST(SmallOrderedHashMap)
563 
564   DECL_PRINTER(SmallOrderedHashMap)
565 
566   static const int kKeyIndex = 0;
567   static const int kValueIndex = 1;
568   static const int kEntrySize = 2;
569 
570   // Adds |value| to |table|, if the capacity isn't enough, a new
571   // table is created. The original |table| is returned if there is
572   // capacity to store |value| otherwise the new table is returned.
573   static MaybeHandle<SmallOrderedHashMap> Add(Isolate* isolate,
574                                               Handle<SmallOrderedHashMap> table,
575                                               Handle<Object> key,
576                                               Handle<Object> value);
577   static inline bool Is(Handle<HeapObject> table);
578   static inline int GetMapRootIndex();
579 };
580 
581 // TODO(gsathya): Rename this to OrderedHashTable, after we rename
582 // OrderedHashTable to LargeOrderedHashTable. Also set up a
583 // OrderedHashSetBase class as a base class for the two tables and use
584 // that instead of a HeapObject here.
585 template <class SmallTable, class LargeTable>
586 class OrderedHashTableHandler {
587  public:
588   typedef int Entry;
589 
590   static Handle<HeapObject> Allocate(Isolate* isolate, int capacity);
591   static bool Delete(Handle<HeapObject> table, Handle<Object> key);
592   static bool HasKey(Isolate* isolate, Handle<HeapObject> table,
593                      Handle<Object> key);
594 
595   // TODO(gsathya): Move this to OrderedHashTable
596   static const int OrderedHashTableMinSize =
597       SmallOrderedHashTable<SmallTable>::kGrowthHack << 1;
598 };
599 
600 class OrderedHashMapHandler
601     : public OrderedHashTableHandler<SmallOrderedHashMap, OrderedHashMap> {
602  public:
603   static Handle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table,
604                                 Handle<Object> key, Handle<Object> value);
605   static Handle<OrderedHashMap> AdjustRepresentation(
606       Isolate* isolate, Handle<SmallOrderedHashMap> table);
607 };
608 
609 class OrderedHashSetHandler
610     : public OrderedHashTableHandler<SmallOrderedHashSet, OrderedHashSet> {
611  public:
612   static Handle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table,
613                                 Handle<Object> key);
614   static Handle<OrderedHashSet> AdjustRepresentation(
615       Isolate* isolate, Handle<SmallOrderedHashSet> table);
616 };
617 
618 class JSCollectionIterator : public JSObject {
619  public:
620   // [table]: the backing hash table mapping keys to values.
621   DECL_ACCESSORS(table, Object)
622 
623   // [index]: The index into the data table.
624   DECL_ACCESSORS(index, Object)
625 
626   // Dispatched behavior.
627   DECL_PRINTER(JSCollectionIterator)
628 
629   static const int kTableOffset = JSObject::kHeaderSize;
630   static const int kIndexOffset = kTableOffset + kPointerSize;
631   static const int kSize = kIndexOffset + kPointerSize;
632 
633  private:
634   DISALLOW_IMPLICIT_CONSTRUCTORS(JSCollectionIterator);
635 };
636 
637 // OrderedHashTableIterator is an iterator that iterates over the keys and
638 // values of an OrderedHashTable.
639 //
640 // The iterator has a reference to the underlying OrderedHashTable data,
641 // [table], as well as the current [index] the iterator is at.
642 //
643 // When the OrderedHashTable is rehashed it adds a reference from the old table
644 // to the new table as well as storing enough data about the changes so that the
645 // iterator [index] can be adjusted accordingly.
646 //
647 // When the [Next] result from the iterator is requested, the iterator checks if
648 // there is a newer table that it needs to transition to.
649 template <class Derived, class TableType>
650 class OrderedHashTableIterator : public JSCollectionIterator {
651  public:
652   // Whether the iterator has more elements. This needs to be called before
653   // calling |CurrentKey| and/or |CurrentValue|.
654   bool HasMore();
655 
656   // Move the index forward one.
MoveNext()657   void MoveNext() { set_index(Smi::FromInt(Smi::ToInt(index()) + 1)); }
658 
659   // Returns the current key of the iterator. This should only be called when
660   // |HasMore| returns true.
661   inline Object* CurrentKey();
662 
663  private:
664   // Transitions the iterator to the non obsolete backing store. This is a NOP
665   // if the [table] is not obsolete.
666   void Transition();
667 
668   DISALLOW_IMPLICIT_CONSTRUCTORS(OrderedHashTableIterator);
669 };
670 
671 }  // namespace internal
672 }  // namespace v8
673 
674 #include "src/objects/object-macros-undef.h"
675 
676 #endif  // V8_OBJECTS_ORDERED_HASH_TABLE_H_
677