Home | History | Annotate | Download | only in objects
      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 
    114   int NumberOfElements() const {
    115     return Smi::ToInt(get(kNumberOfElementsIndex));
    116   }
    117 
    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.
    124   int UsedCapacity() const {
    125     return NumberOfElements() + NumberOfDeletedElements();
    126   }
    127 
    128   int NumberOfBuckets() const { return Smi::ToInt(get(kNumberOfBucketsIndex)); }
    129 
    130   // Returns an index into |this| for the given entry.
    131   int EntryToIndex(int entry) {
    132     return kHashTableStartIndex + NumberOfBuckets() + (entry * kEntrySize);
    133   }
    134 
    135   int HashToBucket(int hash) { return hash & (NumberOfBuckets() - 1); }
    136 
    137   int HashToEntry(int hash) {
    138     int bucket = HashToBucket(hash);
    139     Object* entry = this->get(kHashTableStartIndex + bucket);
    140     return Smi::ToInt(entry);
    141   }
    142 
    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 
    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 
    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.
    175   Object* KeyAt(int entry) {
    176     DCHECK_LT(entry, this->UsedCapacity());
    177     return get(EntryToIndex(entry));
    178   }
    179 
    180   bool IsObsolete() { return !get(kNextTableIndex)->IsSmi(); }
    181 
    182   // The next newer table. This is only valid if the table is obsolete.
    183   Derived* NextTable() { return Derived::cast(get(kNextTableIndex)); }
    184 
    185   // When the table is obsolete we store the indexes of the removed holes.
    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 
    201   void SetNumberOfBuckets(int num) {
    202     set(kNumberOfBucketsIndex, Smi::FromInt(num));
    203   }
    204 
    205   void SetNumberOfElements(int num) {
    206     set(kNumberOfElementsIndex, Smi::FromInt(num));
    207   }
    208 
    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.
    214   int Capacity() { return NumberOfBuckets() * kLoadFactor; }
    215 
    216   void SetNextTable(Derived* next_table) { set(kNextTableIndex, next_table); }
    217 
    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.
    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.
    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.
    357   int NumberOfElements() const {
    358     int nof_elements = getByte(kNumberOfElementsOffset, 0);
    359     DCHECK_LE(nof_elements, Capacity());
    360 
    361     return nof_elements;
    362   }
    363 
    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 
    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.
    403   Offset GetBucketsStartOffset() const {
    404     int capacity = Capacity();
    405     int data_table_size = DataTableSizeFor(capacity);
    406     return kDataTableStartOffset + data_table_size;
    407   }
    408 
    409   Address GetHashTableStartAddress(int capacity) const {
    410     return FIELD_ADDR(this, kDataTableStartOffset + DataTableSizeFor(capacity));
    411   }
    412 
    413   void SetFirstEntry(int bucket, byte value) {
    414     DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets());
    415     setByte(GetBucketsStartOffset(), bucket, value);
    416   }
    417 
    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.
    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 
    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 
    442   int GetNextEntry(int entry) const {
    443     DCHECK_LT(entry, Capacity());
    444     return getByte(GetChainTableOffset(), entry);
    445   }
    446 
    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 
    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 
    460   int HashToBucket(int hash) const { return hash & (NumberOfBuckets() - 1); }
    461 
    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 
    469   void SetNumberOfBuckets(int num) { setByte(kNumberOfBucketsOffset, 0, num); }
    470 
    471   void SetNumberOfElements(int num) {
    472     DCHECK_LE(static_cast<unsigned>(num), Capacity());
    473     setByte(kNumberOfElementsOffset, 0, num);
    474   }
    475 
    476   void SetNumberOfDeletedElements(int num) {
    477     DCHECK_LE(static_cast<unsigned>(num), Capacity());
    478     setByte(kNumberOfDeletedElementsOffset, 0, num);
    479   }
    480 
    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 
    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.
    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 
    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 
    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 
    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.
    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