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