1 // Copyright 2018 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 #ifndef V8_OBJECTS_ORDERED_HASH_TABLE_H_
6 #define V8_OBJECTS_ORDERED_HASH_TABLE_H_
7
8 #include "src/base/export-template.h"
9 #include "src/common/globals.h"
10 #include "src/objects/fixed-array.h"
11 #include "src/objects/internal-index.h"
12 #include "src/objects/js-objects.h"
13 #include "src/objects/keys.h"
14 #include "src/objects/smi.h"
15 #include "src/roots/roots.h"
16
17 // Has to be the last include (doesn't have include guards):
18 #include "src/objects/object-macros.h"
19
20 namespace v8 {
21 namespace internal {
22
23 // OrderedHashTable is a HashTable with Object keys that preserves
24 // insertion order. There are Map and Set interfaces (OrderedHashMap
25 // and OrderedHashTable, below). It is meant to be used by JSMap/JSSet.
26 //
27 // Only Object keys are supported, with Object::SameValueZero() used as the
28 // equality operator and Object::GetHash() for the hash function.
29 //
30 // Based on the "Deterministic Hash Table" as described by Jason Orendorff at
31 // https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables
32 // Originally attributed to Tyler Close.
33 //
34 // Memory layout:
35 // [0] : Prefix
36 // [kPrefixSize]: element count
37 // [kPrefixSize + 1]: deleted element count
38 // [kPrefixSize + 2]: bucket count
39 // [kPrefixSize + 3..(kPrefixSize + 3 + NumberOfBuckets() - 1)]: "hash table",
40 // where each item is an offset into the
41 // data table (see below) where the first
42 // item in this bucket is stored.
43 // [kPrefixSize + 3 + NumberOfBuckets()..length]: "data table", an
44 // array of length Capacity() * kEntrySize,
45 // where the first entrysize items are
46 // handled by the derived class and the
47 // item at kChainOffset is another entry
48 // into the data table indicating the next
49 // entry in this hash bucket.
50 //
51 // When we transition the table to a new version we obsolete it and reuse parts
52 // of the memory to store information how to transition an iterator to the new
53 // table:
54 //
55 // Memory layout for obsolete table:
56 // [0] : Prefix
57 // [kPrefixSize + 0]: Next newer table
58 // [kPrefixSize + 1]: deleted element count or kClearedTableSentinel if
59 // the table was cleared
60 // [kPrefixSize + 2]: bucket count
61 // [kPrefixSize + 3..(kPrefixSize + 3 + NumberOfDeletedElements() - 1)]:
62 // The indexes of the removed holes. This part is only
63 // usable for non-cleared tables, as clearing removes the
64 // deleted elements count.
65 // [kPrefixSize + 3 + NumberOfDeletedElements()..length]: Not used
66 template <class Derived, int entrysize>
67 class OrderedHashTable : public FixedArray {
68 public:
69 // Returns an OrderedHashTable (possibly |table|) with enough space
70 // to add at least one new element.
71 template <typename IsolateT>
72 static MaybeHandle<Derived> EnsureGrowable(IsolateT* isolate,
73 Handle<Derived> table);
74
75 // Returns an OrderedHashTable (possibly |table|) that's shrunken
76 // if possible.
77 static Handle<Derived> Shrink(Isolate* isolate, Handle<Derived> table);
78
79 // Returns a new empty OrderedHashTable and records the clearing so that
80 // existing iterators can be updated.
81 static Handle<Derived> Clear(Isolate* isolate, Handle<Derived> table);
82
83 // Returns true if the OrderedHashTable contains the key
84 static bool HasKey(Isolate* isolate, Derived table, Object key);
85
86 // Returns whether a potential key |k| returned by KeyAt is a real
87 // key (meaning that it is not a hole).
88 static inline bool IsKey(ReadOnlyRoots roots, Object k);
89
90 // Returns a true value if the OrderedHashTable contains the key and
91 // the key has been deleted. This does not shrink the table.
92 static bool Delete(Isolate* isolate, Derived table, Object key);
93
94 InternalIndex FindEntry(Isolate* isolate, Object key);
95
NumberOfElements()96 int NumberOfElements() const {
97 return Smi::ToInt(get(NumberOfElementsIndex()));
98 }
99
NumberOfDeletedElements()100 int NumberOfDeletedElements() const {
101 return Smi::ToInt(get(NumberOfDeletedElementsIndex()));
102 }
103
104 // Returns the number of contiguous entries in the data table, starting at 0,
105 // that either are real entries or have been deleted.
UsedCapacity()106 int UsedCapacity() const {
107 return NumberOfElements() + NumberOfDeletedElements();
108 }
109
Capacity()110 int Capacity() { return NumberOfBuckets() * kLoadFactor; }
111
NumberOfBuckets()112 int NumberOfBuckets() const {
113 return Smi::ToInt(get(NumberOfBucketsIndex()));
114 }
115
IterateEntries()116 InternalIndex::Range IterateEntries() {
117 return InternalIndex::Range(UsedCapacity());
118 }
119
120 // use IsKey to check if this is a deleted entry.
KeyAt(InternalIndex entry)121 Object KeyAt(InternalIndex entry) {
122 DCHECK_LT(entry.as_int(), this->UsedCapacity());
123 return get(EntryToIndex(entry));
124 }
125
126 // Similar to KeyAt, but indicates whether the given entry is valid
127 // (not deleted one)
128 inline bool ToKey(ReadOnlyRoots roots, InternalIndex entry, Object* out_key);
129
IsObsolete()130 bool IsObsolete() { return !get(NextTableIndex()).IsSmi(); }
131
132 // The next newer table. This is only valid if the table is obsolete.
NextTable()133 Derived NextTable() { return Derived::cast(get(NextTableIndex())); }
134
135 // When the table is obsolete we store the indexes of the removed holes.
RemovedIndexAt(int index)136 int RemovedIndexAt(int index) {
137 return Smi::ToInt(get(RemovedHolesIndex() + index));
138 }
139
140 // The extra +1 is for linking the bucket chains together.
141 static const int kEntrySize = entrysize + 1;
142 static const int kEntrySizeWithoutChain = entrysize;
143 static const int kChainOffset = entrysize;
144
145 static const int kNotFound = -1;
146 // The minimum capacity. Note that despite this value, 0 is also a permitted
147 // capacity, indicating a table without any storage for elements.
148 static const int kInitialCapacity = 4;
149
PrefixIndex()150 static constexpr int PrefixIndex() { return 0; }
151
NumberOfElementsIndex()152 static constexpr int NumberOfElementsIndex() { return Derived::kPrefixSize; }
153
154 // The next table is stored at the same index as the nof elements.
NextTableIndex()155 static constexpr int NextTableIndex() { return NumberOfElementsIndex(); }
156
NumberOfDeletedElementsIndex()157 static constexpr int NumberOfDeletedElementsIndex() {
158 return NumberOfElementsIndex() + 1;
159 }
160
NumberOfBucketsIndex()161 static constexpr int NumberOfBucketsIndex() {
162 return NumberOfDeletedElementsIndex() + 1;
163 }
164
HashTableStartIndex()165 static constexpr int HashTableStartIndex() {
166 return NumberOfBucketsIndex() + 1;
167 }
168
RemovedHolesIndex()169 static constexpr int RemovedHolesIndex() { return HashTableStartIndex(); }
170
NumberOfElementsOffset()171 static constexpr int NumberOfElementsOffset() {
172 return FixedArray::OffsetOfElementAt(NumberOfElementsIndex());
173 }
174
NextTableOffset()175 static constexpr int NextTableOffset() {
176 return FixedArray::OffsetOfElementAt(NextTableIndex());
177 }
178
NumberOfDeletedElementsOffset()179 static constexpr int NumberOfDeletedElementsOffset() {
180 return FixedArray::OffsetOfElementAt(NumberOfDeletedElementsIndex());
181 }
182
NumberOfBucketsOffset()183 static constexpr int NumberOfBucketsOffset() {
184 return FixedArray::OffsetOfElementAt(NumberOfBucketsIndex());
185 }
186
HashTableStartOffset()187 static constexpr int HashTableStartOffset() {
188 return FixedArray::OffsetOfElementAt(HashTableStartIndex());
189 }
190
191 static const int kLoadFactor = 2;
192
193 // NumberOfDeletedElements is set to kClearedTableSentinel when
194 // the table is cleared, which allows iterator transitions to
195 // optimize that case.
196 static const int kClearedTableSentinel = -1;
MaxCapacity()197 static constexpr int MaxCapacity() {
198 return (FixedArray::kMaxLength - HashTableStartIndex()) /
199 (1 + (kEntrySize * kLoadFactor));
200 }
201
202 protected:
203 // Returns an OrderedHashTable with a capacity of at least |capacity|.
204 template <typename IsolateT>
205 static MaybeHandle<Derived> Allocate(
206 IsolateT* isolate, int capacity,
207 AllocationType allocation = AllocationType::kYoung);
208
209 static MaybeHandle<Derived> AllocateEmpty(Isolate* isolate,
210 AllocationType allocation,
211 RootIndex root_ndex);
212
213 template <typename IsolateT>
214 static MaybeHandle<Derived> Rehash(IsolateT* isolate, Handle<Derived> table);
215 template <typename IsolateT>
216 static MaybeHandle<Derived> Rehash(IsolateT* isolate, Handle<Derived> table,
217 int new_capacity);
218
HashToEntryRaw(int hash)219 int HashToEntryRaw(int hash) {
220 int bucket = HashToBucket(hash);
221 Object entry = this->get(HashTableStartIndex() + bucket);
222 int entry_int = Smi::ToInt(entry);
223 DCHECK(entry_int == kNotFound || entry_int >= 0);
224 return entry_int;
225 }
226
NextChainEntryRaw(int entry)227 int NextChainEntryRaw(int entry) {
228 DCHECK_LT(entry, this->UsedCapacity());
229 Object next_entry = get(EntryToIndexRaw(entry) + kChainOffset);
230 int next_entry_int = Smi::ToInt(next_entry);
231 DCHECK(next_entry_int == kNotFound || next_entry_int >= 0);
232 return next_entry_int;
233 }
234
235 // Returns an index into |this| for the given entry.
EntryToIndexRaw(int entry)236 int EntryToIndexRaw(int entry) {
237 return HashTableStartIndex() + NumberOfBuckets() + (entry * kEntrySize);
238 }
239
EntryToIndex(InternalIndex entry)240 int EntryToIndex(InternalIndex entry) {
241 return EntryToIndexRaw(entry.as_int());
242 }
243
HashToBucket(int hash)244 int HashToBucket(int hash) { return hash & (NumberOfBuckets() - 1); }
245
SetNumberOfBuckets(int num)246 void SetNumberOfBuckets(int num) {
247 set(NumberOfBucketsIndex(), Smi::FromInt(num));
248 }
249
SetNumberOfElements(int num)250 void SetNumberOfElements(int num) {
251 set(NumberOfElementsIndex(), Smi::FromInt(num));
252 }
253
SetNumberOfDeletedElements(int num)254 void SetNumberOfDeletedElements(int num) {
255 set(NumberOfDeletedElementsIndex(), Smi::FromInt(num));
256 }
257
258
SetNextTable(Derived next_table)259 void SetNextTable(Derived next_table) { set(NextTableIndex(), next_table); }
260
SetRemovedIndexAt(int index,int removed_index)261 void SetRemovedIndexAt(int index, int removed_index) {
262 return set(RemovedHolesIndex() + index, Smi::FromInt(removed_index));
263 }
264
265 OBJECT_CONSTRUCTORS(OrderedHashTable, FixedArray);
266
267 private:
268 friend class OrderedNameDictionaryHandler;
269 };
270
271 class V8_EXPORT_PRIVATE OrderedHashSet
272 : public OrderedHashTable<OrderedHashSet, 1> {
273 using Base = OrderedHashTable<OrderedHashSet, 1>;
274
275 public:
276 DECL_CAST(OrderedHashSet)
277 DECL_PRINTER(OrderedHashSet)
278
279 static MaybeHandle<OrderedHashSet> Add(Isolate* isolate,
280 Handle<OrderedHashSet> table,
281 Handle<Object> value);
282 static Handle<FixedArray> ConvertToKeysArray(Isolate* isolate,
283 Handle<OrderedHashSet> table,
284 GetKeysConversion convert);
285 static MaybeHandle<OrderedHashSet> Rehash(Isolate* isolate,
286 Handle<OrderedHashSet> table,
287 int new_capacity);
288 static MaybeHandle<OrderedHashSet> Rehash(Isolate* isolate,
289 Handle<OrderedHashSet> table);
290 template <typename IsolateT>
291 static MaybeHandle<OrderedHashSet> Allocate(
292 IsolateT* isolate, int capacity,
293 AllocationType allocation = AllocationType::kYoung);
294
295 static MaybeHandle<OrderedHashSet> AllocateEmpty(
296 Isolate* isolate, AllocationType allocation = AllocationType::kReadOnly);
297
298 static HeapObject GetEmpty(ReadOnlyRoots ro_roots);
299 static inline Handle<Map> GetMap(ReadOnlyRoots roots);
300 static inline bool Is(Handle<HeapObject> table);
301 static const int kPrefixSize = 0;
302
303 OBJECT_CONSTRUCTORS(OrderedHashSet, OrderedHashTable<OrderedHashSet, 1>);
304 };
305
306 class V8_EXPORT_PRIVATE OrderedHashMap
307 : public OrderedHashTable<OrderedHashMap, 2> {
308 using Base = OrderedHashTable<OrderedHashMap, 2>;
309
310 public:
311 DECL_CAST(OrderedHashMap)
312 DECL_PRINTER(OrderedHashMap)
313
314 // Returns a value if the OrderedHashMap contains the key, otherwise
315 // returns undefined.
316 static MaybeHandle<OrderedHashMap> Add(Isolate* isolate,
317 Handle<OrderedHashMap> table,
318 Handle<Object> key,
319 Handle<Object> value);
320
321 template <typename IsolateT>
322 static MaybeHandle<OrderedHashMap> Allocate(
323 IsolateT* isolate, int capacity,
324 AllocationType allocation = AllocationType::kYoung);
325
326 static MaybeHandle<OrderedHashMap> AllocateEmpty(
327 Isolate* isolate, AllocationType allocation = AllocationType::kReadOnly);
328
329 static MaybeHandle<OrderedHashMap> Rehash(Isolate* isolate,
330 Handle<OrderedHashMap> table,
331 int new_capacity);
332 static MaybeHandle<OrderedHashMap> Rehash(Isolate* isolate,
333 Handle<OrderedHashMap> table);
334
335 void SetEntry(InternalIndex entry, Object key, Object value);
336
337 Object ValueAt(InternalIndex entry);
338
339 // This takes and returns raw Address values containing tagged Object
340 // pointers because it is called via ExternalReference.
341 static Address GetHash(Isolate* isolate, Address raw_key);
342
343 static HeapObject GetEmpty(ReadOnlyRoots ro_roots);
344 static inline Handle<Map> GetMap(ReadOnlyRoots roots);
345 static inline bool Is(Handle<HeapObject> table);
346
347 static const int kValueOffset = 1;
348 static const int kPrefixSize = 0;
349
350 OBJECT_CONSTRUCTORS(OrderedHashMap, OrderedHashTable<OrderedHashMap, 2>);
351 };
352
353 // This is similar to the OrderedHashTable, except for the memory
354 // layout where we use byte instead of Smi. The max capacity of this
355 // is only 254, we transition to an OrderedHashTable beyond that
356 // limit.
357 //
358 // Each bucket and chain value is a byte long. The padding exists so
359 // that the DataTable entries start aligned. A bucket or chain value
360 // of 255 is used to denote an unknown entry.
361 //
362 // The prefix size is calculated as the kPrefixSize * kTaggedSize.
363 //
364 // Memory layout: [ Prefix ] [ Header ] [ Padding ] [ DataTable ] [ HashTable ]
365 // [ Chains ]
366 //
367 // The index are represented as bytes, on a 64 bit machine with
368 // kEntrySize = 1, capacity = 4 and entries = 2:
369 //
370 // [ 0 ] : Prefix
371 //
372 // Note: For the sake of brevity, the following start with index 0
373 // but, they actually start from kPrefixSize * kTaggedSize to
374 // account for the the prefix.
375 //
376 // [ Header ] :
377 // [0] : Number of elements
378 // [1] : Number of deleted elements
379 // [2] : Number of buckets
380 //
381 // [ Padding ] :
382 // [3 .. 7] : Padding
383 //
384 // [ DataTable ] :
385 // [8 .. 15] : Entry 1
386 // [16 .. 23] : Entry 2
387 // [24 .. 31] : empty
388 // [32 .. 39] : empty
389 //
390 // [ HashTable ] :
391 // [40] : First chain-link for bucket 1
392 // [41] : empty
393 //
394 // [ Chains ] :
395 // [42] : Next chain link for bucket 1
396 // [43] : empty
397 // [44] : empty
398 // [45] : empty
399 //
400 template <class Derived>
401 class SmallOrderedHashTable : public HeapObject {
402 public:
403 // Offset points to a relative location in the table
404 using Offset = int;
405
406 // ByteIndex points to a index in the table that needs to be
407 // converted to an Offset.
408 using ByteIndex = int;
409
410 void Initialize(Isolate* isolate, int capacity);
411
412 static Handle<Derived> Allocate(
413 Isolate* isolate, int capacity,
414 AllocationType allocation = AllocationType::kYoung);
415
416 // Returns a true if the OrderedHashTable contains the key
417 bool HasKey(Isolate* isolate, Handle<Object> key);
418
419 // Returns a true value if the table contains the key and
420 // the key has been deleted. This does not shrink the table.
421 static bool Delete(Isolate* isolate, Derived table, Object key);
422
423 // Returns an SmallOrderedHashTable (possibly |table|) with enough
424 // space to add at least one new element. Returns empty handle if
425 // we've already reached MaxCapacity.
426 static MaybeHandle<Derived> Grow(Isolate* isolate, Handle<Derived> table);
427
428 InternalIndex FindEntry(Isolate* isolate, Object key);
429 static Handle<Derived> Shrink(Isolate* isolate, Handle<Derived> table);
430
431 // Iterates only fields in the DataTable.
432 class BodyDescriptor;
433
434 // Returns total size in bytes required for a table of given
435 // capacity.
SizeFor(int capacity)436 static int SizeFor(int capacity) {
437 DCHECK_GE(capacity, kMinCapacity);
438 DCHECK_LE(capacity, kMaxCapacity);
439
440 int data_table_size = DataTableSizeFor(capacity);
441 int hash_table_size = capacity / kLoadFactor;
442 int chain_table_size = capacity;
443 int total_size = DataTableStartOffset() + data_table_size +
444 hash_table_size + chain_table_size;
445
446 return RoundUp(total_size, kTaggedSize);
447 }
448
449 // Returns the number elements that can fit into the allocated table.
Capacity()450 int Capacity() const {
451 int capacity = NumberOfBuckets() * kLoadFactor;
452 DCHECK_GE(capacity, kMinCapacity);
453 DCHECK_LE(capacity, kMaxCapacity);
454
455 return capacity;
456 }
457
458 // Returns the number elements that are present in the table.
NumberOfElements()459 int NumberOfElements() const {
460 int nof_elements = getByte(NumberOfElementsOffset(), 0);
461 DCHECK_LE(nof_elements, Capacity());
462
463 return nof_elements;
464 }
465
NumberOfDeletedElements()466 int NumberOfDeletedElements() const {
467 int nof_deleted_elements = getByte(NumberOfDeletedElementsOffset(), 0);
468 DCHECK_LE(nof_deleted_elements, Capacity());
469
470 return nof_deleted_elements;
471 }
472
NumberOfBuckets()473 int NumberOfBuckets() const { return getByte(NumberOfBucketsOffset(), 0); }
474
475 V8_INLINE Object KeyAt(InternalIndex entry) const;
476
IterateEntries()477 InternalIndex::Range IterateEntries() {
478 return InternalIndex::Range(UsedCapacity());
479 }
480
481 DECL_VERIFIER(SmallOrderedHashTable)
482
483 static const int kMinCapacity = 4;
484 static const byte kNotFound = 0xFF;
485
486 // We use the value 255 to indicate kNotFound for chain and bucket
487 // values, which means that this value can't be used a valid
488 // index.
489 static const int kMaxCapacity = 254;
490 STATIC_ASSERT(kMaxCapacity < kNotFound);
491
492 // The load factor is used to derive the number of buckets from
493 // capacity during Allocation. We also depend on this to calaculate
494 // the capacity from number of buckets after allocation. If we
495 // decide to change kLoadFactor to something other than 2, capacity
496 // should be stored as another field of this object.
497 static const int kLoadFactor = 2;
498
499 // Our growth strategy involves doubling the capacity until we reach
500 // kMaxCapacity, but since the kMaxCapacity is always less than 256,
501 // we will never fully utilize this table. We special case for 256,
502 // by changing the new capacity to be kMaxCapacity in
503 // SmallOrderedHashTable::Grow.
504 static const int kGrowthHack = 256;
505
506 protected:
507 static Handle<Derived> Rehash(Isolate* isolate, Handle<Derived> table,
508 int new_capacity);
509
510 void SetDataEntry(int entry, int relative_index, Object value);
511
512 // TODO(gsathya): Calculate all the various possible values for this
513 // at compile time since capacity can only be 4 different values.
GetBucketsStartOffset()514 Offset GetBucketsStartOffset() const {
515 int capacity = Capacity();
516 int data_table_size = DataTableSizeFor(capacity);
517 return DataTableStartOffset() + data_table_size;
518 }
519
GetHashTableStartAddress(int capacity)520 Address GetHashTableStartAddress(int capacity) const {
521 return field_address(DataTableStartOffset() + DataTableSizeFor(capacity));
522 }
523
SetFirstEntry(int bucket,byte value)524 void SetFirstEntry(int bucket, byte value) {
525 DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets());
526 setByte(GetBucketsStartOffset(), bucket, value);
527 }
528
GetFirstEntry(int bucket)529 int GetFirstEntry(int bucket) const {
530 DCHECK_LE(static_cast<unsigned>(bucket), NumberOfBuckets());
531 return getByte(GetBucketsStartOffset(), bucket);
532 }
533
534 // TODO(gsathya): Calculate all the various possible values for this
535 // at compile time since capacity can only be 4 different values.
GetChainTableOffset()536 Offset GetChainTableOffset() const {
537 int nof_buckets = NumberOfBuckets();
538 int capacity = nof_buckets * kLoadFactor;
539 DCHECK_EQ(Capacity(), capacity);
540
541 int data_table_size = DataTableSizeFor(capacity);
542 int hash_table_size = nof_buckets;
543 return DataTableStartOffset() + data_table_size + hash_table_size;
544 }
545
SetNextEntry(int entry,int next_entry)546 void SetNextEntry(int entry, int next_entry) {
547 DCHECK_LT(static_cast<unsigned>(entry), Capacity());
548 DCHECK_GE(static_cast<unsigned>(next_entry), 0);
549 DCHECK(next_entry <= Capacity() || next_entry == kNotFound);
550 setByte(GetChainTableOffset(), entry, next_entry);
551 }
552
GetNextEntry(int entry)553 int GetNextEntry(int entry) const {
554 DCHECK_LT(entry, Capacity());
555 return getByte(GetChainTableOffset(), entry);
556 }
557
558 V8_INLINE Object GetDataEntry(int entry, int relative_index);
559
HashToBucket(int hash)560 int HashToBucket(int hash) const { return hash & (NumberOfBuckets() - 1); }
561
HashToFirstEntry(int hash)562 int HashToFirstEntry(int hash) const {
563 int bucket = HashToBucket(hash);
564 int entry = GetFirstEntry(bucket);
565 DCHECK(entry < Capacity() || entry == kNotFound);
566 return entry;
567 }
568
SetNumberOfBuckets(int num)569 void SetNumberOfBuckets(int num) { setByte(NumberOfBucketsOffset(), 0, num); }
570
SetNumberOfElements(int num)571 void SetNumberOfElements(int num) {
572 DCHECK_LE(static_cast<unsigned>(num), Capacity());
573 setByte(NumberOfElementsOffset(), 0, num);
574 }
575
SetNumberOfDeletedElements(int num)576 void SetNumberOfDeletedElements(int num) {
577 DCHECK_LE(static_cast<unsigned>(num), Capacity());
578 setByte(NumberOfDeletedElementsOffset(), 0, num);
579 }
580
PrefixOffset()581 static constexpr Offset PrefixOffset() { return kHeaderSize; }
582
NumberOfElementsOffset()583 static constexpr Offset NumberOfElementsOffset() {
584 return PrefixOffset() + (Derived::kPrefixSize * kTaggedSize);
585 }
586
NumberOfDeletedElementsOffset()587 static constexpr Offset NumberOfDeletedElementsOffset() {
588 return NumberOfElementsOffset() + kOneByteSize;
589 }
590
NumberOfBucketsOffset()591 static constexpr Offset NumberOfBucketsOffset() {
592 return NumberOfDeletedElementsOffset() + kOneByteSize;
593 }
594
PaddingOffset()595 static constexpr Offset PaddingOffset() {
596 return NumberOfBucketsOffset() + kOneByteSize;
597 }
598
PaddingSize()599 static constexpr size_t PaddingSize() {
600 return RoundUp<kTaggedSize>(PaddingOffset()) - PaddingOffset();
601 }
602
DataTableStartOffset()603 static constexpr Offset DataTableStartOffset() {
604 return PaddingOffset() + PaddingSize();
605 }
606
DataTableSizeFor(int capacity)607 static constexpr int DataTableSizeFor(int capacity) {
608 return capacity * Derived::kEntrySize * kTaggedSize;
609 }
610
611 // This is used for accessing the non |DataTable| part of the
612 // structure.
getByte(Offset offset,ByteIndex index)613 byte getByte(Offset offset, ByteIndex index) const {
614 DCHECK(offset < DataTableStartOffset() ||
615 offset >= GetBucketsStartOffset());
616 return ReadField<byte>(offset + (index * kOneByteSize));
617 }
618
setByte(Offset offset,ByteIndex index,byte value)619 void setByte(Offset offset, ByteIndex index, byte value) {
620 DCHECK(offset < DataTableStartOffset() ||
621 offset >= GetBucketsStartOffset());
622 WriteField<byte>(offset + (index * kOneByteSize), value);
623 }
624
GetDataEntryOffset(int entry,int relative_index)625 Offset GetDataEntryOffset(int entry, int relative_index) const {
626 DCHECK_LT(entry, Capacity());
627 int offset_in_datatable = entry * Derived::kEntrySize * kTaggedSize;
628 int offset_in_entry = relative_index * kTaggedSize;
629 return DataTableStartOffset() + offset_in_datatable + offset_in_entry;
630 }
631
UsedCapacity()632 int UsedCapacity() const {
633 int used = NumberOfElements() + NumberOfDeletedElements();
634 DCHECK_LE(used, Capacity());
635
636 return used;
637 }
638
639 private:
640 friend class OrderedHashMapHandler;
641 friend class OrderedHashSetHandler;
642 friend class OrderedNameDictionaryHandler;
643 friend class CodeStubAssembler;
644
645 OBJECT_CONSTRUCTORS(SmallOrderedHashTable, HeapObject);
646 };
647
648 class SmallOrderedHashSet : public SmallOrderedHashTable<SmallOrderedHashSet> {
649 public:
650 DECL_CAST(SmallOrderedHashSet)
651
652 DECL_PRINTER(SmallOrderedHashSet)
653 EXPORT_DECL_VERIFIER(SmallOrderedHashSet)
654
655 static const int kKeyIndex = 0;
656 static const int kEntrySize = 1;
657 static const int kPrefixSize = 0;
658
659 // Adds |value| to |table|, if the capacity isn't enough, a new
660 // table is created. The original |table| is returned if there is
661 // capacity to store |value| otherwise the new table is returned.
662 V8_EXPORT_PRIVATE static MaybeHandle<SmallOrderedHashSet> Add(
663 Isolate* isolate, Handle<SmallOrderedHashSet> table, Handle<Object> key);
664 V8_EXPORT_PRIVATE static bool Delete(Isolate* isolate,
665 SmallOrderedHashSet table, Object key);
666 V8_EXPORT_PRIVATE bool HasKey(Isolate* isolate, Handle<Object> key);
667
668 static inline bool Is(Handle<HeapObject> table);
669 static inline Handle<Map> GetMap(ReadOnlyRoots roots);
670 static Handle<SmallOrderedHashSet> Rehash(Isolate* isolate,
671 Handle<SmallOrderedHashSet> table,
672 int new_capacity);
673 OBJECT_CONSTRUCTORS(SmallOrderedHashSet,
674 SmallOrderedHashTable<SmallOrderedHashSet>);
675 };
676
677 STATIC_ASSERT(kSmallOrderedHashSetMinCapacity ==
678 SmallOrderedHashSet::kMinCapacity);
679
680 class SmallOrderedHashMap : public SmallOrderedHashTable<SmallOrderedHashMap> {
681 public:
682 DECL_CAST(SmallOrderedHashMap)
683
684 DECL_PRINTER(SmallOrderedHashMap)
685 EXPORT_DECL_VERIFIER(SmallOrderedHashMap)
686
687 static const int kKeyIndex = 0;
688 static const int kValueIndex = 1;
689 static const int kEntrySize = 2;
690 static const int kPrefixSize = 0;
691
692 // Adds |value| to |table|, if the capacity isn't enough, a new
693 // table is created. The original |table| is returned if there is
694 // capacity to store |value| otherwise the new table is returned.
695 V8_EXPORT_PRIVATE static MaybeHandle<SmallOrderedHashMap> Add(
696 Isolate* isolate, Handle<SmallOrderedHashMap> table, Handle<Object> key,
697 Handle<Object> value);
698 V8_EXPORT_PRIVATE static bool Delete(Isolate* isolate,
699 SmallOrderedHashMap table, Object key);
700 V8_EXPORT_PRIVATE bool HasKey(Isolate* isolate, Handle<Object> key);
701 static inline bool Is(Handle<HeapObject> table);
702 static inline Handle<Map> GetMap(ReadOnlyRoots roots);
703
704 static Handle<SmallOrderedHashMap> Rehash(Isolate* isolate,
705 Handle<SmallOrderedHashMap> table,
706 int new_capacity);
707
708 OBJECT_CONSTRUCTORS(SmallOrderedHashMap,
709 SmallOrderedHashTable<SmallOrderedHashMap>);
710 };
711
712 STATIC_ASSERT(kSmallOrderedHashMapMinCapacity ==
713 SmallOrderedHashMap::kMinCapacity);
714
715 // TODO(gsathya): Rename this to OrderedHashTable, after we rename
716 // OrderedHashTable to LargeOrderedHashTable. Also set up a
717 // OrderedHashSetBase class as a base class for the two tables and use
718 // that instead of a HeapObject here.
719 template <class SmallTable, class LargeTable>
EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)720 class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE) OrderedHashTableHandler {
721 public:
722 using Entry = int;
723
724 static MaybeHandle<HeapObject> Allocate(Isolate* isolate, int capacity);
725 static bool Delete(Isolate* isolate, Handle<HeapObject> table,
726 Handle<Object> key);
727 static bool HasKey(Isolate* isolate, Handle<HeapObject> table,
728 Handle<Object> key);
729
730 // TODO(gsathya): Move this to OrderedHashTable
731 static const int OrderedHashTableMinSize =
732 SmallOrderedHashTable<SmallTable>::kGrowthHack << 1;
733 };
734
735 extern template class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
736 OrderedHashTableHandler<SmallOrderedHashMap, OrderedHashMap>;
737
738 class V8_EXPORT_PRIVATE OrderedHashMapHandler
739 : public OrderedHashTableHandler<SmallOrderedHashMap, OrderedHashMap> {
740 public:
741 static MaybeHandle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table,
742 Handle<Object> key, Handle<Object> value);
743 static MaybeHandle<OrderedHashMap> AdjustRepresentation(
744 Isolate* isolate, Handle<SmallOrderedHashMap> table);
745 };
746
747 extern template class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
748 OrderedHashTableHandler<SmallOrderedHashSet, OrderedHashSet>;
749
750 class V8_EXPORT_PRIVATE OrderedHashSetHandler
751 : public OrderedHashTableHandler<SmallOrderedHashSet, OrderedHashSet> {
752 public:
753 static MaybeHandle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table,
754 Handle<Object> key);
755 static MaybeHandle<OrderedHashSet> AdjustRepresentation(
756 Isolate* isolate, Handle<SmallOrderedHashSet> table);
757 };
758
759 class V8_EXPORT_PRIVATE OrderedNameDictionary
760 : public OrderedHashTable<OrderedNameDictionary, 3> {
761 using Base = OrderedHashTable<OrderedNameDictionary, 3>;
762
763 public:
764 DECL_CAST(OrderedNameDictionary)
765 DECL_PRINTER(OrderedNameDictionary)
766
767 template <typename IsolateT>
768 static MaybeHandle<OrderedNameDictionary> Add(
769 IsolateT* isolate, Handle<OrderedNameDictionary> table, Handle<Name> key,
770 Handle<Object> value, PropertyDetails details);
771
772 void SetEntry(InternalIndex entry, Object key, Object value,
773 PropertyDetails details);
774
775 template <typename IsolateT>
776 InternalIndex FindEntry(IsolateT* isolate, Object key);
777
778 // This is to make the interfaces of NameDictionary::FindEntry and
779 // OrderedNameDictionary::FindEntry compatible.
780 // TODO(emrich) clean this up: NameDictionary uses Handle<Object>
781 // for FindEntry keys due to its Key typedef, but that's also used
782 // for adding, where we do need handles.
783 template <typename IsolateT>
FindEntry(IsolateT * isolate,Handle<Object> key)784 InternalIndex FindEntry(IsolateT* isolate, Handle<Object> key) {
785 return FindEntry(isolate, *key);
786 }
787
788 static Handle<OrderedNameDictionary> DeleteEntry(
789 Isolate* isolate, Handle<OrderedNameDictionary> table,
790 InternalIndex entry);
791
792 template <typename IsolateT>
793 static MaybeHandle<OrderedNameDictionary> Allocate(
794 IsolateT* isolate, int capacity,
795 AllocationType allocation = AllocationType::kYoung);
796
797 static MaybeHandle<OrderedNameDictionary> AllocateEmpty(
798 Isolate* isolate, AllocationType allocation = AllocationType::kReadOnly);
799
800 template <typename IsolateT>
801 static MaybeHandle<OrderedNameDictionary> Rehash(
802 IsolateT* isolate, Handle<OrderedNameDictionary> table, int new_capacity);
803
804 // Returns the value for entry.
805 inline Object ValueAt(InternalIndex entry);
806
807 // Like KeyAt, but casts to Name
808 inline Name NameAt(InternalIndex entry);
809
810 // Set the value for entry.
811 inline void ValueAtPut(InternalIndex entry, Object value);
812
813 // Returns the property details for the property at entry.
814 inline PropertyDetails DetailsAt(InternalIndex entry);
815
816 // Set the details for entry.
817 inline void DetailsAtPut(InternalIndex entry, PropertyDetails value);
818
819 inline void SetHash(int hash);
820 inline int Hash();
821
822 static HeapObject GetEmpty(ReadOnlyRoots ro_roots);
823 static inline Handle<Map> GetMap(ReadOnlyRoots roots);
824 static inline bool Is(Handle<HeapObject> table);
825
826 static const int kValueOffset = 1;
827 static const int kPropertyDetailsOffset = 2;
828 static const int kPrefixSize = 1;
829
HashIndex()830 static constexpr int HashIndex() { return PrefixIndex(); }
831
832 static const bool kIsOrderedDictionaryType = true;
833
834 OBJECT_CONSTRUCTORS(OrderedNameDictionary,
835 OrderedHashTable<OrderedNameDictionary, 3>);
836 };
837
838 extern template class EXPORT_TEMPLATE_DECLARE(V8_EXPORT_PRIVATE)
839 OrderedHashTableHandler<SmallOrderedNameDictionary, OrderedNameDictionary>;
840
841 class V8_EXPORT_PRIVATE OrderedNameDictionaryHandler
842 : public OrderedHashTableHandler<SmallOrderedNameDictionary,
843 OrderedNameDictionary> {
844 public:
845 static MaybeHandle<HeapObject> Add(Isolate* isolate, Handle<HeapObject> table,
846 Handle<Name> key, Handle<Object> value,
847 PropertyDetails details);
848 static Handle<HeapObject> Shrink(Isolate* isolate, Handle<HeapObject> table);
849
850 static Handle<HeapObject> DeleteEntry(Isolate* isolate,
851 Handle<HeapObject> table,
852 InternalIndex entry);
853 static InternalIndex FindEntry(Isolate* isolate, HeapObject table, Name key);
854 static void SetEntry(HeapObject table, InternalIndex entry, Object key,
855 Object value, PropertyDetails details);
856
857 // Returns the value for entry.
858 static Object ValueAt(HeapObject table, InternalIndex entry);
859
860 // Set the value for entry.
861 static void ValueAtPut(HeapObject table, InternalIndex entry, Object value);
862
863 // Returns the property details for the property at entry.
864 static PropertyDetails DetailsAt(HeapObject table, InternalIndex entry);
865
866 // Set the details for entry.
867 static void DetailsAtPut(HeapObject table, InternalIndex entry,
868 PropertyDetails value);
869
870 static Name KeyAt(HeapObject table, InternalIndex entry);
871
872 static void SetHash(HeapObject table, int hash);
873 static int Hash(HeapObject table);
874
875 static int NumberOfElements(HeapObject table);
876 static int Capacity(HeapObject table);
877
878 protected:
879 static MaybeHandle<OrderedNameDictionary> AdjustRepresentation(
880 Isolate* isolate, Handle<SmallOrderedNameDictionary> table);
881 };
882
883 class SmallOrderedNameDictionary
884 : public SmallOrderedHashTable<SmallOrderedNameDictionary> {
885 public:
886 DECL_CAST(SmallOrderedNameDictionary)
887
888 DECL_PRINTER(SmallOrderedNameDictionary)
889 DECL_VERIFIER(SmallOrderedNameDictionary)
890
891 // Returns the value for entry.
892 inline Object ValueAt(InternalIndex entry);
893
894 static Handle<SmallOrderedNameDictionary> Rehash(
895 Isolate* isolate, Handle<SmallOrderedNameDictionary> table,
896 int new_capacity);
897
898 V8_EXPORT_PRIVATE static Handle<SmallOrderedNameDictionary> DeleteEntry(
899 Isolate* isolate, Handle<SmallOrderedNameDictionary> table,
900 InternalIndex entry);
901
902 // Set the value for entry.
903 inline void ValueAtPut(InternalIndex entry, Object value);
904
905 // Returns the property details for the property at entry.
906 inline PropertyDetails DetailsAt(InternalIndex entry);
907
908 // Set the details for entry.
909 inline void DetailsAtPut(InternalIndex entry, PropertyDetails value);
910
911 inline void SetHash(int hash);
912 inline int Hash();
913
914 static const int kKeyIndex = 0;
915 static const int kValueIndex = 1;
916 static const int kPropertyDetailsIndex = 2;
917 static const int kEntrySize = 3;
918 static const int kPrefixSize = 1;
919
920 // Adds |value| to |table|, if the capacity isn't enough, a new
921 // table is created. The original |table| is returned if there is
922 // capacity to store |value| otherwise the new table is returned.
923 V8_EXPORT_PRIVATE static MaybeHandle<SmallOrderedNameDictionary> Add(
924 Isolate* isolate, Handle<SmallOrderedNameDictionary> table,
925 Handle<Name> key, Handle<Object> value, PropertyDetails details);
926
927 V8_EXPORT_PRIVATE void SetEntry(InternalIndex entry, Object key, Object value,
928 PropertyDetails details);
929
930 static inline Handle<Map> GetMap(ReadOnlyRoots roots);
931 static inline bool Is(Handle<HeapObject> table);
932
933 OBJECT_CONSTRUCTORS(SmallOrderedNameDictionary,
934 SmallOrderedHashTable<SmallOrderedNameDictionary>);
935 };
936
937 } // namespace internal
938 } // namespace v8
939
940 #include "src/objects/object-macros-undef.h"
941
942 #endif // V8_OBJECTS_ORDERED_HASH_TABLE_H_
943