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