blob: daefef1c291198e75103803349e6ff28bace4c91 [file] [log] [blame]
// 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 <cstdint>
#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 <typename IsolateT>
inline static Handle<SwissNameDictionary> Add(
IsolateT* isolate, Handle<SwissNameDictionary> table, Handle<Name> key,
Handle<Object> value, PropertyDetails details,
InternalIndex* entry_out = nullptr);
static Handle<SwissNameDictionary> Shrink(Isolate* isolate,
Handle<SwissNameDictionary> table);
static Handle<SwissNameDictionary> DeleteEntry(
Isolate* isolate, Handle<SwissNameDictionary> table, InternalIndex entry);
template <typename IsolateT>
inline InternalIndex FindEntry(IsolateT* isolate, Tagged<Object> key);
// This is to make the interfaces of NameDictionary::FindEntry and
// OrderedNameDictionary::FindEntry compatible.
// TODO(emrich) clean this up: NameDictionary uses Handle<Object>
// for FindEntry keys due to its Key typedef, but that's also used
// for adding, where we do need handles.
template <typename IsolateT>
inline InternalIndex FindEntry(IsolateT* isolate, Handle<Object> key);
static inline bool IsKey(ReadOnlyRoots roots, Tagged<Object> key_candidate);
inline bool ToKey(ReadOnlyRoots roots, InternalIndex entry,
Tagged<Object>* out_key);
inline Tagged<Object> KeyAt(InternalIndex entry);
inline Tagged<Name> NameAt(InternalIndex entry);
inline Tagged<Object> ValueAt(InternalIndex entry);
// Returns {} if we would be reading out of the bounds of the object.
inline base::Optional<Tagged<Object>> TryValueAt(InternalIndex entry);
inline PropertyDetails DetailsAt(InternalIndex entry);
inline void ValueAtPut(InternalIndex entry, Tagged<Object> value);
inline void DetailsAtPut(InternalIndex entry, PropertyDetails value);
inline int NumberOfElements();
inline int NumberOfDeletedElements();
inline int Capacity();
inline int UsedCapacity();
int NumberOfEnumerableProperties();
// TODO(pthier): Add flags (similar to NamedDictionary) also for swiss dicts.
inline bool may_have_interesting_properties() { UNREACHABLE(); }
inline void set_may_have_interesting_properties(bool value) { UNREACHABLE(); }
static Handle<SwissNameDictionary> ShallowCopy(
Isolate* isolate, Handle<SwissNameDictionary> 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(Tagged<SwissNameDictionary> other);
template <typename IsolateT>
void Initialize(IsolateT* isolate, Tagged<ByteArray> meta_table,
int capacity);
template <typename IsolateT>
static Handle<SwissNameDictionary> Rehash(IsolateT* isolate,
Handle<SwissNameDictionary> table,
int new_capacity);
template <typename IsolateT>
void Rehash(IsolateT* isolate);
inline void SetHash(int hash);
inline int Hash();
Tagged<Object> SlowReverseLookup(Isolate* isolate, Tagged<Object> value);
class IndexIterator {
public:
inline IndexIterator(Handle<SwissNameDictionary> 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<SwissNameDictionary> dict_;
};
class IndexIterable {
public:
inline explicit IndexIterable(Handle<SwissNameDictionary> 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<SwissNameDictionary> 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 <typename IsolateT>
inline static Handle<SwissNameDictionary> EnsureGrowable(
IsolateT* isolate, Handle<SwissNameDictionary> 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, Tagged<Object> key);
inline void DetailsAtPut(int entry, PropertyDetails value);
inline void ValueAtPut(int entry, Tagged<Object> value);
inline PropertyDetails DetailsAt(int entry);
inline Tagged<Object> ValueAtRaw(int entry);
inline Tagged<Object> KeyAt(int entry);
inline bool ToKey(ReadOnlyRoots roots, int entry, Tagged<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(Tagged<Name> key, Tagged<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 Tagged<Object> LoadFromDataTable(int entry, int data_offset);
inline Tagged<Object> LoadFromDataTable(PtrComprCageBase cage_base, int entry,
int data_offset);
inline void StoreToDataTable(int entry, int data_offset, Tagged<Object> data);
inline void StoreToDataTableNoBarrier(int entry, int data_offset,
Tagged<Object> data);
inline void SetCapacity(int capacity);
inline void SetNumberOfElements(int elements);
inline void SetNumberOfDeletedElements(int deleted_elements);
static inline swiss_table::ProbeSequence<Group::kWidth> 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, Tagged<ByteArray>)
inline void SetMetaTableField(int field_index, int value);
inline int GetMetaTableField(int field_index);
template <typename T>
inline static void SetMetaTableField(Tagged<ByteArray> meta_table,
int field_index, int value);
template <typename T>
inline static int GetMetaTableField(Tagged<ByteArray> meta_table,
int field_index);
};
} // namespace internal
} // namespace v8
#include "src/objects/object-macros-undef.h"
#endif // V8_OBJECTS_SWISS_NAME_DICTIONARY_H_