// 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(capacity) * 2]: JSAny|TheHole; ctrl_table[Convert(capacity) + swiss_table::kGroupWidth]: uint8; property_details_table[Convert(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(kGroupWidth == 16)) { dcheck(atLeastSpaceFor == 4); return 4; } else if (FromConstexpr(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(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(2 * kTaggedSize + 2) + FromConstexpr(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(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(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( table: SwissNameDictionary, key: Name): never labels Found(intptr), NotFound { const hash: uint32 = LoadNameHash(key); const capacity: int32 = table.capacity; const nonZeroCapacity: int32 = capacity | Convert(capacity == 0); const mask: uint32 = Unsigned(nonZeroCapacity - 1); const ctrlTableStart: intptr = SwissNameDictionaryCtrlTableStartOffsetMT(Convert(capacity)) + BitcastTaggedToWord(table); let seq = Probe(hash, mask); while (true) { const group = GroupLoader{}.LoadGroup(ctrlTableStart + Convert(seq.offset)); let match = group.Match(H2(hash)); while (match.HasBitsSet()) { const inGroupIndex = match.LowestBitSet(); const candidateEntry = Convert(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( table: SwissNameDictionary, capacity: intptr, hash: uint32): int32 { const nonZeroCapacity: int32 = Convert(capacity) | Convert(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(seq.offset)); const match = group.MatchEmpty(); if (match.HasBitsSet()) { const inGroupIndex = match.LowestBitSet(); return Signed(seq.Offset(inGroupIndex)); } seq.Next(); } unreachable; } macro Add( table: SwissNameDictionary, key: Name, value: Object, propertyDetails: uint8): void labels Bailout { const capacity: intptr = Convert(table.capacity); const maxUsable: uint32 = Unsigned(Convert(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(table, capacity, hash); const newEntry = Convert(newEntry32); StoreSwissNameDictionaryKeyAndValue(table, newEntry, key, value); StoreSwissNameDictionaryEnumToEntryMapping( table, capacity, Convert(used), newEntry32); const h2 = Convert(Convert(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(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(ctrl::kDeleted); SwissNameDictionarySetCtrl(table, capacity, entry, kDeleted); // Same logic for deciding when to shrink as in SwissNameDictionary::Delete. if (Convert(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(table, key) otherwise Found, NotFound; } @export macro SwissNameDictionaryFindEntryPortable( table: SwissNameDictionary, key: Name): never labels Found(intptr), NotFound { FindEntry(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(table, key, value, propertyDetails) otherwise Bailout; } @export macro SwissNameDictionaryAddPortable( table: SwissNameDictionary, key: Name, value: Object, propertyDetails: uint8): void labels Bailout { Add(table, key, value, propertyDetails) otherwise Bailout; } }