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