1// Copyright 2021 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include 'src/objects/swiss-name-dictionary.h' 6 7@doNotGenerateCppClass 8extern class SwissNameDictionary extends HeapObject { 9 hash: uint32; 10 const capacity: int32; 11 meta_table: ByteArray; 12 data_table[Convert<intptr>(capacity) * 2]: JSAny|TheHole; 13 ctrl_table[Convert<intptr>(capacity) + swiss_table::kGroupWidth]: uint8; 14 property_details_table[Convert<intptr>(capacity)]: uint8; 15} 16 17namespace swiss_table { 18 19const kDataTableEntryCount: constexpr intptr 20 generates 'SwissNameDictionary::kDataTableEntryCount'; 21 22const kMax1ByteMetaTableCapacity: constexpr int32 23 generates 'SwissNameDictionary::kMax1ByteMetaTableCapacity'; 24 25const kMax2ByteMetaTableCapacity: constexpr int32 26 generates 'SwissNameDictionary::kMax2ByteMetaTableCapacity'; 27 28const kNotFoundSentinel: 29 constexpr int32 generates 'SwissNameDictionary::kNotFoundSentinel'; 30 31extern macro LoadSwissNameDictionaryKey(SwissNameDictionary, intptr): Name; 32 33extern macro StoreSwissNameDictionaryKeyAndValue( 34 SwissNameDictionary, intptr, Object, Object): void; 35 36extern macro SwissNameDictionarySetCtrl( 37 SwissNameDictionary, intptr, intptr, uint8): void; 38 39extern macro StoreSwissNameDictionaryPropertyDetails( 40 SwissNameDictionary, intptr, intptr, uint8): void; 41 42extern macro 43SwissNameDictionaryIncreaseElementCountOrBailout( 44 ByteArray, intptr, uint32): uint32 labels Bailout; 45 46extern macro 47StoreSwissNameDictionaryEnumToEntryMapping( 48 SwissNameDictionary, intptr, intptr, int32): void; 49 50extern macro 51SwissNameDictionaryUpdateCountsForDeletion(ByteArray, intptr): uint32; 52 53namespace runtime { 54extern runtime SwissTableFindEntry(NoContext, SwissNameDictionary, Name): Smi; 55 56extern runtime SwissTableAdd( 57 NoContext, SwissNameDictionary, Name, Object, Smi): SwissNameDictionary; 58 59extern runtime ShrinkSwissNameDictionary( 60 NoContext, SwissNameDictionary): SwissNameDictionary; 61} 62 63// Counterpart for SwissNameDictionary::CapacityFor in C++. 64@export 65macro SwissNameDictionaryCapacityFor(atLeastSpaceFor: intptr): intptr { 66 if (atLeastSpaceFor <= 4) { 67 if (atLeastSpaceFor == 0) { 68 return 0; 69 } else if (atLeastSpaceFor < kSwissNameDictionaryInitialCapacity) { 70 return 4; 71 } else if (FromConstexpr<bool>(kGroupWidth == 16)) { 72 dcheck(atLeastSpaceFor == 4); 73 return 4; 74 } else if (FromConstexpr<bool>(kGroupWidth == 8)) { 75 dcheck(atLeastSpaceFor == 4); 76 return 8; 77 } 78 } 79 80 const nonNormalized = atLeastSpaceFor + atLeastSpaceFor / 7; 81 return IntPtrRoundUpToPowerOfTwo32(nonNormalized); 82} 83 84// Counterpart for SwissNameDictionary::MaxUsableCapacity in C++. 85@export 86macro SwissNameDictionaryMaxUsableCapacity(capacity: intptr): intptr { 87 dcheck(capacity == 0 || capacity >= kSwissNameDictionaryInitialCapacity); 88 if (FromConstexpr<bool>(kGroupWidth == 8) && capacity == 4) { 89 // If the group size is 16 we can fully utilize capacity 4: There will be 90 // enough kEmpty entries in the ctrl table. 91 return 3; 92 } 93 return capacity - capacity / 8; 94} 95 96// Counterpart for SwissNameDictionary::SizeFor in C++. 97@export 98macro SwissNameDictionarySizeFor(capacity: intptr): intptr { 99 const constant: constexpr int32 = kHeapObjectHeaderSize + 8 + kTaggedSize; 100 const dynamic: intptr = 101 capacity * FromConstexpr<intptr>(2 * kTaggedSize + 2) + 102 FromConstexpr<intptr>(kGroupWidth); 103 return constant + dynamic; 104} 105 106// Counterpart for SwissNameDictionary::MetaTableSizePerEntryFor in C++. 107@export 108macro SwissNameDictionaryMetaTableSizePerEntryFor(capacity: intptr): intptr { 109 if (capacity <= kMax1ByteMetaTableCapacity) { 110 return 1; 111 } else if (capacity <= kMax2ByteMetaTableCapacity) { 112 return 2; 113 } else { 114 return 4; 115 } 116} 117 118// Counterpart for SwissNameDictionary::MetaTableSizeFor in C++. 119@export 120macro SwissNameDictionaryMetaTableSizeFor(capacity: intptr): intptr { 121 const perEntry: intptr = 122 SwissNameDictionaryMetaTableSizePerEntryFor(capacity); 123 const maxUsable: intptr = 124 Convert<intptr>(SwissNameDictionaryMaxUsableCapacity(capacity)); 125 126 return (2 + maxUsable) * perEntry; 127} 128 129// 130// Offsets. MT stands for "minus tag" 131// 132 133const kDataTableStartOffsetMT: constexpr intptr 134 generates 'SwissNameDictionary::DataTableStartOffset() - kHeapObjectTag'; 135 136@export 137macro SwissNameDictionaryDataTableStartOffsetMT(): intptr { 138 return kDataTableStartOffsetMT; 139} 140 141@export 142macro SwissNameDictionaryCtrlTableStartOffsetMT(capacity: intptr): intptr { 143 return kDataTableStartOffsetMT + 144 kDataTableEntryCount * FromConstexpr<intptr>(kTaggedSize) * capacity; 145} 146 147macro Probe(hash: uint32, mask: uint32): ProbeSequence { 148 // Mask must be a power of 2 minus 1. 149 dcheck(((mask + 1) & mask) == 0); 150 151 return ProbeSequence{mask: mask, offset: H1(hash) & mask, index: 0}; 152} 153 154macro FindEntry<GroupLoader: type>( 155 table: SwissNameDictionary, key: Name): never labels 156Found(intptr), NotFound { 157 const hash: uint32 = LoadNameHash(key); 158 const capacity: int32 = table.capacity; 159 const nonZeroCapacity: int32 = capacity | Convert<int32>(capacity == 0); 160 const mask: uint32 = Unsigned(nonZeroCapacity - 1); 161 162 const ctrlTableStart: intptr = 163 SwissNameDictionaryCtrlTableStartOffsetMT(Convert<intptr>(capacity)) + 164 BitcastTaggedToWord(table); 165 166 let seq = Probe(hash, mask); 167 while (true) { 168 const group = 169 GroupLoader{}.LoadGroup(ctrlTableStart + Convert<intptr>(seq.offset)); 170 let match = group.Match(H2(hash)); 171 while (match.HasBitsSet()) { 172 const inGroupIndex = match.LowestBitSet(); 173 const candidateEntry = Convert<intptr>(seq.Offset(inGroupIndex)); 174 const candidateKey: Object = 175 LoadSwissNameDictionaryKey(table, candidateEntry); 176 if (TaggedEqual(key, candidateKey)) { 177 goto Found(candidateEntry); 178 } 179 match.ClearLowestSetBit(); 180 } 181 if (group.MatchEmpty().HasBitsSet()) { 182 goto NotFound; 183 } 184 seq.Next(); 185 } 186 187 unreachable; 188} 189 190macro FindFirstEmpty<GroupLoader: type>( 191 table: SwissNameDictionary, capacity: intptr, hash: uint32): int32 { 192 const nonZeroCapacity: int32 = 193 Convert<int32>(capacity) | Convert<int32>(capacity == 0); 194 const mask: uint32 = Unsigned(nonZeroCapacity - 1); 195 196 const ctrlTableStart: intptr = 197 SwissNameDictionaryCtrlTableStartOffsetMT(capacity) + 198 BitcastTaggedToWord(table); 199 200 let seq = Probe(hash, mask); 201 while (true) { 202 const group = 203 GroupLoader{}.LoadGroup(ctrlTableStart + Convert<intptr>(seq.offset)); 204 const match = group.MatchEmpty(); 205 if (match.HasBitsSet()) { 206 const inGroupIndex = match.LowestBitSet(); 207 return Signed(seq.Offset(inGroupIndex)); 208 } 209 seq.Next(); 210 } 211 212 unreachable; 213} 214 215macro Add<GroupLoader: type>( 216 table: SwissNameDictionary, key: Name, value: Object, 217 propertyDetails: uint8): void labels Bailout { 218 const capacity: intptr = Convert<intptr>(table.capacity); 219 const maxUsable: uint32 = 220 Unsigned(Convert<int32>(SwissNameDictionaryMaxUsableCapacity(capacity))); 221 222 try { 223 // We read the used capacity (present + deleted elements), compare it 224 // against the max usable capacity to determine if a bailout is necessary, 225 // and in case of no bailout increase the present element count all in one 226 // go using the following macro. This way we don't have to do the branching 227 // needed for meta table accesses multiple times. 228 const used: uint32 = SwissNameDictionaryIncreaseElementCountOrBailout( 229 table.meta_table, capacity, maxUsable) otherwise Bailout; 230 231 const hash: uint32 = LoadNameHash(key); 232 const newEntry32 = FindFirstEmpty<GroupLoader>(table, capacity, hash); 233 const newEntry = Convert<intptr>(newEntry32); 234 235 StoreSwissNameDictionaryKeyAndValue(table, newEntry, key, value); 236 237 StoreSwissNameDictionaryEnumToEntryMapping( 238 table, capacity, Convert<intptr>(used), newEntry32); 239 240 const h2 = Convert<uint8>(Convert<intptr>(H2(hash))); 241 SwissNameDictionarySetCtrl(table, capacity, newEntry, h2); 242 243 StoreSwissNameDictionaryPropertyDetails( 244 table, capacity, newEntry, propertyDetails); 245 } label Bailout { 246 goto Bailout; 247 } 248} 249 250@export 251macro SwissNameDictionaryDelete(table: SwissNameDictionary, entry: intptr): 252 void labels Shrunk(SwissNameDictionary) { 253 const capacity = Convert<intptr>(table.capacity); 254 255 // Update present and deleted element counts at once, without needing to do 256 // the meta table access related branching more than once. 257 const newElementCount = 258 SwissNameDictionaryUpdateCountsForDeletion(table.meta_table, capacity); 259 260 StoreSwissNameDictionaryKeyAndValue(table, entry, TheHole, TheHole); 261 262 const kDeleted = FromConstexpr<uint8>(ctrl::kDeleted); 263 SwissNameDictionarySetCtrl(table, capacity, entry, kDeleted); 264 265 // Same logic for deciding when to shrink as in SwissNameDictionary::Delete. 266 if (Convert<intptr>(Signed(newElementCount)) < (capacity >> 2)) { 267 const shrunkTable = runtime::ShrinkSwissNameDictionary(kNoContext, table); 268 goto Shrunk(shrunkTable); 269 } 270} 271 272// TODO(v8:11330) Ideally, we would like to implement 273// CodeStubAssembler::SwissNameDictionaryFindEntry in Torque and do the 274// necessary switching between the two implementations with if(kUseSimd) {...} 275// else {...}. However, Torque currently generates a call to 276// CodeAssembler::Branch which cannot guarantee that code for the "bad" path is 277// not generated, even if the branch can be resolved at compile time. This means 278// that we end up trying to generate unused code using unsupported instructions. 279@export 280macro SwissNameDictionaryFindEntrySIMD(table: SwissNameDictionary, key: Name): 281 never labels Found(intptr), NotFound { 282 FindEntry<GroupSse2Loader>(table, key) 283 otherwise Found, NotFound; 284} 285 286@export 287macro SwissNameDictionaryFindEntryPortable( 288 table: SwissNameDictionary, key: Name): never labels 289Found(intptr), 290 NotFound { 291 FindEntry<GroupPortableLoader>(table, key) 292 otherwise Found, NotFound; 293} 294 295// TODO(v8:11330) Ideally, we would like to implement 296// CodeStubAssembler::SwissNameDictionaryAdd in Torque and do the necessary 297// switching between the two implementations with if(kUseSimd) {...} else {...}. 298// However, Torque currently generates a call to CodeAssembler::Branch which 299// cannot guarantee that code for the "bad" path is not generated, even if the 300// branch can be resolved at compile time. This means that we end up trying to 301// generate unused code using unsupported instructions. 302@export 303macro SwissNameDictionaryAddSIMD( 304 table: SwissNameDictionary, key: Name, value: Object, 305 propertyDetails: uint8): void labels Bailout { 306 Add<GroupSse2Loader>(table, key, value, propertyDetails) 307 otherwise Bailout; 308} 309 310@export 311macro SwissNameDictionaryAddPortable( 312 table: SwissNameDictionary, key: Name, value: Object, 313 propertyDetails: uint8): void labels Bailout { 314 Add<GroupPortableLoader>(table, key, value, propertyDetails) 315 otherwise Bailout; 316} 317} 318