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