[dict-proto] C++ implementation of SwissNameDictionary, pt. 6
This CL is part of a series that adds the C++ implementation of SwissNameDictionary, a deterministic property backing store based on Swiss Tables. This CL adds the Add, DeleteEntry, Rehash, and Shrink operations, as well as some helpers required by those. Bug: v8:11388 Change-Id: I81a181e99e679095c2acc877a293e81b6a581db4 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2715192 Reviewed-by: Igor Sheludko <ishell@chromium.org> Reviewed-by: Marja Hölttä <marja@chromium.org> Commit-Queue: Frank Emrich <emrich@google.com> Cr-Commit-Position: refs/heads/master@{#73020}
This commit is contained in:
parent
491d01c035
commit
ae153904b8
@ -317,6 +317,21 @@ PropertyDetails SwissNameDictionary::DetailsAt(InternalIndex entry) {
|
||||
return DetailsAt(entry.as_int());
|
||||
}
|
||||
|
||||
// static
|
||||
template <typename LocalIsolate>
|
||||
Handle<SwissNameDictionary> SwissNameDictionary::EnsureGrowable(
|
||||
LocalIsolate* isolate, Handle<SwissNameDictionary> table) {
|
||||
int capacity = table->Capacity();
|
||||
|
||||
if (table->UsedCapacity() < MaxUsableCapacity(capacity)) {
|
||||
// We have room for at least one more entry, nothing to do.
|
||||
return table;
|
||||
}
|
||||
|
||||
int new_capacity = capacity == 0 ? kInitialCapacity : capacity * 2;
|
||||
return Rehash(isolate, table, new_capacity);
|
||||
}
|
||||
|
||||
swiss_table::ctrl_t SwissNameDictionary::GetCtrl(int entry) {
|
||||
DCHECK_LT(static_cast<unsigned>(entry), static_cast<unsigned>(Capacity()));
|
||||
|
||||
@ -352,6 +367,25 @@ void SwissNameDictionary::SetCtrl(int entry, ctrl_t h) {
|
||||
ctrl[copy_entry] = h;
|
||||
}
|
||||
|
||||
// static
|
||||
inline int SwissNameDictionary::FindFirstEmpty(uint32_t hash) {
|
||||
// See SwissNameDictionary::FindEntry for description of probing algorithm.
|
||||
|
||||
auto seq = probe(hash, Capacity());
|
||||
while (true) {
|
||||
Group g{CtrlTable() + seq.offset()};
|
||||
auto mask = g.MatchEmpty();
|
||||
if (mask) {
|
||||
// Note that picking the lowest bit set here means using the leftmost
|
||||
// empty bucket in the group. Here, "left" means smaller entry/bucket
|
||||
// index.
|
||||
return seq.offset(mask.LowestBitSet());
|
||||
}
|
||||
seq.next();
|
||||
DCHECK_LT(seq.index(), Capacity());
|
||||
}
|
||||
}
|
||||
|
||||
void SwissNameDictionary::SetMetaTableField(int field_index, int value) {
|
||||
// See the STATIC_ASSERTs on |kMax1ByteMetaTableCapacity| and
|
||||
// |kMax2ByteMetaTableCapacity| in the .cc file for an explanation of these
|
||||
@ -453,6 +487,57 @@ bool SwissNameDictionary::ToKey(ReadOnlyRoots roots, InternalIndex entry,
|
||||
return ToKey(roots, entry.as_int(), out_key);
|
||||
}
|
||||
|
||||
// static
|
||||
template <typename LocalIsolate>
|
||||
Handle<SwissNameDictionary> SwissNameDictionary::Add(
|
||||
LocalIsolate* isolate, Handle<SwissNameDictionary> original_table,
|
||||
Handle<Name> key, Handle<Object> value, PropertyDetails details,
|
||||
InternalIndex* entry_out) {
|
||||
DCHECK(original_table->FindEntry(isolate, *key).is_not_found());
|
||||
|
||||
Handle<SwissNameDictionary> table = EnsureGrowable(isolate, original_table);
|
||||
|
||||
int nof = table->NumberOfElements();
|
||||
int nod = table->NumberOfDeletedElements();
|
||||
int new_enum_index = nof + nod;
|
||||
|
||||
int new_entry = table->AddInternal(*key, *value, details);
|
||||
|
||||
table->SetNumberOfElements(nof + 1);
|
||||
table->SetEntryForEnumerationIndex(new_enum_index, new_entry);
|
||||
|
||||
if (entry_out) {
|
||||
*entry_out = InternalIndex(new_entry);
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
int SwissNameDictionary::AddInternal(Name key, Object value,
|
||||
PropertyDetails details) {
|
||||
DisallowHeapAllocation no_gc;
|
||||
|
||||
DCHECK(key.IsUniqueName());
|
||||
DCHECK_LE(UsedCapacity(), MaxUsableCapacity(Capacity()));
|
||||
|
||||
uint32_t hash = key.hash();
|
||||
|
||||
// For now we don't re-use deleted buckets (due to enumeration table
|
||||
// complications), which is why we only look for empty buckets here, not
|
||||
// deleted ones.
|
||||
int target = FindFirstEmpty(hash);
|
||||
|
||||
SetCtrl(target, swiss_table::H2(hash));
|
||||
SetKey(target, key);
|
||||
ValueAtPut(target, value);
|
||||
DetailsAtPut(target, details);
|
||||
|
||||
// Note that we do not update the number of elements or the enumeration table
|
||||
// in this function.
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
template <typename LocalIsolate>
|
||||
void SwissNameDictionary::Initialize(LocalIsolate* isolate,
|
||||
ByteArray meta_table, int capacity) {
|
||||
|
@ -10,6 +10,90 @@
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// static
|
||||
Handle<SwissNameDictionary> SwissNameDictionary::DeleteEntry(
|
||||
Isolate* isolate, Handle<SwissNameDictionary> table, InternalIndex entry) {
|
||||
// GetCtrl() does the bounds check.
|
||||
DCHECK(IsFull(table->GetCtrl(entry.as_int())));
|
||||
|
||||
int i = entry.as_int();
|
||||
|
||||
table->SetCtrl(i, Ctrl::kDeleted);
|
||||
table->ClearDataTableEntry(isolate, i);
|
||||
// We leave the PropertyDetails unchanged because they are not relevant for
|
||||
// GC.
|
||||
|
||||
int nof = table->NumberOfElements();
|
||||
table->SetNumberOfElements(nof - 1);
|
||||
int nod = table->NumberOfDeletedElements();
|
||||
table->SetNumberOfDeletedElements(nod + 1);
|
||||
|
||||
// TODO(v8:11388) Abseil's flat_hash_map doesn't shrink on deletion, but may
|
||||
// decide on addition to do an in-place rehash to remove deleted elements. We
|
||||
// shrink on deletion here to follow what NameDictionary and
|
||||
// OrderedNameDictionary do. We should investigate which approach works
|
||||
// better.
|
||||
return Shrink(isolate, table);
|
||||
}
|
||||
|
||||
// static
|
||||
template <typename LocalIsolate>
|
||||
Handle<SwissNameDictionary> SwissNameDictionary::Rehash(
|
||||
LocalIsolate* isolate, Handle<SwissNameDictionary> table,
|
||||
int new_capacity) {
|
||||
DCHECK(IsValidCapacity(new_capacity));
|
||||
DCHECK_LE(table->NumberOfElements(), MaxUsableCapacity(new_capacity));
|
||||
ReadOnlyRoots roots(isolate);
|
||||
|
||||
Handle<SwissNameDictionary> new_table =
|
||||
isolate->factory()->NewSwissNameDictionaryWithCapacity(
|
||||
new_capacity, Heap::InYoungGeneration(*table) ? AllocationType::kYoung
|
||||
: AllocationType::kOld);
|
||||
|
||||
DisallowHeapAllocation no_gc;
|
||||
|
||||
int new_enum_index = 0;
|
||||
new_table->SetNumberOfElements(table->NumberOfElements());
|
||||
for (int enum_index = 0; enum_index < table->UsedCapacity(); ++enum_index) {
|
||||
int entry = table->EntryForEnumerationIndex(enum_index);
|
||||
|
||||
Object key;
|
||||
|
||||
if (table->ToKey(roots, entry, &key)) {
|
||||
Object value = table->ValueAtRaw(entry);
|
||||
PropertyDetails details = table->DetailsAt(entry);
|
||||
|
||||
int new_entry = new_table->AddInternal(Name::cast(key), value, details);
|
||||
|
||||
// TODO(v8::11388) Investigate ways of hoisting the branching needed to
|
||||
// select the correct meta table entry size (based on the capacity of the
|
||||
// table) out of the loop.
|
||||
new_table->SetEntryForEnumerationIndex(new_enum_index, new_entry);
|
||||
++new_enum_index;
|
||||
}
|
||||
}
|
||||
|
||||
new_table->SetHash(table->Hash());
|
||||
return new_table;
|
||||
}
|
||||
|
||||
// static
|
||||
Handle<SwissNameDictionary> SwissNameDictionary::Shrink(
|
||||
Isolate* isolate, Handle<SwissNameDictionary> table) {
|
||||
// TODO(v8:11388) We're using the same logic to decide whether or not to
|
||||
// shrink as OrderedNameDictionary and NameDictionary here. We should compare
|
||||
// this with the logic used by Abseil's flat_hash_map, which has a heuristic
|
||||
// for triggering an (in-place) rehash on addition, but never shrinks the
|
||||
// table. Abseil's heuristic doesn't take the numbere of deleted elements into
|
||||
// account, because it doesn't track that.
|
||||
|
||||
int nof = table->NumberOfElements();
|
||||
int capacity = table->Capacity();
|
||||
if (nof >= (capacity >> 2)) return table;
|
||||
int new_capacity = std::max(capacity / 2, kInitialCapacity);
|
||||
return Rehash(isolate, table, new_capacity);
|
||||
}
|
||||
|
||||
// The largest value we ever have to store in the enumeration table is
|
||||
// Capacity() - 1. The largest value we ever have to store for the present or
|
||||
// deleted element count is MaxUsableCapacity(Capacity()). All data in the
|
||||
|
@ -72,6 +72,18 @@ class SwissNameDictionary : public HeapObject {
|
||||
public:
|
||||
using Group = swiss_table::Group;
|
||||
|
||||
template <typename LocalIsolate>
|
||||
inline static Handle<SwissNameDictionary> Add(
|
||||
LocalIsolate* 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 LocalIsolate>
|
||||
inline InternalIndex FindEntry(LocalIsolate* isolate, Object key);
|
||||
|
||||
@ -103,6 +115,11 @@ class SwissNameDictionary : public HeapObject {
|
||||
template <typename LocalIsolate>
|
||||
void Initialize(LocalIsolate* isolate, ByteArray meta_table, int capacity);
|
||||
|
||||
template <typename LocalIsolate>
|
||||
static Handle<SwissNameDictionary> Rehash(LocalIsolate* isolate,
|
||||
Handle<SwissNameDictionary> table,
|
||||
int new_capacity);
|
||||
|
||||
inline void SetHash(int hash);
|
||||
inline int Hash();
|
||||
|
||||
@ -218,6 +235,10 @@ class SwissNameDictionary : public HeapObject {
|
||||
using ctrl_t = swiss_table::ctrl_t;
|
||||
using Ctrl = swiss_table::Ctrl;
|
||||
|
||||
template <typename LocalIsolate>
|
||||
inline static Handle<SwissNameDictionary> EnsureGrowable(
|
||||
LocalIsolate* isolate, Handle<SwissNameDictionary> table);
|
||||
|
||||
// Returns table of byte-encoded PropertyDetails (without enumeration index
|
||||
// stored in PropertyDetails).
|
||||
inline uint8_t* PropertyDetailsTable();
|
||||
@ -235,6 +256,13 @@ class SwissNameDictionary : public HeapObject {
|
||||
|
||||
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.
|
||||
|
Loading…
Reference in New Issue
Block a user