| // Copyright 2018 the V8 project authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #ifndef V8_OBJECTS_ORDERED_HASH_TABLE_H_ | |
| #define V8_OBJECTS_ORDERED_HASH_TABLE_H_ | |
| #include "src/globals.h" | |
| #include "src/objects/fixed-array.h" | |
| // Has to be the last include (doesn't have include guards): | |
| #include "src/objects/object-macros.h" | |
| namespace v8 { | |
| namespace internal { | |
| // Non-templatized base class for {OrderedHashTable}s. | |
| // TODO(hash): Unify this with the HashTableBase above. | |
| class OrderedHashTableBase : public FixedArray { | |
| public: | |
| static const int kNotFound = -1; | |
| static const int kMinCapacity = 4; | |
| static const int kNumberOfElementsIndex = 0; | |
| // The next table is stored at the same index as the nof elements. | |
| static const int kNextTableIndex = kNumberOfElementsIndex; | |
| static const int kNumberOfDeletedElementsIndex = kNumberOfElementsIndex + 1; | |
| static const int kNumberOfBucketsIndex = kNumberOfDeletedElementsIndex + 1; | |
| static const int kHashTableStartIndex = kNumberOfBucketsIndex + 1; | |
| static const int kRemovedHolesIndex = kHashTableStartIndex; | |
| static constexpr const int kNumberOfElementsOffset = | |
| FixedArray::OffsetOfElementAt(kNumberOfElementsIndex); | |
| static constexpr const int kNextTableOffset = | |
| FixedArray::OffsetOfElementAt(kNextTableIndex); | |
| static constexpr const int kNumberOfDeletedElementsOffset = | |
| FixedArray::OffsetOfElementAt(kNumberOfDeletedElementsIndex); | |
| static constexpr const int kNumberOfBucketsOffset = | |
| FixedArray::OffsetOfElementAt(kNumberOfBucketsIndex); | |
| static constexpr const int kHashTableStartOffset = | |
| FixedArray::OffsetOfElementAt(kHashTableStartIndex); | |
| static const int kLoadFactor = 2; | |
| // NumberOfDeletedElements is set to kClearedTableSentinel when | |
| // the table is cleared, which allows iterator transitions to | |
| // optimize that case. | |
| static const int kClearedTableSentinel = -1; | |
| }; | |
| // OrderedHashTable is a HashTable with Object keys that preserves | |
| // insertion order. There are Map and Set interfaces (OrderedHashMap | |
| // and OrderedHashTable, below). It is meant to be used by JSMap/JSSet. | |
| // | |
| // Only Object* keys are supported, with Object::SameValueZero() used as the | |
| // equality operator and Object::GetHash() for the hash function. | |
| // | |
| // Based on the "Deterministic Hash Table" as described by Jason Orendorff at | |
| // https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables | |
| // Originally attributed to Tyler Close. | |
| // | |
| // Memory layout: | |
| // [0]: element count | |
| // [1]: deleted element count | |
| // [2]: bucket count | |
| // [3..(3 + NumberOfBuckets() - 1)]: "hash table", where each item is an | |
| // offset into the data table (see below) where the | |
| // first item in this bucket is stored. | |
| // [3 + NumberOfBuckets()..length]: "data table", an array of length | |
| // Capacity() * kEntrySize, where the first entrysize | |
| // items are handled by the derived class and the | |
| // item at kChainOffset is another entry into the | |
| // data table indicating the next entry in this hash | |
| // bucket. | |
| // | |
| // When we transition the table to a new version we obsolete it and reuse parts | |
| // of the memory to store information how to transition an iterator to the new | |
| // table: | |
| // | |
| // Memory layout for obsolete table: | |
| // [0]: bucket count | |
| // [1]: Next newer table | |
| // [2]: Number of removed holes or -1 when the table was cleared. | |
| // [3..(3 + NumberOfRemovedHoles() - 1)]: The indexes of the removed holes. | |
| // [3 + NumberOfRemovedHoles()..length]: Not used | |
| // | |
| template <class Derived, int entrysize> | |
| class OrderedHashTable : public OrderedHashTableBase { | |
| public: | |
| // Returns an OrderedHashTable with a capacity of at least |capacity|. | |
| static Handle<Derived> Allocate(Isolate* isolate, int capacity, | |
| PretenureFlag pretenure = NOT_TENURED); | |
| // Returns an OrderedHashTable (possibly |table|) with enough space | |
| // to add at least one new element. | |
| static Handle<Derived> EnsureGrowable(Isolate* isolate, | |
| Handle<Derived> table); | |
| // Returns an OrderedHashTable (possibly |table|) that's shrunken | |
| // if possible. | |
| static Handle<Derived> Shrink(Isolate* isolate, Handle<Derived> table); | |
| // Returns a new empty OrderedHashTable and records the clearing so that | |
| // existing iterators can be updated. | |
| static Handle<Derived> Clear(Isolate* isolate, Handle<Derived> table); | |
| // Returns true if the OrderedHashTable contains the key | |
| static bool HasKey(Isolate* isolate, Derived* table, Object* key); | |
| // Returns a true value if the OrderedHashTable contains the key and | |
| // the key has been deleted. This does not shrink the table. | |
| static bool Delete(Isolate* isolate, Derived* table, Object* key); | |
| int NumberOfElements() const { | |
| return Smi::ToInt(get(kNumberOfElementsIndex)); | |
| } | |
| int NumberOfDeletedElements() const { | |
| return Smi::ToInt(get(kNumberOfDeletedElementsIndex)); | |
| } | |
| // Returns the number of contiguous entries in the data table, starting at 0, | |
| // that either are real entries or have been deleted. | |
| int UsedCapacity() const { | |
| return NumberOfElements() + NumberOfDeletedElements(); | |
| } | |
| int NumberOfBuckets() const { return Smi::ToInt(get(kNumberOfBucketsIndex)); } | |
| // Returns an index into |this| for the given entry. | |
| int EntryToIndex(int entry) { | |
| return kHashTableStartIndex + NumberOfBuckets() + (entry * kEntrySize); | |
| } | |
| int HashToBucket(int hash) { return hash & (NumberOfBuckets() - 1); } | |
| int HashToEntry(int hash) { | |
| int bucket = HashToBucket(hash); | |
| Object* entry = this->get(kHashTableStartIndex + bucket); | |
| return Smi::ToInt(entry); | |
| } | |
| int KeyToFirstEntry(Isolate* isolate, Object* key) { | |
| // This special cases for Smi, so that we avoid the HandleScope | |
| // creation below. | |
| if (key->IsSmi()) { | |
| uint32_t hash = ComputeIntegerHash(Smi::ToInt(key)); | |
| return HashToEntry(hash & Smi::kMaxValue); | |
| } | |
| HandleScope scope(isolate); | |
| Object* hash = key->GetHash(); | |
| // If the object does not have an identity hash, it was never used as a key | |
| if (hash->IsUndefined(isolate)) return kNotFound; | |
| return HashToEntry(Smi::ToInt(hash)); | |
| } | |
| int FindEntry(Isolate* isolate, Object* key) { | |
| int entry = KeyToFirstEntry(isolate, key); | |
| // Walk the chain in the bucket to find the key. | |
| while (entry != kNotFound) { | |
| Object* candidate_key = KeyAt(entry); | |
| if (candidate_key->SameValueZero(key)) break; | |
| entry = NextChainEntry(entry); | |
| } | |
| return entry; | |
| } | |
| int NextChainEntry(int entry) { | |
| Object* next_entry = get(EntryToIndex(entry) + kChainOffset); | |
| return Smi::ToInt(next_entry); | |
| } | |
| // use KeyAt(i)->IsTheHole(isolate) to determine if this is a deleted entry. | |
| Object* KeyAt(int entry) { | |
| DCHECK_LT(entry, this->UsedCapacity()); | |
| return get(EntryToIndex(entry)); | |
| } | |
| bool IsObsolete() { return !get(kNextTableIndex)->IsSmi(); } | |
| // The next newer table. This is only valid if the table is obsolete. | |
| Derived* NextTable() { return Derived::cast(get(kNextTableIndex)); } | |
| // When the table is obsolete we store the indexes of the removed holes. | |
| int RemovedIndexAt(int index) { | |
| return Smi::ToInt(get(kRemovedHolesIndex + index)); | |
| } | |
| static const int kEntrySize = entrysize + 1; | |
| static const int kChainOffset = entrysize; | |
| static const int kMaxCapacity = | |
| (FixedArray::kMaxLength - kHashTableStartIndex) / | |
| (1 + (kEntrySize * kLoadFactor)); | |
| protected: | |
| static Handle<Derived> Rehash(Isolate* isolate, Handle<Derived> table, | |
| int new_capacity); | |
| void SetNumberOfBuckets(int num) { | |
| set(kNumberOfBucketsIndex, Smi::FromInt(num)); | |
| } | |
| void SetNumberOfElements(int num) { | |
| set(kNumberOfElementsIndex, Smi::FromInt(num)); | |
| } | |
| void SetNumberOfDeletedElements(int num) { | |
| set(kNumberOfDeletedElementsIndex, Smi::FromInt(num)); | |
| } | |
| // Returns the number elements that can fit into the allocated buffer. | |
| int Capacity() { return NumberOfBuckets() * kLoadFactor; } | |
| void SetNextTable(Derived* next_table) { set(kNextTableIndex, next_table); } | |
| void SetRemovedIndexAt(int index, int removed_index) { | |
| return set(kRemovedHolesIndex + index, Smi::FromInt(removed_index)); | |
| } | |
| }; | |
| class OrderedHashSet : public OrderedHashTable<OrderedHashSet, 1> { | |
| public: | |
| DECL_CAST(OrderedHashSet) | |
| static Handle<OrderedHashSet> Add(Isolate* isolate, | |
| Handle<OrderedHashSet> table, | |
| Handle<Object> value); | |
| static Handle<FixedArray> ConvertToKeysArray(Isolate* isolate, | |
| Handle<OrderedHashSet> table, | |
| GetKeysConversion convert); | |
| static HeapObject* GetEmpty(ReadOnlyRoots ro_roots); | |
| static inline int GetMapRootIndex(); | |
| static inline bool Is(Handle<HeapObject> table); | |
| }; | |
| class OrderedHashMap : public OrderedHashTable<OrderedHashMap, 2> { | |
| public: | |
| DECL_CAST(OrderedHashMap) | |
| // Returns a value if the OrderedHashMap contains the key, otherwise | |
| // returns undefined. | |
| static Handle<OrderedHashMap> Add(Isolate* isolate, | |
| Handle<OrderedHashMap> table, | |
| Handle<Object> key, Handle<Object> value); | |
| Object* ValueAt(int entry); | |
| static Object* GetHash(Isolate* isolate, Object* key); | |
| static HeapObject* GetEmpty(ReadOnlyRoots ro_roots); | |
| static inline int GetMapRootIndex(); | |
| static inline bool Is(Handle<HeapObject> table); | |
| static const int kValueOffset = 1; | |
| }; | |
| // This is similar to the OrderedHashTable, except for the memory | |
| // layout where we use byte instead of Smi. The max capacity of this | |
| // is only 254, we transition to an OrderedHashTable beyond that | |
| // limit. | |
| // | |
| // Each bucket and chain value is a byte long. The padding exists so | |
| // that the DataTable entries start aligned. A bucket or chain value | |
| // of 255 is used to denote an unknown entry. | |
| // | |
| // Memory layout: [ Header ] [ Padding ] [ DataTable ] [ HashTable ] [ Chains ] | |
| // | |
| // The index are represented as bytes, on a 64 bit machine with | |
| // kEntrySize = 1, capacity = 4 and entries = 2: | |
| // | |
| // [ Header ] : | |
| // [0] : Number of elements | |
| // [1] : Number of deleted elements | |
| // [2] : Number of buckets | |
| // | |
| // [ Padding ] : | |
| // [3 .. 7] : Padding | |
| // | |
| // [ DataTable ] : | |
| // [8 .. 15] : Entry 1 | |
| // [16 .. 23] : Entry 2 | |
| // [24 .. 31] : empty | |
| // [32 .. 39] : empty | |
| // | |
| // [ HashTable ] : | |
| // [40] : First chain-link for bucket 1 | |
| // [41] : empty | |
| // | |
| // [ Chains ] : | |
| // [42] : Next chain link for bucket 1 | |
| // [43] : empty | |
| // [44] : empty | |
| // [45] : empty | |
| // | |
| template <class Derived> | |
| class SmallOrderedHashTable : public HeapObject { | |
| public: | |
| // Offset points to a relative location in the table | |
| typedef int Offset; | |
| // ByteIndex points to a index in the table that needs to be | |
| // converted to an Offset. | |
| typedef int ByteIndex; | |
| void Initialize(Isolate* isolate, int capacity); | |
| static Handle<Derived> Allocate(Isolate* isolate, int capacity, | |
| PretenureFlag pretenure = NOT_TENURED); | |
| // Returns a true if the OrderedHashTable contains the key | |
| bool HasKey(Isolate* isolate, Handle<Object> key); | |
| // Returns a true value if the table contains the key and | |
| // the key has been deleted. This does not shrink the table. | |
| static bool Delete(Isolate* isolate, Derived* table, Object* key); | |
| // Returns an SmallOrderedHashTable (possibly |table|) with enough | |
| // space to add at least one new element. Returns empty handle if | |
| // we've already reached MaxCapacity. | |
| static MaybeHandle<Derived> Grow(Isolate* isolate, Handle<Derived> table); | |
| static Handle<Derived> Rehash(Isolate* isolate, Handle<Derived> table, | |
| int new_capacity); | |
| // Iterates only fields in the DataTable. | |
| class BodyDescriptor; | |
| // No weak fields. | |
| typedef BodyDescriptor BodyDescriptorWeak; | |
| // Returns total size in bytes required for a table of given | |
| // capacity. | |
| static int SizeFor(int capacity) { | |
| DCHECK_GE(capacity, kMinCapacity); | |
| DCHECK_LE(capacity, kMaxCapacity); | |
| int data_table_size = DataTableSizeFor(capacity); | |
| int hash_table_size = capacity / kLoadFactor; | |
| int chain_table_size = capacity; | |
| int total_size = kDataTableStartOffset + data_table_size + hash_table_size + | |
| chain_table_size; | |
| return RoundUp(total_size, kPointerSize); | |
| } | |
| // Returns the number elements that can fit into the allocated table. | |
| int Capacity() const { | |
| int capacity = NumberOfBuckets() * kLoadFactor; | |
| DCHECK_GE(capacity, kMinCapacity); | |
| DCHECK_LE(capacity, kMaxCapacity); | |
| return capacity; | |
| } | |
| // Returns the number elements that are present in the table. | |
| int NumberOfElements() const { | |
| int nof_elements = getByte(kNumberOfElementsOffset, 0); | |
| DCHECK_LE(nof_elements, Capacity()); | |
| return nof_elements; | |
| } | |
| int NumberOfDeletedElements() const { | |
| int nof_deleted_elements = getByte(kNumberOfDeletedElementsOffset, 0); | |
| DCHECK_LE(nof_deleted_elements, Capacity()); | |
| return nof_deleted_elements; | |
| } | |
| int NumberOfBuckets() const { return getByte(kNumberOfBucketsOffset, 0); } | |
| DECL_VERIFIER(SmallOrderedHashTable) | |
| static const int kMinCapacity = 4; | |
| static const byte kNotFound = 0xFF; | |
| // We use the value 255 to indicate kNotFound for chain and bucket | |
| // values, which means that this value can't be used a valid | |
| // index. | |
| static const int kMaxCapacity = 254; | |
| STATIC_ASSERT(kMaxCapacity < kNotFound); | |
| // The load factor is used to derive the number of buckets from | |
| // capacity during Allocation. We also depend on this to calaculate | |
| // the capacity from number of buckets after allocation. If we | |
| // decide to change kLoadFactor to something other than 2, capacity | |
| // should be stored as another field of this object. | |
| static const int kLoadFactor = 2; | |
| // Our growth strategy involves doubling the capacity until we reach | |
| // kMaxCapacity, but since the kMaxCapacity is always less than 256, | |
| // we will never fully utilize this table. We special case for 256, | |
| // by changing the new capacity to be kMaxCapacity in | |
| // SmallOrderedHashTable::Grow. | |
| static const int kGrowthHack = 256; | |
| protected: | |
| void SetDataEntry(int entry, int relative_index, Object* value); | |
| // TODO(gsathya): Calculate all the various possible values for this | |
| // at compile time since capacity can only be 4 different values. | |
| Offset GetBucketsStartOffset() const { | |
| int capacity = Capacity(); | |
| int data_table_size = DataTableSizeFor(capacity); | |
| return kDataTableStartOffset + data_table_size; | |
| } | |
| Address GetHashTableStartAddress(int capacity) const { | |
| return FIELD_ADDR(this, kDataTableStartOffset + DataTableSizeFor(capacity)); | |
| } | |
| void SetFirstEntry(int bucket, byte value) { | |
| DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets()); | |
| setByte(GetBucketsStartOffset(), bucket, value); | |
| } | |
| int GetFirstEntry(int bucket) const { | |
| DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets()); | |
| return getByte(GetBucketsStartOffset(), bucket); | |
| } | |
| // TODO(gsathya): Calculate all the various possible values for this | |
| // at compile time since capacity can only be 4 different values. | |
| Offset GetChainTableOffset() const { | |
| int nof_buckets = NumberOfBuckets(); | |
| int capacity = nof_buckets * kLoadFactor; | |
| DCHECK_EQ(Capacity(), capacity); | |
| int data_table_size = DataTableSizeFor(capacity); | |
| int hash_table_size = nof_buckets; | |
| return kDataTableStartOffset + data_table_size + hash_table_size; | |
| } | |
| void SetNextEntry(int entry, int next_entry) { | |
| DCHECK_LT(static_cast<unsigned>(entry), Capacity()); | |
| DCHECK_GE(static_cast<unsigned>(next_entry), 0); | |
| DCHECK(next_entry <= Capacity() || next_entry == kNotFound); | |
| setByte(GetChainTableOffset(), entry, next_entry); | |
| } | |
| int GetNextEntry(int entry) const { | |
| DCHECK_LT(entry, Capacity()); | |
| return getByte(GetChainTableOffset(), entry); | |
| } | |
| Object* GetDataEntry(int entry, int relative_index) { | |
| DCHECK_LT(entry, Capacity()); | |
| DCHECK_LE(static_cast<unsigned>(relative_index), Derived::kEntrySize); | |
| Offset entry_offset = GetDataEntryOffset(entry, relative_index); | |
| return READ_FIELD(this, entry_offset); | |
| } | |
| Object* KeyAt(int entry) const { | |
| DCHECK_LT(entry, Capacity()); | |
| Offset entry_offset = GetDataEntryOffset(entry, Derived::kKeyIndex); | |
| return READ_FIELD(this, entry_offset); | |
| } | |
| int HashToBucket(int hash) const { return hash & (NumberOfBuckets() - 1); } | |
| int HashToFirstEntry(int hash) const { | |
| int bucket = HashToBucket(hash); | |
| int entry = GetFirstEntry(bucket); | |
| DCHECK(entry < Capacity() || entry == kNotFound); | |
| return entry; | |
| } | |
| void SetNumberOfBuckets(int num) { setByte(kNumberOfBucketsOffset, 0, num); } | |
| void SetNumberOfElements(int num) { | |
| DCHECK_LE(static_cast<unsigned>(num), Capacity()); | |
| setByte(kNumberOfElementsOffset, 0, num); | |
| } | |
| void SetNumberOfDeletedElements(int num) { | |
| DCHECK_LE(static_cast<unsigned>(num), Capacity()); | |
| setByte(kNumberOfDeletedElementsOffset, 0, num); | |
| } | |
| int FindEntry(Isolate* isolate, Object* key) { | |
| DisallowHeapAllocation no_gc; | |
| Object* hash = key->GetHash(); | |
| if (hash->IsUndefined(isolate)) return kNotFound; | |
| int entry = HashToFirstEntry(Smi::ToInt(hash)); | |
| // Walk the chain in the bucket to find the key. | |
| while (entry != kNotFound) { | |
| Object* candidate_key = KeyAt(entry); | |
| if (candidate_key->SameValueZero(key)) return entry; | |
| entry = GetNextEntry(entry); | |
| } | |
| return kNotFound; | |
| } | |
| static const Offset kNumberOfElementsOffset = kHeaderSize; | |
| static const Offset kNumberOfDeletedElementsOffset = | |
| kNumberOfElementsOffset + kOneByteSize; | |
| static const Offset kNumberOfBucketsOffset = | |
| kNumberOfDeletedElementsOffset + kOneByteSize; | |
| static const constexpr Offset kDataTableStartOffset = | |
| RoundUp<kPointerSize>(kNumberOfBucketsOffset); | |
| static constexpr int DataTableSizeFor(int capacity) { | |
| return capacity * Derived::kEntrySize * kPointerSize; | |
| } | |
| // This is used for accessing the non |DataTable| part of the | |
| // structure. | |
| byte getByte(Offset offset, ByteIndex index) const { | |
| DCHECK(offset < kDataTableStartOffset || offset >= GetBucketsStartOffset()); | |
| return READ_BYTE_FIELD(this, offset + (index * kOneByteSize)); | |
| } | |
| void setByte(Offset offset, ByteIndex index, byte value) { | |
| DCHECK(offset < kDataTableStartOffset || offset >= GetBucketsStartOffset()); | |
| WRITE_BYTE_FIELD(this, offset + (index * kOneByteSize), value); | |
| } | |
| Offset GetDataEntryOffset(int entry, int relative_index) const { | |
| DCHECK_LT(entry, Capacity()); | |
| int offset_in_datatable = entry * Derived::kEntrySize * kPointerSize; | |
| int offset_in_entry = relative_index * kPointerSize; | |
| return kDataTableStartOffset + offset_in_datatable + offset_in_entry; | |
| } | |
| int UsedCapacity() const { | |
| int used = NumberOfElements() + NumberOfDeletedElements(); | |
| DCHECK_LE(used, Capacity()); | |
| return used; | |
| } | |
| private: | |
| friend class OrderedHashMapHandler; | |
| friend class OrderedHashSetHandler; | |
| friend class CodeStubAssembler; | |
| }; | |
| class SmallOrderedHashSet : public SmallOrderedHashTable<SmallOrderedHashSet> { | |
| public: | |
| DECL_CAST(SmallOrderedHashSet) | |
| DECL_PRINTER(SmallOrderedHashSet) | |
| static const int kKeyIndex = 0; | |
| static const int kEntrySize = 1; | |
| // Adds |value| to |table|, if the capacity isn't enough, a new | |
| // table is created. The original |table| is returned if there is | |
| // capacity to store |value| otherwise the new table is returned. | |
| static MaybeHandle<SmallOrderedHashSet> Add(Isolate* isolate, | |
| Handle<SmallOrderedHashSet> table, | |
| Handle<Object> key); | |
| static inline bool Is(Handle<HeapObject> table); | |
| static inline int GetMapRootIndex(); | |
| }; | |
| class SmallOrderedHashMap : public SmallOrderedHashTable<SmallOrderedHashMap> { | |
| public: | |
| DECL_CAST(SmallOrderedHashMap) | |
| DECL_PRINTER(SmallOrderedHashMap) | |
| static const int kKeyIndex = 0; | |
| static const int kValueIndex = 1; | |
| static const int kEntrySize = 2; | |
| // Adds |value| to |table|, if the capacity isn't enough, a new | |
| // table is created. The original |table| is returned if there is | |
| // capacity to store |value| otherwise the new table is returned. | |
| static MaybeHandle<SmallOrderedHashMap> Add(Isolate* isolate, | |
| Handle<SmallOrderedHashMap> table, | |
| Handle<Object> key, | |
| Handle<Object> value); | |
| static inline bool Is(Handle<HeapObject> table); | |
| static inline int GetMapRootIndex(); | |
| }; | |
| // TODO(gsathya): Rename this to OrderedHashTable, after we rename | |
| // OrderedHashTable to LargeOrderedHashTable. Also set up a | |
| // OrderedHashSetBase class as a base class for the two tables and use | |
| // that instead of a HeapObject here. | |
| template <class SmallTable, class LargeTable> | |
| class OrderedHashTableHandler { | |
| public: | |
| typedef int Entry; | |
| static Handle<HeapObject> Allocate(Isolate* isolate, int capacity); | |
| static bool Delete(Handle<HeapObject> table, Handle<Object> key); | |
| static bool HasKey(Isolate* isolate, Handle<HeapObject> table, | |
| Handle<Object> key); | |
| // TODO(gsathya): Move this to OrderedHashTable | |
| static const int OrderedHashTableMinSize = | |
| SmallOrderedHashTable<SmallTable>::kGrowthHack << 1; | |
| }; | |
| class OrderedHashMapHandler | |
| : public OrderedHashTableHandler<SmallOrderedHashMap, OrderedHashMap> { | |
| public: | |
| static Handle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table, | |
| Handle<Object> key, Handle<Object> value); | |
| static Handle<OrderedHashMap> AdjustRepresentation( | |
| Isolate* isolate, Handle<SmallOrderedHashMap> table); | |
| }; | |
| class OrderedHashSetHandler | |
| : public OrderedHashTableHandler<SmallOrderedHashSet, OrderedHashSet> { | |
| public: | |
| static Handle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table, | |
| Handle<Object> key); | |
| static Handle<OrderedHashSet> AdjustRepresentation( | |
| Isolate* isolate, Handle<SmallOrderedHashSet> table); | |
| }; | |
| class JSCollectionIterator : public JSObject { | |
| public: | |
| // [table]: the backing hash table mapping keys to values. | |
| DECL_ACCESSORS(table, Object) | |
| // [index]: The index into the data table. | |
| DECL_ACCESSORS(index, Object) | |
| // Dispatched behavior. | |
| DECL_PRINTER(JSCollectionIterator) | |
| static const int kTableOffset = JSObject::kHeaderSize; | |
| static const int kIndexOffset = kTableOffset + kPointerSize; | |
| static const int kSize = kIndexOffset + kPointerSize; | |
| private: | |
| DISALLOW_IMPLICIT_CONSTRUCTORS(JSCollectionIterator); | |
| }; | |
| // OrderedHashTableIterator is an iterator that iterates over the keys and | |
| // values of an OrderedHashTable. | |
| // | |
| // The iterator has a reference to the underlying OrderedHashTable data, | |
| // [table], as well as the current [index] the iterator is at. | |
| // | |
| // When the OrderedHashTable is rehashed it adds a reference from the old table | |
| // to the new table as well as storing enough data about the changes so that the | |
| // iterator [index] can be adjusted accordingly. | |
| // | |
| // When the [Next] result from the iterator is requested, the iterator checks if | |
| // there is a newer table that it needs to transition to. | |
| template <class Derived, class TableType> | |
| class OrderedHashTableIterator : public JSCollectionIterator { | |
| public: | |
| // Whether the iterator has more elements. This needs to be called before | |
| // calling |CurrentKey| and/or |CurrentValue|. | |
| bool HasMore(); | |
| // Move the index forward one. | |
| void MoveNext() { set_index(Smi::FromInt(Smi::ToInt(index()) + 1)); } | |
| // Returns the current key of the iterator. This should only be called when | |
| // |HasMore| returns true. | |
| inline Object* CurrentKey(); | |
| private: | |
| // Transitions the iterator to the non obsolete backing store. This is a NOP | |
| // if the [table] is not obsolete. | |
| void Transition(); | |
| DISALLOW_IMPLICIT_CONSTRUCTORS(OrderedHashTableIterator); | |
| }; | |
| } // namespace internal | |
| } // namespace v8 | |
| #include "src/objects/object-macros-undef.h" | |
| #endif // V8_OBJECTS_ORDERED_HASH_TABLE_H_ |