• 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         int entry = table->FindEntry(key.GetTaggedValue());
106         if (entry != -1) {
107             table->SetValue(thread, entry, value.GetTaggedValue());
108             return table;
109         }
110 
111         // Make sure the key object has an identity hash code.
112         int32_t hash = static_cast<int32_t>(Derived::Hash(key.GetTaggedValue()));
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 
FindEntry(const uint8_t * str,int strSize)243     inline int FindEntry(const uint8_t* str, int strSize)
244     {
245         int size = Size();
246         int count = 1;
247         JSTaggedValue keyValue;
248         int32_t hash = static_cast<int32_t>(Derived::Hash(str, strSize));
249 
250         for (uint32_t entry = GetFirstPosition(hash, size);; entry = GetNextPosition(entry, count++, size)) {
251             keyValue = GetKey(entry);
252             if (keyValue.IsHole()) {
253                 continue;
254             }
255             if (keyValue.IsUndefined()) {
256                 return -1;
257             }
258             // keyValue defaults to ecmaString
259             if (Derived::IsMatch(str, strSize, keyValue)) {
260                 return entry;
261             }
262         }
263         return -1;
264     }
265 
FindInsertIndex(int hash)266     inline int FindInsertIndex(int hash)
267     {
268         int size = Size();
269         int count = 1;
270         // GrowHashTable will guarantee the hash table is never full.
271         for (uint32_t entry = GetFirstPosition(hash, size);; entry = GetNextPosition(entry, count++, size)) {
272             if (!IsKey(GetKey(entry))) {
273                 return entry;
274             }
275         }
276     }
277 
SetKey(const JSThread * thread,int entry,const JSTaggedValue & key)278     inline void SetKey(const JSThread *thread, int entry, const JSTaggedValue &key)
279     {
280         int index = Derived::GetKeyIndex(entry);
281         if (UNLIKELY(index < 0 || index > static_cast<int>(GetLength()))) {
282             return;
283         }
284         Set(thread, index, key);
285     }
286 
SetValue(const JSThread * thread,int entry,const JSTaggedValue & value)287     inline void SetValue(const JSThread *thread, int entry, const JSTaggedValue &value)
288     {
289         int index = Derived::GetValueIndex(entry);
290         if (UNLIKELY((index < 0 || index > static_cast<int>(GetLength())))) {
291             return;
292         }
293         Set(thread, index, value);
294     }
295 
296     static constexpr int MIN_SHRINK_SIZE = 16;
297     static constexpr int MIN_SIZE = 4;
298     static constexpr int NUMBER_OF_ENTRIES_INDEX = 0;
299     static constexpr int NUMBER_OF_HOLE_ENTRIES_INDEX = 1;
300     static constexpr int SIZE_INDEX = 2;
301     static constexpr int TABLE_HEADER_SIZE = 3;
302 
303 protected:
IsKey(const JSTaggedValue & key)304     inline bool IsKey(const JSTaggedValue &key) const
305     {
306         return !key.IsHole() && !key.IsUndefined();
307     };
308 
GetFirstPosition(uint32_t hash,uint32_t size)309     inline static uint32_t GetFirstPosition(uint32_t hash, uint32_t size)
310     {
311         return hash & (size - 1);
312     }
313 
GetNextPosition(uint32_t last,uint32_t number,uint32_t size)314     inline static uint32_t GetNextPosition(uint32_t last, uint32_t number, uint32_t size)
315     {
316         return (last + (number * (number + 1)) / 2) & (size - 1);  // 2 : half
317     }
318 
SetEntriesCount(const JSThread * thread,int nof)319     inline void SetEntriesCount(const JSThread *thread, int nof)
320     {
321         Set(thread, NUMBER_OF_ENTRIES_INDEX, JSTaggedValue(nof));
322     }
323 
SetHoleEntriesCount(const JSThread * thread,int nod)324     inline void SetHoleEntriesCount(const JSThread *thread, int nod)
325     {
326         Set(thread, NUMBER_OF_HOLE_ENTRIES_INDEX, JSTaggedValue(nod));
327     }
328 
329     // Sets the size of the hash table.
SetHashTableSize(const JSThread * thread,int size)330     inline void SetHashTableSize(const JSThread *thread, int size)
331     {
332         Set(thread, SIZE_INDEX, JSTaggedValue(size));
333     }
334 
335     inline static int GetHeadSizeOfTable();
336     inline static int GetEntrySize();
337     inline static int GetKeyOffset();
338     inline static int GetValueOffset();
339 
AddElement(const JSThread * thread,int entry,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value)340     inline void AddElement(const JSThread *thread, int entry, const JSHandle<JSTaggedValue> &key,
341                            const JSHandle<JSTaggedValue> &value)
342     {
343         this->SetKey(thread, entry, key.GetTaggedValue());
344         this->SetValue(thread, entry, value.GetTaggedValue());
345         this->IncreaseEntries(thread);
346     }
347 
RemoveElement(const JSThread * thread,int entry)348     inline void RemoveElement(const JSThread *thread, int entry)
349     {
350         JSTaggedValue defaultValue(JSTaggedValue::Hole());
351         this->SetKey(thread, entry, defaultValue);
352         this->SetValue(thread, entry, defaultValue);
353         this->IncreaseHoleEntriesCount(thread);
354     }
355 
356     // Rehash element to new_table
Rehash(const JSThread * thread,Derived * newTable)357     void Rehash(const JSThread *thread, Derived *newTable)
358     {
359         if ((newTable == nullptr) || (newTable->Size() < EntriesCount())) {
360             return;
361         }
362         int currentSize = this->Size();
363         // Rehash elements to new table
364         for (int i = 0; i < currentSize; i++) {
365             int fromIndex = Derived::GetKeyIndex(i);
366             JSTaggedValue k = this->GetKey(i);
367             if (!IsKey(k)) {
368                 continue;
369             }
370             int32_t hash = static_cast<int32_t>(Derived::Hash(k));
371             int insertionIndex = Derived::GetKeyIndex(newTable->FindInsertIndex(hash));
372             JSTaggedValue tv = Get(fromIndex);
373             newTable->Set(thread, insertionIndex, tv);
374             for (int j = 1; j < Derived::GetEntrySize(); j++) {
375                 tv = Get(fromIndex + j);
376                 newTable->Set(thread, insertionIndex + j, tv);
377             }
378         }
379         newTable->SetEntriesCount(thread, EntriesCount());
380         newTable->SetHoleEntriesCount(thread, 0);
381     }
382 };
383 
384 template<typename Derived>
385 class OrderTaggedHashTable : public TaggedHashTable<Derived> {
386 public:
387     using HashTableT = TaggedHashTable<Derived>;
Cast(TaggedObject * object)388     static Derived *Cast(TaggedObject *object)
389     {
390         return reinterpret_cast<Derived *>(object);
391     }
392 
393     // Attempt to shrink the table after deletion of key.
Shrink(const JSThread * thread,const JSHandle<Derived> & table)394     static JSHandle<Derived> Shrink(const JSThread *thread, const JSHandle<Derived> &table)
395     {
396         int index = table->GetNextEnumerationIndex();
397         JSHandle<Derived> newTable = HashTableT::Shrink(thread, table, 0);
398         newTable->SetNextEnumerationIndex(thread, index);
399         return newTable;
400     }
401 
402     static JSHandle<Derived> Create(const JSThread *thread, int numberOfElements = DEFAULT_ELEMENTS_NUMBER)
403     {
404         JSHandle<Derived> dict = HashTableT::Create(thread, numberOfElements);
405         dict->SetNextEnumerationIndex(thread, PropertyAttributes::INITIAL_PROPERTY_INDEX);
406         return dict;
407     }
408 
PutIfAbsent(const JSThread * thread,const JSHandle<Derived> & table,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value,const PropertyAttributes & metaData)409     static JSHandle<Derived> PutIfAbsent(const JSThread *thread, const JSHandle<Derived> &table,
410                                          const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value,
411                                          const PropertyAttributes &metaData)
412     {
413         /* no need to add key if exist */
414         int entry = table->FindEntry(key.GetTaggedValue());
415         if (entry != -1) {
416             return table;
417         }
418 
419         int enumIndex = table->NextEnumerationIndex(thread);
420         PropertyAttributes attr(metaData);
421         attr.SetDictionaryOrder(enumIndex);
422         attr.SetRepresentation(Representation::TAGGED);
423         // Check whether the table should be growed.
424         JSHandle<Derived> newTable = HashTableT::GrowHashTable(thread, table);
425 
426         // Compute the key object.
427         int32_t hash = static_cast<int32_t>(Derived::Hash(key.GetTaggedValue()));
428         entry = newTable->FindInsertIndex(hash);
429         newTable->SetEntry(thread, entry, key.GetTaggedValue(), value.GetTaggedValue(), attr);
430 
431         newTable->IncreaseEntries(thread);
432         newTable->SetNextEnumerationIndex(thread, enumIndex + 1);
433         return newTable;
434     }
435 
Put(const JSThread * thread,const JSHandle<Derived> & table,const JSHandle<JSTaggedValue> & key,const JSHandle<JSTaggedValue> & value,const PropertyAttributes & metaData)436     static JSHandle<Derived> Put(const JSThread *thread, const JSHandle<Derived> &table,
437                                  const JSHandle<JSTaggedValue> &key, const JSHandle<JSTaggedValue> &value,
438                                  const PropertyAttributes &metaData)
439     {
440         int enumIndex = table->NextEnumerationIndex(thread);
441         PropertyAttributes attr(metaData);
442         attr.SetDictionaryOrder(enumIndex);
443         attr.SetRepresentation(Representation::TAGGED);
444         int entry = table->FindEntry(key.GetTaggedValue());
445         if (entry != -1) {
446             table->SetEntry(thread, entry, key.GetTaggedValue(), value.GetTaggedValue(), attr);
447             return table;
448         }
449 
450         // Check whether the table should be extended.
451         JSHandle<Derived> newTable = HashTableT::GrowHashTable(thread, table);
452 
453         // Compute the key object.
454         int hash = Derived::Hash(key.GetTaggedValue());
455         entry = newTable->FindInsertIndex(hash);
456         newTable->SetEntry(thread, entry, key.GetTaggedValue(), value.GetTaggedValue(), attr);
457 
458         newTable->IncreaseEntries(thread);
459         newTable->SetNextEnumerationIndex(thread, enumIndex + 1);
460         return newTable;
461     }
Remove(const JSThread * thread,const JSHandle<Derived> & table,int entry)462     static JSHandle<Derived> Remove(const JSThread *thread, const JSHandle<Derived> &table, int entry)
463     {
464         if (!(table->IsKey(table->GetKey(entry)))) {
465             return table;
466         }
467         table->ClearEntry(thread, entry);
468         table->IncreaseHoleEntriesCount(thread);
469         return Shrink(thread, table);
470     }
471 
SetNextEnumerationIndex(const JSThread * thread,int index)472     inline void SetNextEnumerationIndex(const JSThread *thread, int index)
473     {
474         HashTableT::Set(thread, NEXT_ENUMERATION_INDEX, JSTaggedValue(index));
475     }
GetNextEnumerationIndex()476     inline int GetNextEnumerationIndex() const
477     {
478         return HashTableT::Get(NEXT_ENUMERATION_INDEX).GetInt();
479     }
480 
NextEnumerationIndex(const JSThread * thread)481     inline int NextEnumerationIndex(const JSThread *thread)
482     {
483         int index = GetNextEnumerationIndex();
484         auto table = Derived::Cast(this);
485 
486         if (!PropertyAttributes::IsValidIndex(index)) {
487             std::vector<int> indexOrder = GetEnumerationOrder();
488             int length = static_cast<int>(indexOrder.size());
489             for (int i = 0; i < length; i++) {
490                 int oldIndex = indexOrder[i];
491                 int enumIndex = PropertyAttributes::INITIAL_PROPERTY_INDEX + i;
492                 PropertyAttributes attr = table->GetAttributes(oldIndex);
493                 attr.SetDictionaryOrder(enumIndex);
494                 attr.SetRepresentation(Representation::TAGGED);
495                 table->SetAttributes(thread, oldIndex, attr);
496             }
497             index = PropertyAttributes::INITIAL_PROPERTY_INDEX + length;
498         }
499         return index;
500     }
501 
GetEnumerationOrder()502     inline std::vector<int> GetEnumerationOrder()
503     {
504         std::vector<int> result;
505         auto table = Derived::Cast(this);
506         int size = table->Size();
507         for (int i = 0; i < size; i++) {
508             if (table->IsKey(table->GetKey(i))) {
509                 result.push_back(i);
510             }
511         }
512         std::sort(result.begin(), result.end(), [table](int a, int b) {
513             PropertyAttributes attrA = table->GetAttributes(a);
514             PropertyAttributes attrB = table->GetAttributes(b);
515             return attrA.GetDictionaryOrder() < attrB.GetDictionaryOrder();
516         });
517         return result;
518     }
519 
ComputeCompactSize(const JSHandle<Derived> & table,int computeHashTableSize,int tableSize,int addedElements)520     static int ComputeCompactSize([[maybe_unused]] const JSHandle<Derived> &table, int computeHashTableSize,
521         [[maybe_unused]] int tableSize, [[maybe_unused]] int addedElements)
522     {
523         return computeHashTableSize;
524     }
525 
526     static const int NEXT_ENUMERATION_INDEX = HashTableT::SIZE_INDEX + 1;
527     static const int DEFAULT_ELEMENTS_NUMBER = 128;
528     static constexpr int TABLE_HEADER_SIZE = 4;
529 };
530 }  // namespace panda::ecmascript
531 #endif  // ECMASCRIPT_NEW_HASH_TABLE_H
532