[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:
Frank Emrich 2021-02-24 18:01:20 +01:00 committed by Commit Bot
parent 491d01c035
commit ae153904b8
3 changed files with 197 additions and 0 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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.