• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #ifndef ECMASCRIPT_TAGGED_HASH_TABLE_H
17 #define ECMASCRIPT_TAGGED_HASH_TABLE_H
18 
19 #include <vector>
20 
21 #include "ecmascript/ecma_vm.h"
22 #include "ecmascript/js_handle.h"
23 #include "ecmascript/object_factory.h"
24 #include "ecmascript/tagged_array.h"
25 
26 namespace panda::ecmascript {
27 template<typename Derived>
28 class TaggedHashTable : public TaggedArray {
29 public:
EntriesCount()30     inline int EntriesCount() const
31     {
32         return Get(NUMBER_OF_ENTRIES_INDEX).GetInt();
33     }
34 
HoleEntriesCount()35     inline int HoleEntriesCount() const
36     {
37         return Get(NUMBER_OF_HOLE_ENTRIES_INDEX).GetInt();
38     }
39 
Size()40     inline int Size() const
41     {
42         return Get(SIZE_INDEX).GetInt();
43     }
44 
IncreaseEntries(const JSThread * thread)45     inline void IncreaseEntries(const JSThread *thread)
46     {
47         SetEntriesCount(thread, EntriesCount() + 1);
48     }
49 
50     inline void IncreaseHoleEntriesCount(const JSThread *thread, int number = 1)
51     {
52         SetEntriesCount(thread, EntriesCount() - number);
53         SetHoleEntriesCount(thread, HoleEntriesCount() + number);
54     }
55 
ComputeHashTableSize(uint32_t atLeastSize)56     inline static int ComputeHashTableSize(uint32_t atLeastSize)
57     {
58         //  increase size for hash-collision
59         uint32_t rawSize = atLeastSize + (atLeastSize >> 1UL);
60         int newSize = static_cast<int>(helpers::math::GetPowerOfTwoValue32(rawSize));
61         return (newSize > MIN_SIZE) ? newSize : MIN_SIZE;
62     }
63 
64     static JSHandle<Derived> GrowHashTable(const JSThread *thread, const JSHandle<Derived> &table,
65                                            int numOfAddedElements = 1)
66     {
67         if (!table->IsNeedGrowHashTable(numOfAddedElements)) {
68             // if deleted entries are more than half of the free entries, rehash to clear holes.
69             int freeSize = table->Size() - table->EntriesCount() - numOfAddedElements;
70             if (table->HoleEntriesCount() > freeSize / 2) { // 2: half
71                 int copyLength = Derived::GetEntryIndex(table->Size());
72                 JSHandle<Derived> copyTable(thread->GetEcmaVM()->GetFactory()->NewDictionaryArray(copyLength));
73                 copyTable->SetHashTableSize(thread, table->Size());
74                 table->Rehash(thread, *copyTable);
75                 return copyTable;
76             }
77             return table;
78         }
79         int newSize = Derived::ComputeCompactSize(table, ComputeHashTableSize(table->Size() + numOfAddedElements),
80             table->Size(), numOfAddedElements);
81         newSize = std::max(newSize, MIN_SHRINK_SIZE);
82         int length = Derived::GetEntryIndex(newSize);
83         JSHandle<Derived> newTable(thread->GetEcmaVM()->GetFactory()->NewDictionaryArray(length));
84         newTable->SetHashTableSize(thread, newSize);
85         table->Rehash(thread, *newTable);
86         return newTable;
87     }
88 
Create(const JSThread * thread,int entriesCount)89     static JSHandle<Derived> Create(const JSThread *thread, int entriesCount)
90     {
91         ASSERT_PRINT((entriesCount > 0), "the size must be greater than zero");
92         auto size = static_cast<uint32_t>(entriesCount);
93         ASSERT_PRINT(helpers::math::IsPowerOfTwo(static_cast<uint32_t>(entriesCount)), "the size must be power of two");
94 
95         int length = Derived::GetEntryIndex(entriesCount);
96 
97         JSHandle<Derived> table(thread->GetEcmaVM()->GetFactory()->NewDictionaryArray(length));
98         table->SetEntriesCount(thread, 0);
99         table->SetHoleEntriesCount(thread, 0);
100         table->SetHashTableSize(thread, size);
101         return table;
102     }
103 
Insert(const JSThread * thread,JSHandle<Derived> & table,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value)104     static JSHandle<Derived> Insert(const JSThread *thread, JSHandle<Derived> &table,
105                                     const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value)
106     {
107         int entry = table->FindEntry(key.GetTaggedValue());
108         if (entry != -1) {
109             table->SetValue(thread, entry, value.GetTaggedValue());
110             return table;
111         }
112 
113         // Make sure the key object has an identity hash code.
114         int32_t hash = static_cast<int32_t>(Derived::Hash(key.GetTaggedValue()));
115         JSHandle<Derived> newTable = GrowHashTable(thread, table);
116         newTable->AddElement(thread, newTable->FindInsertIndex(hash), key, value);
117         return newTable;
118     }
119 
Remove(const JSThread * thread,JSHandle<Derived> & table,const JSHandle<JSTaggedValue> & key)120     static JSHandle<Derived> Remove(const JSThread *thread, JSHandle<Derived> &table,
121                                     const JSHandle<JSTaggedValue> &key)
122     {
123         int entry = table->FindEntry(key.GetTaggedValue());
124         if (entry == -1) {
125             return table;
126         }
127 
128         table->RemoveElement(thread, entry);
129         return Derived::Shrink(thread, *table);
130     }
131 
RecalculateTableSize(int currentSize,int atLeastSize)132     inline static int RecalculateTableSize(int currentSize, int atLeastSize)
133     {
134         // When the filled entries is greater than a quart of currentSize
135         // it need not to shrink
136         if (atLeastSize > (currentSize / 4)) {  // 4 : quarter
137             return currentSize;
138         }
139         // Recalculate table size
140         int newSize = ComputeHashTableSize(atLeastSize);
141         ASSERT_PRINT(newSize > atLeastSize, "new size must greater than atLeastSize");
142         // Don't go lower than room for MIN_SHRINK_SIZE elements.
143         if (newSize < MIN_SHRINK_SIZE) {
144             return currentSize;
145         }
146         return newSize;
147     }
148 
Shrink(const JSThread * thread,const JSHandle<Derived> & table,int additionalSize)149     inline static JSHandle<Derived> Shrink(const JSThread *thread, const JSHandle<Derived> &table, int additionalSize)
150     {
151         int newSize = RecalculateTableSize(table->Size(), table->EntriesCount() + additionalSize);
152         if (newSize == table->Size()) {
153             return table;
154         }
155 
156         JSHandle<Derived> newTable = TaggedHashTable::Create(thread, newSize);
157 
158         table->Rehash(thread, *newTable);
159         return newTable;
160     }
161 
IsNeedGrowHashTable(int numOfAddEntries)162     bool IsNeedGrowHashTable(int numOfAddEntries)
163     {
164         int entriesCount = EntriesCount();
165         int currentSize = Size();
166         int numberFilled = entriesCount + numOfAddEntries;
167         // needn't to grow table: after adding number entries, table have half free entries.
168         const int halfFree = 2;
169         int neededFree = numberFilled / halfFree;
170         if (numberFilled + neededFree <= currentSize) {
171             return false;
172         }
173         return true;
174     }
175 
GetKey(int entry)176     JSTaggedValue GetKey(int entry) const
177     {
178         int index = Derived::GetKeyIndex(entry);
179         if (UNLIKELY((index < 0 || index > static_cast<int>(GetLength())))) {
180             return JSTaggedValue::Undefined();
181         }
182         return Get(index);
183     }
184 
GetValue(int entry)185     JSTaggedValue GetValue(int entry) const
186     {
187         int index = Derived::GetValueIndex(entry);
188         if (UNLIKELY((index < 0 || index > static_cast<int>(GetLength())))) {
189             return JSTaggedValue::Undefined();
190         }
191         return Get(index);
192     }
193 
GetAllKeys(const JSThread * thread,int offset,TaggedArray * keyArray)194     inline void GetAllKeys(const JSThread *thread, int offset, TaggedArray *keyArray) const
195     {
196         ASSERT_PRINT(offset + EntriesCount() <= static_cast<int>(keyArray->GetLength()),
197                      "keyArray size is not enough for dictionary");
198         int arrayIndex = 0;
199         int size = Size();
200         for (int hashIndex = 0; hashIndex < size; hashIndex++) {
201             JSTaggedValue key = GetKey(hashIndex);
202             if (!key.IsUndefined() && !key.IsHole()) {
203                 keyArray->Set(thread, arrayIndex + offset, key);
204                 arrayIndex++;
205             }
206         }
207     }
208 
GetAllKeysIntoVector(std::vector<JSTaggedValue> & vector)209     inline void GetAllKeysIntoVector(std::vector<JSTaggedValue> &vector) const
210     {
211         int capacity = Size();
212         for (int hashIndex = 0; hashIndex < capacity; hashIndex++) {
213             JSTaggedValue key = GetKey(hashIndex);
214             if (!key.IsUndefined() && !key.IsHole()) {
215                 vector.push_back(key);
216             }
217         }
218     }
219 
220     inline void Swap(const JSThread *thread, int src, int dst);
221 
222     // Find entry for key otherwise return -1.
FindEntry(const JSTaggedValue & key)223     inline int FindEntry(const JSTaggedValue &key)
224     {
225         int size = Size();
226         int count = 1;
227         JSTaggedValue keyValue;
228         int32_t hash = static_cast<int32_t>(Derived::Hash(key));
229 
230         for (uint32_t entry = GetFirstPosition(hash, size);; entry = GetNextPosition(entry, count++, size)) {
231             keyValue = GetKey(entry);
232             if (keyValue.IsHole()) {
233                 continue;
234             }
235             if (keyValue.IsUndefined()) {
236                 return -1;
237             }
238             if (Derived::IsMatch(key, keyValue)) {
239                 return entry;
240             }
241         }
242         return -1;
243     }
244 
FindEntry(const uint8_t * str,int strSize)245     inline int FindEntry(const uint8_t* str, int strSize)
246     {
247         int size = Size();
248         int count = 1;
249         JSTaggedValue keyValue;
250         int32_t hash = static_cast<int32_t>(Derived::Hash(str, strSize));
251 
252         for (uint32_t entry = GetFirstPosition(hash, size);; entry = GetNextPosition(entry, count++, size)) {
253             keyValue = GetKey(entry);
254             if (keyValue.IsHole()) {
255                 continue;
256             }
257             if (keyValue.IsUndefined()) {
258                 return -1;
259             }
260             // keyValue defaults to ecmaString
261             if (Derived::IsMatch(str, strSize, keyValue)) {
262                 return entry;
263             }
264         }
265         return -1;
266     }
267 
FindInsertIndex(int hash)268     inline int FindInsertIndex(int hash)
269     {
270         int size = Size();
271         int count = 1;
272         // GrowHashTable will guarantee the hash table is never full.
273         for (uint32_t entry = GetFirstPosition(hash, size);; entry = GetNextPosition(entry, count++, size)) {
274             if (!IsKey(GetKey(entry))) {
275                 return entry;
276             }
277         }
278     }
279 
SetKey(const JSThread * thread,int entry,const JSTaggedValue & key)280     inline void SetKey(const JSThread *thread, int entry, const JSTaggedValue &key)
281     {
282         int index = Derived::GetKeyIndex(entry);
283         if (UNLIKELY(index < 0 || index > static_cast<int>(GetLength()))) {
284             return;
285         }
286         Set(thread, index, key);
287     }
288 
SetValue(const JSThread * thread,int entry,const JSTaggedValue & value)289     inline void SetValue(const JSThread *thread, int entry, const JSTaggedValue &value)
290     {
291         int index = Derived::GetValueIndex(entry);
292         if (UNLIKELY((index < 0 || index > static_cast<int>(GetLength())))) {
293             return;
294         }
295         Set(thread, index, value);
296     }
297 
298     // Rehash element to new_table
Rehash(const JSThread * thread,Derived * newTable)299     void Rehash(const JSThread *thread, Derived *newTable)
300     {
301         if ((newTable == nullptr) || (newTable->Size() < EntriesCount())) {
302             return;
303         }
304         int currentSize = this->Size();
305         // Rehash elements to new table
306         for (int i = 0; i < currentSize; i++) {
307             int fromIndex = Derived::GetKeyIndex(i);
308             JSTaggedValue k = this->GetKey(i);
309             if (!IsKey(k)) {
310                 continue;
311             }
312             int32_t hash = static_cast<int32_t>(Derived::Hash(k));
313             int insertionIndex = Derived::GetKeyIndex(newTable->FindInsertIndex(hash));
314             JSTaggedValue tv = Get(fromIndex);
315             newTable->Set(thread, insertionIndex, tv);
316             for (int j = 1; j < Derived::GetEntrySize(); j++) {
317                 tv = Get(fromIndex + j);
318                 newTable->Set(thread, insertionIndex + j, tv);
319             }
320         }
321         newTable->SetEntriesCount(thread, EntriesCount());
322         newTable->SetHoleEntriesCount(thread, 0);
323     }
324 
325     static constexpr int MIN_SHRINK_SIZE = 16;
326     static constexpr int MIN_SIZE = 4;
327     static constexpr int NUMBER_OF_ENTRIES_INDEX = 0;
328     static constexpr int NUMBER_OF_HOLE_ENTRIES_INDEX = 1;
329     static constexpr int SIZE_INDEX = 2;
330     static constexpr int TABLE_HEADER_SIZE = 3;
331 
332 protected:
IsKey(const JSTaggedValue & key)333     inline bool IsKey(const JSTaggedValue &key) const
334     {
335         return !key.IsHole() && !key.IsUndefined();
336     };
337 
GetFirstPosition(uint32_t hash,uint32_t size)338     inline static uint32_t GetFirstPosition(uint32_t hash, uint32_t size)
339     {
340         return hash & (size - 1);
341     }
342 
GetNextPosition(uint32_t last,uint32_t number,uint32_t size)343     inline static uint32_t GetNextPosition(uint32_t last, uint32_t number, uint32_t size)
344     {
345         return (last + (number * (number + 1)) / 2) & (size - 1);  // 2 : half
346     }
347 
SetEntriesCount(const JSThread * thread,int nof)348     inline void SetEntriesCount(const JSThread *thread, int nof)
349     {
350         Set(thread, NUMBER_OF_ENTRIES_INDEX, JSTaggedValue(nof));
351     }
352 
SetHoleEntriesCount(const JSThread * thread,int nod)353     inline void SetHoleEntriesCount(const JSThread *thread, int nod)
354     {
355         Set(thread, NUMBER_OF_HOLE_ENTRIES_INDEX, JSTaggedValue(nod));
356     }
357 
358     // Sets the size of the hash table.
SetHashTableSize(const JSThread * thread,int size)359     inline void SetHashTableSize(const JSThread *thread, int size)
360     {
361         Set(thread, SIZE_INDEX, JSTaggedValue(size));
362     }
363 
364     inline static int GetHeadSizeOfTable();
365     inline static int GetEntrySize();
366     inline static int GetKeyOffset();
367     inline static int GetValueOffset();
368 
AddElement(const JSThread * thread,int entry,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value)369     inline void AddElement(const JSThread *thread, int entry, const JSHandle<JSTaggedValue> &key,
370                            const JSHandle<JSTaggedValue> &value)
371     {
372         this->SetKey(thread, entry, key.GetTaggedValue());
373         this->SetValue(thread, entry, value.GetTaggedValue());
374         this->IncreaseEntries(thread);
375     }
376 
RemoveElement(const JSThread * thread,int entry)377     inline void RemoveElement(const JSThread *thread, int entry)
378     {
379         JSTaggedValue defaultValue(JSTaggedValue::Hole());
380         this->SetKey(thread, entry, defaultValue);
381         this->SetValue(thread, entry, defaultValue);
382         this->IncreaseHoleEntriesCount(thread);
383     }
384 };
385 
386 template<typename Derived>
387 class OrderTaggedHashTable : public TaggedHashTable<Derived> {
388 public:
389     using HashTableT = TaggedHashTable<Derived>;
Cast(TaggedObject * object)390     static Derived *Cast(TaggedObject *object)
391     {
392         return reinterpret_cast<Derived *>(object);
393     }
394 
395     // Attempt to shrink the table after deletion of key.
Shrink(const JSThread * thread,const JSHandle<Derived> & table)396     static JSHandle<Derived> Shrink(const JSThread *thread, const JSHandle<Derived> &table)
397     {
398         int index = table->GetNextEnumerationIndex();
399         JSHandle<Derived> newTable = HashTableT::Shrink(thread, table, 0);
400         newTable->SetNextEnumerationIndex(thread, index);
401         return newTable;
402     }
403 
404     static JSHandle<Derived> Create(const JSThread *thread, int numberOfElements = DEFAULT_ELEMENTS_NUMBER)
405     {
406         JSHandle<Derived> dict = HashTableT::Create(thread, numberOfElements);
407         dict->SetNextEnumerationIndex(thread, PropertyAttributes::INITIAL_PROPERTY_INDEX);
408         return dict;
409     }
410 
PutIfAbsent(const JSThread * thread,const JSHandle<Derived> & table,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value,const PropertyAttributes & metaData)411     static JSHandle<Derived> PutIfAbsent(const JSThread *thread, const JSHandle<Derived> &table,
412                                          const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value,
413                                          const PropertyAttributes &metaData)
414     {
415         /* no need to add key if exist */
416         int entry = table->FindEntry(key.GetTaggedValue());
417         if (entry != -1) {
418             return table;
419         }
420 
421         int enumIndex = table->NextEnumerationIndex(thread);
422         PropertyAttributes attr(metaData);
423         attr.SetDictionaryOrder(enumIndex);
424         attr.SetRepresentation(Representation::TAGGED);
425         // Check whether the table should be growed.
426         JSHandle<Derived> newTable = HashTableT::GrowHashTable(thread, table);
427 
428         // Compute the key object.
429         int32_t hash = static_cast<int32_t>(Derived::Hash(key.GetTaggedValue()));
430         entry = newTable->FindInsertIndex(hash);
431         newTable->SetEntry(thread, entry, key.GetTaggedValue(), value.GetTaggedValue(), attr);
432 
433         newTable->IncreaseEntries(thread);
434         newTable->SetNextEnumerationIndex(thread, enumIndex + 1);
435         return newTable;
436     }
437 
Put(const JSThread * thread,const JSHandle<Derived> & table,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value,const PropertyAttributes & metaData)438     static JSHandle<Derived> Put(const JSThread *thread, const JSHandle<Derived> &table,
439                                  const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value,
440                                  const PropertyAttributes &metaData)
441     {
442         int enumIndex = table->NextEnumerationIndex(thread);
443         PropertyAttributes attr(metaData);
444         attr.SetDictionaryOrder(enumIndex);
445         attr.SetRepresentation(Representation::TAGGED);
446         attr.SetDictTrackType(metaData.GetTrackType());
447         int entry = table->FindEntry(key.GetTaggedValue());
448         if (entry != -1) {
449             table->SetEntry(thread, entry, key.GetTaggedValue(), value.GetTaggedValue(), attr);
450             return table;
451         }
452 
453         // Check whether the table should be extended.
454         JSHandle<Derived> newTable = HashTableT::GrowHashTable(thread, table);
455 
456         // Compute the key object.
457         int hash = Derived::Hash(key.GetTaggedValue());
458         entry = newTable->FindInsertIndex(hash);
459         newTable->SetEntry(thread, entry, key.GetTaggedValue(), value.GetTaggedValue(), attr);
460 
461         newTable->IncreaseEntries(thread);
462         newTable->SetNextEnumerationIndex(thread, enumIndex + 1);
463         return newTable;
464     }
Remove(const JSThread * thread,const JSHandle<Derived> & table,int entry)465     static JSHandle<Derived> Remove(const JSThread *thread, const JSHandle<Derived> &table, int entry)
466     {
467         if (!(table->IsKey(table->GetKey(entry)))) {
468             return table;
469         }
470         table->ClearEntry(thread, entry);
471         table->IncreaseHoleEntriesCount(thread);
472         return Shrink(thread, table);
473     }
474 
SetNextEnumerationIndex(const JSThread * thread,int index)475     inline void SetNextEnumerationIndex(const JSThread *thread, int index)
476     {
477         HashTableT::Set(thread, NEXT_ENUMERATION_INDEX, JSTaggedValue(index));
478     }
GetNextEnumerationIndex()479     inline int GetNextEnumerationIndex() const
480     {
481         return HashTableT::Get(NEXT_ENUMERATION_INDEX).GetInt();
482     }
483 
NextEnumerationIndex(const JSThread * thread)484     inline int NextEnumerationIndex(const JSThread *thread)
485     {
486         int index = GetNextEnumerationIndex();
487         auto table = Derived::Cast(this);
488 
489         if (!PropertyAttributes::IsValidIndex(index)) {
490             std::vector<int> indexOrder = GetEnumerationOrder();
491             int length = static_cast<int>(indexOrder.size());
492             for (int i = 0; i < length; i++) {
493                 int oldIndex = indexOrder[i];
494                 int enumIndex = PropertyAttributes::INITIAL_PROPERTY_INDEX + i;
495                 PropertyAttributes attr = table->GetAttributes(oldIndex);
496                 attr.SetDictionaryOrder(enumIndex);
497                 attr.SetRepresentation(Representation::TAGGED);
498                 table->SetAttributes(thread, oldIndex, attr);
499             }
500             index = PropertyAttributes::INITIAL_PROPERTY_INDEX + length;
501         }
502         return index;
503     }
504 
GetEnumerationOrder()505     inline std::vector<int> GetEnumerationOrder()
506     {
507         std::vector<int> result;
508         auto table = Derived::Cast(this);
509         int size = table->Size();
510         for (int i = 0; i < size; i++) {
511             if (table->IsKey(table->GetKey(i))) {
512                 result.push_back(i);
513             }
514         }
515         std::sort(result.begin(), result.end(), [table](int a, int b) {
516             PropertyAttributes attrA = table->GetAttributes(a);
517             PropertyAttributes attrB = table->GetAttributes(b);
518             return attrA.GetDictionaryOrder() < attrB.GetDictionaryOrder();
519         });
520         return result;
521     }
522 
ComputeCompactSize(const JSHandle<Derived> & table,int computeHashTableSize,int tableSize,int addedElements)523     static int ComputeCompactSize([[maybe_unused]] const JSHandle<Derived> &table, int computeHashTableSize,
524         [[maybe_unused]] int tableSize, [[maybe_unused]] int addedElements)
525     {
526         return computeHashTableSize;
527     }
528 
529     static const int NEXT_ENUMERATION_INDEX = HashTableT::SIZE_INDEX + 1;
530     static const int DEFAULT_ELEMENTS_NUMBER = 128;
531     static constexpr int TABLE_HEADER_SIZE = 4;
532 };
533 }  // namespace panda::ecmascript
534 #endif  // ECMASCRIPT_NEW_HASH_TABLE_H
535