/* * Copyright (c) 2021 Huawei Device Co., Ltd. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "ecmascript/js_object-inl.h" #include "libpandabase/utils/bit_utils.h" #include "linked_hash_table-inl.h" #include "object_factory.h" namespace panda::ecmascript { template Derived *LinkedHashTable::Create(const JSThread *thread, int numberOfElements) { ASSERT_PRINT(numberOfElements > 0, "size must be a non-negative integer"); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); auto capacity = static_cast(numberOfElements); ASSERT_PRINT(helpers::math::IsPowerOfTwo(capacity), "capacity must be pow of '2'"); int length = ELEMENTS_START_INDEX + numberOfElements + numberOfElements * (HashObject::ENTRY_SIZE + 1); auto table = static_cast(*factory->NewTaggedArray(length)); table->SetNumberOfElements(thread, 0); table->SetNumberOfDeletedElements(thread, 0); table->SetCapacity(thread, capacity); return table; } template Derived *LinkedHashTable::Insert(const JSThread *thread, const JSHandle &table, const JSHandle &key, const JSHandle &value) { ASSERT(IsKey(key.GetTaggedValue())); int hash = HashObject::Hash(key.GetTaggedValue()); int entry = table->FindElement(key.GetTaggedValue()); if (entry != -1) { table->SetValue(thread, entry, value.GetTaggedValue()); return Derived::Cast(*table); } Derived *newTable = GrowCapacity(thread, table); int bucket = newTable->HashToBucket(hash); entry = newTable->NumberOfElements() + newTable->NumberOfDeletedElements(); newTable->InsertNewEntry(thread, bucket, entry); newTable->SetKey(thread, entry, key.GetTaggedValue()); newTable->SetValue(thread, entry, value.GetTaggedValue()); newTable->SetNumberOfElements(thread, newTable->NumberOfElements() + 1); return newTable; } template Derived *LinkedHashTable::InsertWeakRef(const JSThread *thread, const JSHandle &table, const JSHandle &key, const JSHandle &value) { ASSERT(IsKey(key.GetTaggedValue())); int hash = HashObject::Hash(key.GetTaggedValue()); int entry = table->FindElement(key.GetTaggedValue()); if (entry != -1) { table->SetValue(thread, entry, value.GetTaggedValue()); return Derived::Cast(*table); } Derived *newTable = GrowCapacity(thread, table); int bucket = newTable->HashToBucket(hash); entry = newTable->NumberOfElements() + newTable->NumberOfDeletedElements(); newTable->InsertNewEntry(thread, bucket, entry); JSTaggedValue weakKey(key->CreateAndGetWeakRef()); newTable->SetKey(thread, entry, weakKey); // The ENTRY_VALUE_INDEX of LinkedHashSet is 0. SetValue will cause the overwitten key. if (std::is_same_v) { newTable->SetValue(thread, entry, value.GetTaggedValue()); } newTable->SetNumberOfElements(thread, newTable->NumberOfElements() + 1); return newTable; } template void LinkedHashTable::Rehash(const JSThread *thread, Derived *newTable) { ASSERT_PRINT(newTable != nullptr && newTable->Capacity() > NumberOfElements(), "can not rehash to new table"); // Rehash elements to new table int numberOfAllElements = NumberOfElements() + NumberOfDeletedElements(); int desEntry = 0; int currentDeletedElements = 0; SetNextTable(thread, JSTaggedValue(newTable)); for (int i = 0; i < numberOfAllElements; i++) { int fromIndex = EntryToIndex(i); JSTaggedValue key = GetElement(fromIndex); if (key.IsHole()) { // store num_of_deleted_element before entry i; it will be used when iterator update. currentDeletedElements++; SetDeletedNum(thread, i, JSTaggedValue(currentDeletedElements)); continue; } if (key.IsWeak()) { // If the key is a weak reference, we use the weak referent to calculate the new index in the new table. key.RemoveWeakTag(); } int bucket = newTable->HashToBucket(HashObject::Hash(key)); newTable->InsertNewEntry(thread, bucket, desEntry); int desIndex = newTable->EntryToIndex(desEntry); for (int j = 0; j < HashObject::ENTRY_SIZE; j++) { newTable->SetElement(thread, desIndex + j, GetElement(fromIndex + j)); } desEntry++; } newTable->SetNumberOfElements(thread, NumberOfElements()); newTable->SetNumberOfDeletedElements(thread, 0); } template Derived *LinkedHashTable::GrowCapacity(const JSThread *thread, const JSHandle &table, int numberOfAddedElements) { if (table->HasSufficientCapacity(numberOfAddedElements)) { return Derived::Cast(*table); } int newCapacity = ComputeCapacity(table->NumberOfElements() + numberOfAddedElements); Derived *newTable = Create(thread, newCapacity); table->Rehash(thread, newTable); return newTable; } template Derived *LinkedHashTable::Remove(const JSThread *thread, const JSHandle &table, const JSHandle &key) { int entry = table->FindElement(key.GetTaggedValue()); if (entry == -1) { return Derived::Cast(*table); } table->RemoveEntry(thread, entry); return Shrink(thread, table); } template Derived *LinkedHashTable::Shrink(const JSThread *thread, const JSHandle &table, int additionalCapacity) { int newCapacity = ComputeCapacityWithShrink(table->Capacity(), table->NumberOfElements() + additionalCapacity); if (newCapacity == table->Capacity()) { return Derived::Cast(*table); } Derived *newTable = Create(thread, newCapacity); table->Rehash(thread, newTable); return newTable; } // LinkedHashMap JSTaggedValue LinkedHashMap::Create(const JSThread *thread, int numberOfElements) { return JSTaggedValue(LinkedHashTable::Create(thread, numberOfElements)); } JSTaggedValue LinkedHashMap::Delete(const JSThread *thread, const JSHandle &obj, const JSHandle &key) { return JSTaggedValue(LinkedHashTable::Remove(thread, obj, key)); } JSTaggedValue LinkedHashMap::Set(const JSThread *thread, const JSHandle &obj, const JSHandle &key, const JSHandle &value) { return JSTaggedValue(LinkedHashTable::Insert(thread, obj, key, value)); } JSTaggedValue LinkedHashMap::SetWeakRef(const JSThread *thread, const JSHandle &obj, const JSHandle &key, const JSHandle &value) { return JSTaggedValue(LinkedHashTable::InsertWeakRef(thread, obj, key, value)); } JSTaggedValue LinkedHashMap::Get(JSTaggedValue key) const { int entry = FindElement(key); if (entry == -1) { return JSTaggedValue::Undefined(); } return GetValue(entry); } bool LinkedHashMap::Has(JSTaggedValue key) const { int entry = FindElement(key); return entry != -1; } void LinkedHashMap::Clear(const JSThread *thread) { int numberOfElements = NumberOfElements() + NumberOfDeletedElements(); for (int entry = 0; entry < numberOfElements; entry++) { SetKey(thread, entry, JSTaggedValue::Hole()); SetValue(thread, entry, JSTaggedValue::Hole()); } SetNumberOfElements(thread, 0); SetNumberOfDeletedElements(thread, numberOfElements); } JSTaggedValue LinkedHashMap::Shrink(const JSThread *thread, const JSHandle &table, int additionalCapacity) { return JSTaggedValue( LinkedHashTable::Shrink(thread, table, additionalCapacity)); } // LinkedHashSet JSTaggedValue LinkedHashSet::Create(const JSThread *thread, int numberOfElements) { return JSTaggedValue(LinkedHashTable::Create(thread, numberOfElements)); } JSTaggedValue LinkedHashSet::Delete(const JSThread *thread, const JSHandle &obj, const JSHandle &key) { return JSTaggedValue(LinkedHashTable::Remove(thread, obj, key)); } JSTaggedValue LinkedHashSet::Add(const JSThread *thread, const JSHandle &obj, const JSHandle &key) { return JSTaggedValue(LinkedHashTable::Insert(thread, obj, key, key)); } JSTaggedValue LinkedHashSet::AddWeakRef(const JSThread *thread, const JSHandle &obj, const JSHandle &key) { return JSTaggedValue(LinkedHashTable::InsertWeakRef(thread, obj, key, key)); } bool LinkedHashSet::Has(JSTaggedValue key) const { int entry = FindElement(key); return entry != -1; } void LinkedHashSet::Clear(const JSThread *thread) { int numberOfElements = NumberOfElements() + NumberOfDeletedElements(); for (int entry = 0; entry < numberOfElements; entry++) { SetKey(thread, entry, JSTaggedValue::Hole()); } SetNumberOfElements(thread, 0); SetNumberOfDeletedElements(thread, numberOfElements); } JSTaggedValue LinkedHashSet::Shrink(const JSThread *thread, const JSHandle &table, int additionalCapacity) { return JSTaggedValue( LinkedHashTable::Shrink(thread, table, additionalCapacity)); } int LinkedHashMapObject::Hash(JSTaggedValue key) { if (key.IsDouble() && key.GetDouble() == 0.0) { key = JSTaggedValue(0); } if (key.IsSymbol()) { auto symbolString = JSSymbol::Cast(key.GetHeapObject()); return static_cast(symbolString->GetHashField()).GetInt(); } if (key.IsString()) { auto keyString = reinterpret_cast(key.GetHeapObject()); return keyString->GetHashcode(); } if (key.IsECMAObject()) { int32_t hash = ECMAObject::Cast(key.GetHeapObject())->GetHash(); if (hash == 0) { uint64_t keyValue = key.GetRawData(); hash = GetHash32(reinterpret_cast(&keyValue), sizeof(keyValue) / sizeof(uint8_t)); ECMAObject::Cast(key.GetHeapObject())->SetHash(hash); } return hash; } // Int, Double, Special and HeapObject(except symbol and string) uint64_t keyValue = key.GetRawData(); return GetHash32(reinterpret_cast(&keyValue), sizeof(keyValue) / sizeof(uint8_t)); } } // namespace panda::ecmascript