// Copyright 2021 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_SWISS_NAME_DICTIONARY_H_ #define V8_OBJECTS_SWISS_NAME_DICTIONARY_H_ #include #include "src/base/export-template.h" #include "src/base/optional.h" #include "src/common/globals.h" #include "src/objects/fixed-array.h" #include "src/objects/internal-index.h" #include "src/objects/js-objects.h" #include "src/objects/swiss-hash-table-helpers.h" #include "src/roots/roots.h" // Has to be the last include (doesn't have include guards): #include "src/objects/object-macros.h" namespace v8 { namespace internal { // A property backing store based on Swiss Tables/Abseil's flat_hash_map. The // implementation is heavily based on Abseil's raw_hash_set.h. // // Memory layout (see below for detailed description of parts): // Prefix: [table type dependent part, can have 0 size] // Capacity: 4 bytes, raw int32_t // Meta table pointer: kTaggedSize bytes // Data table: 2 * |capacity| * |kTaggedSize| bytes // Ctrl table: |capacity| + |kGroupWidth| uint8_t entries // PropertyDetails table: |capacity| uint_8 entries // // Note that because of |kInitialCapacity| == 4 there is no need for padding. // // Description of parts directly contained in SwissNameDictionary allocation: // Prefix: // In case of SwissNameDictionary: // identity hash: 4 bytes, raw int32_t // Meta table pointer: kTaggedSize bytes. // See below for explanation of the meta table. // Data table: // For each logical bucket of the hash table, contains the corresponding key // and value. // Ctrl table: // The control table is used to implement a Swiss Table: Each byte is either // Ctrl::kEmpty, Ctrl::kDeleted, or in case of a bucket denoting a present // entry in the hash table, the 7 lowest bits of the key's hash. The first // |capacity| entries are the actual control table. The additional // |kGroupWidth| bytes contain a copy of the first min(capacity, // kGroupWidth) bytes of the table. // PropertyDetails table: // Each byte contains the PropertyDetails for the corresponding bucket of // the ctrl table. Entries may contain unitialized data if the corresponding // bucket hasn't been used before. // // Meta table: // The meta table (not to be confused with the control table used in any // Swiss Table design!) is a separate ByteArray. Here, the "X" in "uintX_t" // depends on the capacity of the swiss table. For capacities <= 256 we have X // = 8, for 256 < |capacity| <= 2^16 we have X = 16, and otherwise X = 32 (see // MetaTableSizePerEntryFor). It contais the following data: // Number of Entries: uintX_t. // Number of Deleted Entries: uintX_t. // Enumeration table: max_load_factor * Capacity() entries of type uintX_t: // The i-th entry in the enumeration table // contains the number of the bucket representing the i-th entry of the // table in enumeration order. Entries may contain unitialized data if the // corresponding bucket hasn't been used before. class V8_EXPORT_PRIVATE SwissNameDictionary : public HeapObject { public: using Group = swiss_table::Group; template inline static Handle Add( IsolateT* isolate, Handle table, Handle key, Handle value, PropertyDetails details, InternalIndex* entry_out = nullptr); static Handle Shrink(Isolate* isolate, Handle table); static Handle DeleteEntry( Isolate* isolate, Handle table, InternalIndex entry); template inline InternalIndex FindEntry(IsolateT* isolate, Object key); // This is to make the interfaces of NameDictionary::FindEntry and // OrderedNameDictionary::FindEntry compatible. // TODO(emrich) clean this up: NameDictionary uses Handle // for FindEntry keys due to its Key typedef, but that's also used // for adding, where we do need handles. template inline InternalIndex FindEntry(IsolateT* isolate, Handle key); static inline bool IsKey(ReadOnlyRoots roots, Object key_candidate); inline bool ToKey(ReadOnlyRoots roots, InternalIndex entry, Object* out_key); inline Object KeyAt(InternalIndex entry); inline Name NameAt(InternalIndex entry); inline Object ValueAt(InternalIndex entry); // Returns {} if we would be reading out of the bounds of the object. inline base::Optional TryValueAt(InternalIndex entry); inline PropertyDetails DetailsAt(InternalIndex entry); inline void ValueAtPut(InternalIndex entry, Object value); inline void DetailsAtPut(InternalIndex entry, PropertyDetails value); inline int NumberOfElements(); inline int NumberOfDeletedElements(); inline int Capacity(); inline int UsedCapacity(); int NumberOfEnumerableProperties(); static Handle ShallowCopy( Isolate* isolate, Handle table); // Strict in the sense that it checks that all used/initialized memory in // |this| and |other| is the same. The only exceptions are the meta table // pointer (which must differ between the two tables) and PropertyDetails of // deleted entries (which reside in initialized memory, but are not compared). bool EqualsForTesting(SwissNameDictionary other); template void Initialize(IsolateT* isolate, ByteArray meta_table, int capacity); template static Handle Rehash(IsolateT* isolate, Handle table, int new_capacity); template void Rehash(IsolateT* isolate); inline void SetHash(int hash); inline int Hash(); Object SlowReverseLookup(Isolate* isolate, Object value); class IndexIterator { public: inline IndexIterator(Handle dict, int start); inline IndexIterator& operator++(); inline bool operator==(const IndexIterator& b) const; inline bool operator!=(const IndexIterator& b) const; inline InternalIndex operator*(); private: int used_capacity_; int enum_index_; // This may be an empty handle, but only if the capacity of the table is // 0 and pointer compression is disabled. Handle dict_; }; class IndexIterable { public: inline explicit IndexIterable(Handle dict); inline IndexIterator begin(); inline IndexIterator end(); private: // This may be an empty handle, but only if the capacity of the table is // 0 and pointer compression is disabled. Handle dict_; }; inline IndexIterable IterateEntriesOrdered(); inline IndexIterable IterateEntries(); // For the given enumeration index, returns the entry (= bucket of the Swiss // Table) containing the data for the mapping with that enumeration index. // The returned bucket may be deleted. inline int EntryForEnumerationIndex(int enumeration_index); inline static constexpr bool IsValidCapacity(int capacity); inline static int CapacityFor(int at_least_space_for); // Given a capacity, how much of it can we fill before resizing? inline static constexpr int MaxUsableCapacity(int capacity); // The maximum allowed capacity for any SwissNameDictionary. inline static constexpr int MaxCapacity(); // Returns total size in bytes required for a table of given capacity. inline static constexpr int SizeFor(int capacity); inline static constexpr int MetaTableSizePerEntryFor(int capacity); inline static constexpr int MetaTableSizeFor(int capacity); inline static constexpr int DataTableSize(int capacity); inline static constexpr int CtrlTableSize(int capacity); // Indicates that IterateEntries() returns entries ordered. static constexpr bool kIsOrderedDictionaryType = true; // Only used in CSA/Torque, where indices are actual integers. In C++, // InternalIndex::NotFound() is always used instead. static constexpr int kNotFoundSentinel = -1; static const int kGroupWidth = Group::kWidth; static const bool kUseSIMD = kGroupWidth == 16; class BodyDescriptor; // Note that 0 is also a valid capacity. Changing this value to a smaller one // may make some padding necessary in the data layout. static constexpr int kInitialCapacity = kSwissNameDictionaryInitialCapacity; // Defines how many kTaggedSize sized values are associcated which each entry // in the data table. static constexpr int kDataTableEntryCount = 2; static constexpr int kDataTableKeyEntryIndex = 0; static constexpr int kDataTableValueEntryIndex = kDataTableKeyEntryIndex + 1; // Field indices describing the layout of the meta table: A field index of i // means that the corresponding meta table entry resides at an offset of {i * // sizeof(uintX_t)} bytes from the beginning of the meta table. Here, the X in // uintX_t can be 8, 16, or 32, and depends on the capacity of the overall // SwissNameDictionary. See the section "Meta table" in the comment at the // beginning of the SwissNameDictionary class in this file. static constexpr int kMetaTableElementCountFieldIndex = 0; static constexpr int kMetaTableDeletedElementCountFieldIndex = 1; // Field index of the first entry of the enumeration table (which is part of // the meta table). static constexpr int kMetaTableEnumerationDataStartIndex = 2; // The maximum capacity of any SwissNameDictionary whose meta table can use 1 // byte per entry. static constexpr int kMax1ByteMetaTableCapacity = (1 << 8); // The maximum capacity of any SwissNameDictionary whose meta table can use 2 // bytes per entry. static constexpr int kMax2ByteMetaTableCapacity = (1 << 16); // TODO(v8:11388) We would like to use Torque-generated constants here, but // those are currently incorrect. // Offset into the overall table, starting at HeapObject standard fields, // in bytes. This means that the map is stored at offset 0. using Offset = int; inline static constexpr Offset PrefixOffset(); inline static constexpr Offset CapacityOffset(); inline static constexpr Offset MetaTablePointerOffset(); inline static constexpr Offset DataTableStartOffset(); inline static constexpr Offset DataTableEndOffset(int capacity); inline static constexpr Offset CtrlTableStartOffset(int capacity); inline static constexpr Offset PropertyDetailsTableStartOffset(int capacity); #if VERIFY_HEAP void SwissNameDictionaryVerify(Isolate* isolate, bool slow_checks); #endif DECL_VERIFIER(SwissNameDictionary) DECL_PRINTER(SwissNameDictionary) DECL_CAST(SwissNameDictionary) OBJECT_CONSTRUCTORS(SwissNameDictionary, HeapObject); private: using ctrl_t = swiss_table::ctrl_t; using Ctrl = swiss_table::Ctrl; template inline static Handle EnsureGrowable( IsolateT* isolate, Handle table); // Returns table of byte-encoded PropertyDetails (without enumeration index // stored in PropertyDetails). inline uint8_t* PropertyDetailsTable(); // Sets key and value to the hole for the given entry. inline void ClearDataTableEntry(Isolate* isolate, int entry); inline void SetKey(int entry, Object key); inline void DetailsAtPut(int entry, PropertyDetails value); inline void ValueAtPut(int entry, Object value); inline PropertyDetails DetailsAt(int entry); inline Object ValueAtRaw(int entry); inline Object KeyAt(int entry); inline bool ToKey(ReadOnlyRoots roots, int entry, Object* out_key); inline int FindFirstEmpty(uint32_t hash); // Adds |key| -> (|value|, |details|) as a new mapping to the table, which // must have sufficient room. Returns the entry (= bucket) used by the new // mapping. Does not update the number of present entries or the // enumeration table. inline int AddInternal(Name key, Object value, PropertyDetails details); // Use |set_ctrl| for modifications whenever possible, since that function // correctly maintains the copy of the first group at the end of the ctrl // table. inline ctrl_t* CtrlTable(); inline static bool IsEmpty(ctrl_t c); inline static bool IsFull(ctrl_t c); inline static bool IsDeleted(ctrl_t c); inline static bool IsEmptyOrDeleted(ctrl_t c); // Sets the a control byte, taking the necessary copying of the first group // into account. inline void SetCtrl(int entry, ctrl_t h); inline ctrl_t GetCtrl(int entry); inline Object LoadFromDataTable(int entry, int data_offset); inline Object LoadFromDataTable(PtrComprCageBase cage_base, int entry, int data_offset); inline void StoreToDataTable(int entry, int data_offset, Object data); inline void StoreToDataTableNoBarrier(int entry, int data_offset, Object data); inline void SetCapacity(int capacity); inline void SetNumberOfElements(int elements); inline void SetNumberOfDeletedElements(int deleted_elements); static inline swiss_table::ProbeSequence probe(uint32_t hash, int capacity); // Sets that the entry with the given |enumeration_index| is stored at the // given bucket of the data table. inline void SetEntryForEnumerationIndex(int enumeration_index, int entry); DECL_ACCESSORS(meta_table, ByteArray) inline void SetMetaTableField(int field_index, int value); inline int GetMetaTableField(int field_index); template inline static void SetMetaTableField(ByteArray meta_table, int field_index, int value); template inline static int GetMetaTableField(ByteArray meta_table, int field_index); }; } // namespace internal } // namespace v8 #endif // V8_OBJECTS_SWISS_NAME_DICTIONARY_H_