// Copyright 2017 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_HASH_TABLE_H_ #define V8_OBJECTS_HASH_TABLE_H_ #include "src/base/compiler-specific.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 { // HashTable is a subclass of FixedArray that implements a hash table // that uses open addressing and quadratic probing. // // In order for the quadratic probing to work, elements that have not // yet been used and elements that have been deleted are // distinguished. Probing continues when deleted elements are // encountered and stops when unused elements are encountered. // // - Elements with key == undefined have not been used yet. // - Elements with key == the_hole have been deleted. // // The hash table class is parameterized with a Shape. // Shape must be a class with the following interface: // class ExampleShape { // public: // // Tells whether key matches other. // static bool IsMatch(Key key, Object* other); // // Returns the hash value for key. // static uint32_t Hash(Isolate* isolate, Key key); // // Returns the hash value for object. // static uint32_t HashForObject(Isolate* isolate, Object* object); // // Convert key to an object. // static inline Handle AsHandle(Isolate* isolate, Key key); // // The prefix size indicates number of elements in the beginning // // of the backing storage. // static const int kPrefixSize = ..; // // The Element size indicates number of elements per entry. // static const int kEntrySize = ..; // // Indicates whether IsMatch can deal with other being the_hole (a // // deleted entry). // static const bool kNeedsHoleCheck = ..; // }; // The prefix size indicates an amount of memory in the // beginning of the backing storage that can be used for non-element // information by subclasses. template class BaseShape { public: typedef KeyT Key; static inline int GetMapRootIndex(); static const bool kNeedsHoleCheck = true; static Object* Unwrap(Object* key) { return key; } static bool IsKey(Isolate* isolate, Object* key) { return IsLive(isolate, key); } static inline bool IsLive(Isolate* isolate, Object* key); }; class V8_EXPORT_PRIVATE HashTableBase : public NON_EXPORTED_BASE(FixedArray) { public: // Returns the number of elements in the hash table. inline int NumberOfElements() const; // Returns the number of deleted elements in the hash table. inline int NumberOfDeletedElements() const; // Returns the capacity of the hash table. inline int Capacity() const; // ElementAdded should be called whenever an element is added to a // hash table. inline void ElementAdded(); // ElementRemoved should be called whenever an element is removed from // a hash table. inline void ElementRemoved(); inline void ElementsRemoved(int n); // Computes the required capacity for a table holding the given // number of elements. May be more than HashTable::kMaxCapacity. static inline int ComputeCapacity(int at_least_space_for); // Compute the probe offset (quadratic probing). INLINE(static uint32_t GetProbeOffset(uint32_t n)) { return (n + n * n) >> 1; } static const int kNumberOfElementsIndex = 0; static const int kNumberOfDeletedElementsIndex = 1; static const int kCapacityIndex = 2; static const int kPrefixStartIndex = 3; // Constant used for denoting a absent entry. static const int kNotFound = -1; // Minimum capacity for newly created hash tables. static const int kMinCapacity = 4; protected: // Update the number of elements in the hash table. inline void SetNumberOfElements(int nof); // Update the number of deleted elements in the hash table. inline void SetNumberOfDeletedElements(int nod); // Returns probe entry. static uint32_t GetProbe(uint32_t hash, uint32_t number, uint32_t size) { DCHECK(base::bits::IsPowerOfTwo(size)); return (hash + GetProbeOffset(number)) & (size - 1); } inline static uint32_t FirstProbe(uint32_t hash, uint32_t size) { return hash & (size - 1); } inline static uint32_t NextProbe(uint32_t last, uint32_t number, uint32_t size) { return (last + number) & (size - 1); } }; template class HashTable : public HashTableBase { public: typedef Shape ShapeT; typedef typename Shape::Key Key; // Returns a new HashTable object. V8_WARN_UNUSED_RESULT static Handle New( Isolate* isolate, int at_least_space_for, PretenureFlag pretenure = NOT_TENURED, MinimumCapacity capacity_option = USE_DEFAULT_MINIMUM_CAPACITY); DECL_CAST(HashTable) // Garbage collection support. void IteratePrefix(ObjectVisitor* visitor); void IterateElements(ObjectVisitor* visitor); // Find entry for key otherwise return kNotFound. inline int FindEntry(Key key); inline int FindEntry(Isolate* isolate, Key key, int32_t hash); int FindEntry(Isolate* isolate, Key key); // Rehashes the table in-place. void Rehash(); // Tells whether k is a real key. The hole and undefined are not allowed // as keys and can be used to indicate missing or deleted elements. static bool IsKey(Isolate* isolate, Object* k) { return Shape::IsKey(isolate, k); } inline bool ToKey(Isolate* isolate, int entry, Object** out_k) { Object* k = KeyAt(entry); if (!IsKey(isolate, k)) return false; *out_k = Shape::Unwrap(k); return true; } // Returns the key at entry. Object* KeyAt(int entry) { return get(EntryToIndex(entry) + kEntryKeyIndex); } static const int kElementsStartIndex = kPrefixStartIndex + Shape::kPrefixSize; static const int kEntrySize = Shape::kEntrySize; STATIC_ASSERT(kEntrySize > 0); static const int kEntryKeyIndex = 0; static const int kElementsStartOffset = kHeaderSize + kElementsStartIndex * kPointerSize; // Maximal capacity of HashTable. Based on maximal length of underlying // FixedArray. Staying below kMaxCapacity also ensures that EntryToIndex // cannot overflow. static const int kMaxCapacity = (FixedArray::kMaxLength - kElementsStartIndex) / kEntrySize; // Don't shrink a HashTable below this capacity. static const int kMinShrinkCapacity = 16; // Maximum length to create a regular HashTable (aka. non large object). static const int kMaxRegularCapacity = 16384; // Returns the index for an entry (of the key) static constexpr inline int EntryToIndex(int entry) { return (entry * kEntrySize) + kElementsStartIndex; } // Ensure enough space for n additional elements. V8_WARN_UNUSED_RESULT static Handle EnsureCapacity( Handle table, int n, PretenureFlag pretenure = NOT_TENURED); // Returns true if this table has sufficient capacity for adding n elements. bool HasSufficientCapacityToAdd(int number_of_additional_elements); protected: friend class ObjectHashTable; V8_WARN_UNUSED_RESULT static Handle NewInternal( Isolate* isolate, int capacity, PretenureFlag pretenure); // Find the entry at which to insert element with the given key that // has the given hash value. uint32_t FindInsertionEntry(uint32_t hash); // Attempt to shrink hash table after removal of key. V8_WARN_UNUSED_RESULT static Handle Shrink( Handle table, int additionalCapacity = 0); private: // Ensure that kMaxRegularCapacity yields a non-large object dictionary. STATIC_ASSERT(EntryToIndex(kMaxRegularCapacity) < kMaxRegularLength); STATIC_ASSERT(v8::base::bits::IsPowerOfTwo(kMaxRegularCapacity)); static const int kMaxRegularEntry = kMaxRegularCapacity / kEntrySize; static const int kMaxRegularIndex = EntryToIndex(kMaxRegularEntry); STATIC_ASSERT(OffsetOfElementAt(kMaxRegularIndex) < kMaxRegularHeapObjectSize); // Sets the capacity of the hash table. void SetCapacity(int capacity) { // To scale a computed hash code to fit within the hash table, we // use bit-wise AND with a mask, so the capacity must be positive // and non-zero. DCHECK_GT(capacity, 0); DCHECK_LE(capacity, kMaxCapacity); set(kCapacityIndex, Smi::FromInt(capacity)); } // Returns _expected_ if one of entries given by the first _probe_ probes is // equal to _expected_. Otherwise, returns the entry given by the probe // number _probe_. uint32_t EntryForProbe(Object* k, int probe, uint32_t expected); void Swap(uint32_t entry1, uint32_t entry2, WriteBarrierMode mode); // Rehashes this hash-table into the new table. void Rehash(Derived* new_table); }; // HashTableKey is an abstract superclass for virtual key behavior. class HashTableKey { public: explicit HashTableKey(uint32_t hash) : hash_(hash) {} // Returns whether the other object matches this key. virtual bool IsMatch(Object* other) = 0; // Returns the hash value for this key. // Required. virtual ~HashTableKey() {} uint32_t Hash() const { return hash_; } protected: void set_hash(uint32_t hash) { DCHECK_EQ(0, hash_); hash_ = hash; } private: uint32_t hash_ = 0; }; class ObjectHashTableShape : public BaseShape> { public: static inline bool IsMatch(Handle key, Object* other); static inline uint32_t Hash(Isolate* isolate, Handle key); static inline uint32_t HashForObject(Isolate* isolate, Object* object); static inline Handle AsHandle(Isolate* isolate, Handle key); static const int kPrefixSize = 0; static const int kEntryValueIndex = 1; static const int kEntrySize = 2; static const bool kNeedsHoleCheck = false; }; // ObjectHashTable maps keys that are arbitrary objects to object values by // using the identity hash of the key for hashing purposes. class ObjectHashTable : public HashTable { typedef HashTable DerivedHashTable; public: DECL_CAST(ObjectHashTable) // Attempt to shrink hash table after removal of key. V8_WARN_UNUSED_RESULT static inline Handle Shrink( Handle table); // Looks up the value associated with the given key. The hole value is // returned in case the key is not present. Object* Lookup(Handle key); Object* Lookup(Handle key, int32_t hash); Object* Lookup(Isolate* isolate, Handle key, int32_t hash); // Returns the value at entry. Object* ValueAt(int entry); // Adds (or overwrites) the value associated with the given key. static Handle Put(Handle table, Handle key, Handle value); static Handle Put(Handle table, Handle key, Handle value, int32_t hash); // Returns an ObjectHashTable (possibly |table|) where |key| has been removed. static Handle Remove(Handle table, Handle key, bool* was_present); static Handle Remove(Handle table, Handle key, bool* was_present, int32_t hash); // Returns the index to the value of an entry. static inline int EntryToValueIndex(int entry) { return EntryToIndex(entry) + ObjectHashTableShape::kEntryValueIndex; } protected: friend class MarkCompactCollector; void AddEntry(int entry, Object* key, Object* value); void RemoveEntry(int entry); }; class ObjectHashSetShape : public ObjectHashTableShape { public: static const int kPrefixSize = 0; static const int kEntrySize = 1; }; class ObjectHashSet : public HashTable { public: static Handle Add(Handle set, Handle key); inline bool Has(Isolate* isolate, Handle key, int32_t hash); inline bool Has(Isolate* isolate, Handle key); DECL_CAST(ObjectHashSet) }; // 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 OrderedHashTable : public OrderedHashTableBase { public: // Returns an OrderedHashTable with a capacity of at least |capacity|. static Handle 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 EnsureGrowable(Handle table); // Returns an OrderedHashTable (possibly |table|) that's shrunken // if possible. static Handle Shrink(Handle table); // Returns a new empty OrderedHashTable and records the clearing so that // existing iterators can be updated. static Handle Clear(Handle 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 Rehash(Handle 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 { public: DECL_CAST(OrderedHashSet) static Handle Add(Handle table, Handle value); static Handle ConvertToKeysArray(Handle table, GetKeysConversion convert); static HeapObject* GetEmpty(Isolate* isolate); static inline int GetMapRootIndex(); }; class OrderedHashMap : public OrderedHashTable { public: DECL_CAST(OrderedHashMap) // Returns a value if the OrderedHashMap contains the key, otherwise // returns undefined. static Handle Add(Handle table, Handle key, Handle value); Object* ValueAt(int entry); static Object* GetHash(Isolate* isolate, Object* key); static HeapObject* GetEmpty(Isolate* isolate); static inline int GetMapRootIndex(); 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 ] [ HashTable ] [ Chains ] [ Padding ] [ DataTable ] // // On a 64 bit machine with capacity = 4 and 2 entries, // // [ Header ] : // [0 .. 7] : Number of elements // [8 .. 15] : Number of deleted elements // [16 .. 23] : Number of buckets // // [ HashTable ] : // [24 .. 31] : First chain-link for bucket 1 // [32 .. 40] : First chain-link for bucket 2 // // [ Chains ] : // [40 .. 47] : Next chain link for entry 1 // [48 .. 55] : Next chain link for entry 2 // [56 .. 63] : Next chain link for entry 3 // [64 .. 71] : Next chain link for entry 4 // // [ Padding ] : // [72 .. 127] : Padding // // [ DataTable ] : // [128 .. 128 + kEntrySize - 1] : Entry 1 // [128 + kEntrySize .. 128 + kEntrySize + kEntrySize - 1] : Entry 2 // template class SmallOrderedHashTable : public HeapObject { public: void Initialize(Isolate* isolate, int capacity); static Handle Allocate(Isolate* isolate, int capacity, PretenureFlag pretenure = NOT_TENURED); // Returns a true if the OrderedHashTable contains the key bool HasKey(Isolate* isolate, Handle key); // Iterates only fields in the DataTable. class BodyDescriptor; // No weak fields. typedef BodyDescriptor BodyDescriptorWeak; // Returns an SmallOrderedHashTable (possibly |table|) with enough // space to add at least one new element. static Handle Grow(Handle table); static Handle Rehash(Handle table, int new_capacity); void SetDataEntry(int entry, int relative_index, Object* value); static int GetDataTableStartOffset(int capacity) { int nof_buckets = capacity / kLoadFactor; int nof_chain_entries = capacity; int padding_index = kBucketsStartOffset + nof_buckets + nof_chain_entries; int padding_offset = padding_index * kBitsPerByte; return ((padding_offset + kPointerSize - 1) / kPointerSize) * kPointerSize; } int GetDataTableStartOffset() const { return GetDataTableStartOffset(Capacity()); } static int Size(int capacity) { int data_table_start = GetDataTableStartOffset(capacity); int data_table_size = capacity * Derived::kEntrySize * kBitsPerPointer; return data_table_start + data_table_size; } int Size() const { return Size(Capacity()); } void SetFirstEntry(int bucket, byte value) { set(kBucketsStartOffset + bucket, value); } int GetFirstEntry(int bucket) const { return get(kBucketsStartOffset + bucket); } void SetNextEntry(int entry, int next_entry) { set(GetChainTableOffset() + entry, next_entry); } int GetNextEntry(int entry) const { return get(GetChainTableOffset() + entry); } Object* GetDataEntry(int entry, int relative_index) { int entry_offset = GetDataEntryOffset(entry, relative_index); return READ_FIELD(this, entry_offset); } Object* KeyAt(int entry) const { int 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); return entry; } int GetChainTableOffset() const { return kBucketsStartOffset + NumberOfBuckets(); } void SetNumberOfBuckets(int num) { set(kNumberOfBucketsOffset, num); } void SetNumberOfElements(int num) { set(kNumberOfElementsOffset, num); } void SetNumberOfDeletedElements(int num) { set(kNumberOfDeletedElementsOffset, num); } int NumberOfElements() const { return get(kNumberOfElementsOffset); } int NumberOfDeletedElements() const { return get(kNumberOfDeletedElementsOffset); } int NumberOfBuckets() const { return get(kNumberOfBucketsOffset); } static const byte kNotFound = 0xFF; static const int kMinCapacity = 4; // 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); static const int kNumberOfElementsOffset = 0; static const int kNumberOfDeletedElementsOffset = 1; static const int kNumberOfBucketsOffset = 2; static const int kBucketsStartOffset = 3; // 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; static const int kBitsPerPointer = kPointerSize * kBitsPerByte; // 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; DECL_VERIFIER(SmallOrderedHashTable) protected: // This is used for accessing the non |DataTable| part of the // structure. byte get(int index) const { return READ_BYTE_FIELD(this, kHeaderSize + (index * kOneByteSize)); } void set(int index, byte value) { WRITE_BYTE_FIELD(this, kHeaderSize + (index * kOneByteSize), value); } int GetDataEntryOffset(int entry, int relative_index) const { int datatable_start = GetDataTableStartOffset(); int offset_in_datatable = entry * Derived::kEntrySize * kPointerSize; int offset_in_entry = relative_index * kPointerSize; return datatable_start + offset_in_datatable + offset_in_entry; } // Returns the number elements that can fit into the allocated buffer. int Capacity() const { return NumberOfBuckets() * kLoadFactor; } int UsedCapacity() const { return NumberOfElements() + NumberOfDeletedElements(); } }; class SmallOrderedHashSet : public SmallOrderedHashTable { 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 Handle Add(Handle table, Handle key); }; class SmallOrderedHashMap : public SmallOrderedHashTable { 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 Handle Add(Handle table, Handle key, Handle value); }; 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 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_HASH_TABLE_H_