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