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