blob: 4f7e2e550065fa2251ca3745e5addbdcc0669596 [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.
#include 'src/objects/swiss-name-dictionary.h'
@doNotGenerateCppClass
extern class SwissNameDictionary extends HeapObject {
hash: uint32;
const capacity: int32;
meta_table: ByteArray;
data_table[Convert<intptr>(capacity) * 2]: JSAny|TheHole;
ctrl_table[Convert<intptr>(capacity) + swiss_table::kGroupWidth]: uint8;
property_details_table[Convert<intptr>(capacity)]: uint8;
}
namespace swiss_table {
const kDataTableEntryCount: constexpr intptr
generates 'SwissNameDictionary::kDataTableEntryCount';
const kMax1ByteMetaTableCapacity: constexpr int32
generates 'SwissNameDictionary::kMax1ByteMetaTableCapacity';
const kMax2ByteMetaTableCapacity: constexpr int32
generates 'SwissNameDictionary::kMax2ByteMetaTableCapacity';
const kNotFoundSentinel:
constexpr int32 generates 'SwissNameDictionary::kNotFoundSentinel';
extern macro LoadSwissNameDictionaryKey(SwissNameDictionary, intptr): Name;
extern macro StoreSwissNameDictionaryKeyAndValue(
SwissNameDictionary, intptr, Object, Object): void;
extern macro SwissNameDictionarySetCtrl(
SwissNameDictionary, intptr, intptr, uint8): void;
extern macro StoreSwissNameDictionaryPropertyDetails(
SwissNameDictionary, intptr, intptr, uint8): void;
extern macro
SwissNameDictionaryIncreaseElementCountOrBailout(
ByteArray, intptr, uint32): uint32 labels Bailout;
extern macro
StoreSwissNameDictionaryEnumToEntryMapping(
SwissNameDictionary, intptr, intptr, int32): void;
extern macro
SwissNameDictionaryUpdateCountsForDeletion(ByteArray, intptr): uint32;
namespace runtime {
extern runtime SwissTableFindEntry(NoContext, SwissNameDictionary, Name): Smi;
extern runtime SwissTableAdd(
NoContext, SwissNameDictionary, Name, Object, Smi): SwissNameDictionary;
extern runtime ShrinkSwissNameDictionary(
NoContext, SwissNameDictionary): SwissNameDictionary;
}
// Counterpart for SwissNameDictionary::CapacityFor in C++.
@export
macro SwissNameDictionaryCapacityFor(atLeastSpaceFor: intptr): intptr {
if (atLeastSpaceFor <= 4) {
if (atLeastSpaceFor == 0) {
return 0;
} else if (atLeastSpaceFor < kSwissNameDictionaryInitialCapacity) {
return 4;
} else if (FromConstexpr<bool>(kGroupWidth == 16)) {
dcheck(atLeastSpaceFor == 4);
return 4;
} else if (FromConstexpr<bool>(kGroupWidth == 8)) {
dcheck(atLeastSpaceFor == 4);
return 8;
}
}
const nonNormalized = atLeastSpaceFor + atLeastSpaceFor / 7;
return IntPtrRoundUpToPowerOfTwo32(nonNormalized);
}
// Counterpart for SwissNameDictionary::MaxUsableCapacity in C++.
@export
macro SwissNameDictionaryMaxUsableCapacity(capacity: intptr): intptr {
dcheck(capacity == 0 || capacity >= kSwissNameDictionaryInitialCapacity);
if (FromConstexpr<bool>(kGroupWidth == 8) && capacity == 4) {
// If the group size is 16 we can fully utilize capacity 4: There will be
// enough kEmpty entries in the ctrl table.
return 3;
}
return capacity - capacity / 8;
}
// Counterpart for SwissNameDictionary::SizeFor in C++.
@export
macro SwissNameDictionarySizeFor(capacity: intptr): intptr {
const constant: constexpr int32 = kHeapObjectHeaderSize + 8 + kTaggedSize;
const dynamic: intptr =
capacity * FromConstexpr<intptr>(2 * kTaggedSize + 2) +
FromConstexpr<intptr>(kGroupWidth);
return constant + dynamic;
}
// Counterpart for SwissNameDictionary::MetaTableSizePerEntryFor in C++.
@export
macro SwissNameDictionaryMetaTableSizePerEntryFor(capacity: intptr): intptr {
if (capacity <= kMax1ByteMetaTableCapacity) {
return 1;
} else if (capacity <= kMax2ByteMetaTableCapacity) {
return 2;
} else {
return 4;
}
}
// Counterpart for SwissNameDictionary::MetaTableSizeFor in C++.
@export
macro SwissNameDictionaryMetaTableSizeFor(capacity: intptr): intptr {
const perEntry: intptr =
SwissNameDictionaryMetaTableSizePerEntryFor(capacity);
const maxUsable: intptr =
Convert<intptr>(SwissNameDictionaryMaxUsableCapacity(capacity));
return (2 + maxUsable) * perEntry;
}
//
// Offsets. MT stands for "minus tag"
//
const kDataTableStartOffsetMT: constexpr intptr
generates 'SwissNameDictionary::DataTableStartOffset() - kHeapObjectTag';
@export
macro SwissNameDictionaryDataTableStartOffsetMT(): intptr {
return kDataTableStartOffsetMT;
}
@export
macro SwissNameDictionaryCtrlTableStartOffsetMT(capacity: intptr): intptr {
return kDataTableStartOffsetMT +
kDataTableEntryCount * FromConstexpr<intptr>(kTaggedSize) * capacity;
}
macro Probe(hash: uint32, mask: uint32): ProbeSequence {
// Mask must be a power of 2 minus 1.
dcheck(((mask + 1) & mask) == 0);
return ProbeSequence{mask: mask, offset: H1(hash) & mask, index: 0};
}
macro FindEntry<GroupLoader: type>(
table: SwissNameDictionary, key: Name): never labels
Found(intptr), NotFound {
const hash: uint32 = LoadNameHash(key);
const capacity: int32 = table.capacity;
const nonZeroCapacity: int32 = capacity | Convert<int32>(capacity == 0);
const mask: uint32 = Unsigned(nonZeroCapacity - 1);
const ctrlTableStart: intptr =
SwissNameDictionaryCtrlTableStartOffsetMT(Convert<intptr>(capacity)) +
BitcastTaggedToWord(table);
let seq = Probe(hash, mask);
while (true) {
const group =
GroupLoader{}.LoadGroup(ctrlTableStart + Convert<intptr>(seq.offset));
let match = group.Match(H2(hash));
while (match.HasBitsSet()) {
const inGroupIndex = match.LowestBitSet();
const candidateEntry = Convert<intptr>(seq.Offset(inGroupIndex));
const candidateKey: Object =
LoadSwissNameDictionaryKey(table, candidateEntry);
if (TaggedEqual(key, candidateKey)) {
goto Found(candidateEntry);
}
match.ClearLowestSetBit();
}
if (group.MatchEmpty().HasBitsSet()) {
goto NotFound;
}
seq.Next();
}
unreachable;
}
macro FindFirstEmpty<GroupLoader: type>(
table: SwissNameDictionary, capacity: intptr, hash: uint32): int32 {
const nonZeroCapacity: int32 =
Convert<int32>(capacity) | Convert<int32>(capacity == 0);
const mask: uint32 = Unsigned(nonZeroCapacity - 1);
const ctrlTableStart: intptr =
SwissNameDictionaryCtrlTableStartOffsetMT(capacity) +
BitcastTaggedToWord(table);
let seq = Probe(hash, mask);
while (true) {
const group =
GroupLoader{}.LoadGroup(ctrlTableStart + Convert<intptr>(seq.offset));
const match = group.MatchEmpty();
if (match.HasBitsSet()) {
const inGroupIndex = match.LowestBitSet();
return Signed(seq.Offset(inGroupIndex));
}
seq.Next();
}
unreachable;
}
macro Add<GroupLoader: type>(
table: SwissNameDictionary, key: Name, value: Object,
propertyDetails: uint8): void labels Bailout {
const capacity: intptr = Convert<intptr>(table.capacity);
const maxUsable: uint32 =
Unsigned(Convert<int32>(SwissNameDictionaryMaxUsableCapacity(capacity)));
try {
// We read the used capacity (present + deleted elements), compare it
// against the max usable capacity to determine if a bailout is necessary,
// and in case of no bailout increase the present element count all in one
// go using the following macro. This way we don't have to do the branching
// needed for meta table accesses multiple times.
const used: uint32 = SwissNameDictionaryIncreaseElementCountOrBailout(
table.meta_table, capacity, maxUsable) otherwise Bailout;
const hash: uint32 = LoadNameHash(key);
const newEntry32 = FindFirstEmpty<GroupLoader>(table, capacity, hash);
const newEntry = Convert<intptr>(newEntry32);
StoreSwissNameDictionaryKeyAndValue(table, newEntry, key, value);
StoreSwissNameDictionaryEnumToEntryMapping(
table, capacity, Convert<intptr>(used), newEntry32);
const h2 = Convert<uint8>(Convert<intptr>(H2(hash)));
SwissNameDictionarySetCtrl(table, capacity, newEntry, h2);
StoreSwissNameDictionaryPropertyDetails(
table, capacity, newEntry, propertyDetails);
} label Bailout {
goto Bailout;
}
}
@export
macro SwissNameDictionaryDelete(table: SwissNameDictionary, entry: intptr):
void labels Shrunk(SwissNameDictionary) {
const capacity = Convert<intptr>(table.capacity);
// Update present and deleted element counts at once, without needing to do
// the meta table access related branching more than once.
const newElementCount =
SwissNameDictionaryUpdateCountsForDeletion(table.meta_table, capacity);
StoreSwissNameDictionaryKeyAndValue(table, entry, TheHole, TheHole);
const kDeleted = FromConstexpr<uint8>(ctrl::kDeleted);
SwissNameDictionarySetCtrl(table, capacity, entry, kDeleted);
// Same logic for deciding when to shrink as in SwissNameDictionary::Delete.
if (Convert<intptr>(Signed(newElementCount)) < (capacity >> 2)) {
const shrunkTable = runtime::ShrinkSwissNameDictionary(kNoContext, table);
goto Shrunk(shrunkTable);
}
}
// TODO(v8:11330) Ideally, we would like to implement
// CodeStubAssembler::SwissNameDictionaryFindEntry in Torque and do the
// necessary switching between the two implementations with if(kUseSimd) {...}
// else {...}. However, Torque currently generates a call to
// CodeAssembler::Branch which cannot guarantee that code for the "bad" path is
// not generated, even if the branch can be resolved at compile time. This means
// that we end up trying to generate unused code using unsupported instructions.
@export
macro SwissNameDictionaryFindEntrySIMD(table: SwissNameDictionary, key: Name):
never labels Found(intptr), NotFound {
FindEntry<GroupSse2Loader>(table, key)
otherwise Found, NotFound;
}
@export
macro SwissNameDictionaryFindEntryPortable(
table: SwissNameDictionary, key: Name): never labels Found(intptr),
NotFound {
FindEntry<GroupPortableLoader>(table, key)
otherwise Found, NotFound;
}
// TODO(v8:11330) Ideally, we would like to implement
// CodeStubAssembler::SwissNameDictionaryAdd in Torque and do the necessary
// switching between the two implementations with if(kUseSimd) {...} else {...}.
// However, Torque currently generates a call to CodeAssembler::Branch which
// cannot guarantee that code for the "bad" path is not generated, even if the
// branch can be resolved at compile time. This means that we end up trying to
// generate unused code using unsupported instructions.
@export
macro SwissNameDictionaryAddSIMD(
table: SwissNameDictionary, key: Name, value: Object,
propertyDetails: uint8): void labels Bailout {
Add<GroupSse2Loader>(table, key, value, propertyDetails)
otherwise Bailout;
}
@export
macro SwissNameDictionaryAddPortable(
table: SwissNameDictionary, key: Name, value: Object,
propertyDetails: uint8): void labels Bailout {
Add<GroupPortableLoader>(table, key, value, propertyDetails)
otherwise Bailout;
}
}