• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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