/* * Copyright (c) 2022 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 "js_api_lightweightmap.h" #include "ecmascript/js_array.h" #include "ecmascript/js_function.h" #include "ecmascript/js_handle.h" #include "ecmascript/js_object-inl.h" #include "ecmascript/js_tagged_number.h" #include "ecmascript/js_tagged_value.h" #include "libpandabase/utils/bit_utils.h" #include "object_factory.h" #include "utils/bit_utils.h" namespace panda::ecmascript { JSTaggedValue JSAPILightWeightMap::IncreaseCapacityTo(JSThread *thread, const JSHandle &lightWeightMap, int32_t index) { int32_t num = lightWeightMap->GetSize(); if (index < DEFAULT_CAPACITY_LENGTH || num >= index) { return JSTaggedValue::False(); } JSHandle hashArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::HASH); JSHandle keyArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::KEY); JSHandle valueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); JSHandle newHashArray = GrowCapacity(thread, hashArray, index); JSHandle newKeyArray = GrowCapacity(thread, keyArray, index); JSHandle newValueArray = GrowCapacity(thread, valueArray, index); lightWeightMap->SetHashes(thread, newHashArray); lightWeightMap->SetKeys(thread, newKeyArray); lightWeightMap->SetValues(thread, newValueArray); return JSTaggedValue::True(); } void JSAPILightWeightMap::SetValue(const JSThread *thread, const JSHandle &lightWeightMap, int32_t index, const JSHandle &value, AccossorsKind kind) { JSHandle array = GetArrayByKind(thread, lightWeightMap, kind); int32_t len = lightWeightMap->GetSize(); JSHandle newArray = GrowCapacity(thread, array, len + 1); while (len != index && len > 0) { JSTaggedValue oldValue = newArray->Get(len - 1); newArray->Set(thread, len, oldValue); len--; } newArray->Set(thread, index, value.GetTaggedValue()); switch (kind) { case AccossorsKind::HASH: lightWeightMap->SetHashes(thread, newArray); break; case AccossorsKind::KEY: lightWeightMap->SetKeys(thread, newArray); break; case AccossorsKind::VALUE: lightWeightMap->SetValues(thread, newArray); break; default: UNREACHABLE(); } } void JSAPILightWeightMap::RemoveValue(const JSThread *thread, const JSHandle &lightWeightMap, uint32_t index, AccossorsKind kind) { JSHandle array = GetArrayByKind(thread, lightWeightMap, kind); uint32_t len = lightWeightMap->GetLength(); uint32_t num = index; ASSERT(num < len); while (num < len - 1) { array->Set(thread, num, array->Get(num + 1)); num++; } switch (kind) { case AccossorsKind::HASH: lightWeightMap->SetHashes(thread, array); break; case AccossorsKind::KEY: lightWeightMap->SetKeys(thread, array); break; case AccossorsKind::VALUE: lightWeightMap->SetValues(thread, array); break; default: break; } } void JSAPILightWeightMap::Set(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &key, const JSHandle &value) { int32_t hash = Hash(key.GetTaggedValue()); JSHandle hashHandle(thread, JSTaggedValue(hash)); int32_t length = lightWeightMap->GetSize(); int32_t index = GetHashIndex(thread, lightWeightMap, hash, key.GetTaggedValue(), length); if (GetIndexOfKey(thread, lightWeightMap, key) < 0) { SetValue(thread, lightWeightMap, index, hashHandle, AccossorsKind::HASH); SetValue(thread, lightWeightMap, index, key, AccossorsKind::KEY); SetValue(thread, lightWeightMap, index, value, AccossorsKind::VALUE); lightWeightMap->SetLength(length + 1); return; } SetValue(thread, lightWeightMap, index, hashHandle, AccossorsKind::HASH); SetValue(thread, lightWeightMap, index, key, AccossorsKind::KEY); SetValue(thread, lightWeightMap, index, value, AccossorsKind::VALUE); } JSTaggedValue JSAPILightWeightMap::Get(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &key) { int32_t index = GetIndexOfKey(thread, lightWeightMap, key); if (index < 0) { return JSTaggedValue::Undefined(); } JSHandle valueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); return valueArray->Get(index); } JSTaggedValue JSAPILightWeightMap::HasAll(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &newLightWeightMap) { int32_t length = newLightWeightMap->GetSize(); int32_t len = lightWeightMap->GetSize(); if (length > len) { return JSTaggedValue::False(); } JSHandle oldHashArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::HASH); JSHandle oldKeyArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::KEY); JSHandle oldValueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); JSHandle newKeyArray = GetArrayByKind(thread, newLightWeightMap, AccossorsKind::KEY); JSHandle newValueArray = GetArrayByKind(thread, newLightWeightMap, AccossorsKind::VALUE); JSTaggedValue dealKey = JSTaggedValue::Undefined(); int32_t index = -1; int32_t hash; for (int32_t num = 0; num < length; num++) { dealKey = newKeyArray->Get(num); hash = Hash(dealKey); index = BinarySearchHashes(oldHashArray, hash, len); if (index < 0 || index >= len) { return JSTaggedValue::False(); } HashParams params { oldHashArray, oldKeyArray, &dealKey }; index = AvoidHashCollision(params, index, len, hash); if (!JSTaggedValue::SameValue(oldKeyArray->Get(index), dealKey) || !JSTaggedValue::SameValue(oldValueArray->Get(index), newValueArray->Get(num))) { // avoid Hash collision return JSTaggedValue::False(); } } return JSTaggedValue::True(); } JSTaggedValue JSAPILightWeightMap::HasKey(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &key) { int32_t index = GetIndexOfKey(thread, lightWeightMap, key); return index >= 0 ? JSTaggedValue::True() : JSTaggedValue::False(); } JSTaggedValue JSAPILightWeightMap::HasValue(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &value) { JSHandle valueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); int32_t length = lightWeightMap->GetSize(); for (int32_t num = 0; num < length; num++) { if (JSTaggedValue::SameValue(valueArray->Get(num), value.GetTaggedValue())) { return JSTaggedValue::True(); } } return JSTaggedValue::False(); } int32_t JSAPILightWeightMap::GetIndexOfKey(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &key) { int32_t hash = Hash(key.GetTaggedValue()); int32_t length = lightWeightMap->GetSize(); JSHandle hashArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::HASH); int32_t index = BinarySearchHashes(hashArray, hash, length); if (index >= 0) { // avoid Hash Collision JSHandle keyArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::KEY); int32_t right = index; while ((right < length) && (hashArray->Get(right).GetInt() == hash)) { if (JSTaggedValue::SameValue(keyArray->Get(right), key.GetTaggedValue())) { return right; } right++; } int32_t left = index - 1; while ((left >= 0) && ((hashArray->Get(left).GetInt() == hash))) { if (JSTaggedValue::SameValue(keyArray->Get(left), key.GetTaggedValue())) { return left; } left--; } } return -1; } int32_t JSAPILightWeightMap::GetIndexOfValue(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &value) { JSHandle valueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); int32_t length = lightWeightMap->GetSize(); JSTaggedValue compValue = value.GetTaggedValue(); for (int32_t i = 0; i < length; i++) { if (valueArray->Get(i) == compValue) { return i; } } return -1; // not find, default return -1 } JSTaggedValue JSAPILightWeightMap::GetKeyAt(JSThread *thread, const JSHandle &lightWeightMap, int32_t index) { int32_t length = lightWeightMap->GetSize(); if (index < 0 || length <= index) { THROW_RANGE_ERROR_AND_RETURN(thread, "index is not exits", JSTaggedValue::Exception()); } JSHandle keyArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::KEY); return keyArray->Get(index); } JSTaggedValue JSAPILightWeightMap::GetValueAt(JSThread *thread, const JSHandle &lightWeightMap, int32_t index) { int32_t length = lightWeightMap->GetSize(); if (index < 0 || length <= index) { THROW_RANGE_ERROR_AND_RETURN(thread, "index is not exits", JSTaggedValue::Exception()); } JSHandle valueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); return valueArray->Get(index); } void JSAPILightWeightMap::SetAll(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &needLightWeightMap) { JSHandle needKeyArray = GetArrayByKind(thread, needLightWeightMap, AccossorsKind::KEY); JSHandle needValueArray = GetArrayByKind(thread, needLightWeightMap, AccossorsKind::VALUE); JSMutableHandle key(thread, JSTaggedValue::Undefined()); JSMutableHandle value(thread, JSTaggedValue::Undefined()); int32_t length = needLightWeightMap->GetSize(); for (int32_t num = 0; num < length; num++) { key.Update(needKeyArray->Get(num)); value.Update(needValueArray->Get(num)); JSAPILightWeightMap::Set(thread, lightWeightMap, key, value); } } JSTaggedValue JSAPILightWeightMap::Remove(JSThread *thread, const JSHandle &lightWeightMap, const JSHandle &key) { int32_t hash = Hash(key.GetTaggedValue()); int32_t length = lightWeightMap->GetSize(); int32_t index = GetHashIndex(thread, lightWeightMap, hash, key.GetTaggedValue(), length); if (index < 0 || index >= length) { return JSTaggedValue::Undefined(); } JSTaggedValue value = JSTaggedValue::Undefined(); JSHandle keyArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::KEY); JSHandle valueArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::VALUE); for (int32_t i = 0; length > i; i++) { if (keyArray->Get(i) == key.GetTaggedValue()) { value = valueArray->Get(index); RemoveValue(thread, lightWeightMap, index, AccossorsKind::HASH); RemoveValue(thread, lightWeightMap, index, AccossorsKind::VALUE); RemoveValue(thread, lightWeightMap, index, AccossorsKind::KEY); lightWeightMap->SetLength(length - 1); } } return value; } JSTaggedValue JSAPILightWeightMap::RemoveAt(JSThread *thread, const JSHandle &lightWeightMap, int32_t index) { int32_t length = lightWeightMap->GetSize(); if (index < 0 || length <= index) { return JSTaggedValue::False(); } RemoveValue(thread, lightWeightMap, index, AccossorsKind::HASH); RemoveValue(thread, lightWeightMap, index, AccossorsKind::VALUE); RemoveValue(thread, lightWeightMap, index, AccossorsKind::KEY); lightWeightMap->SetLength(length - 1); return JSTaggedValue::True(); } JSTaggedValue JSAPILightWeightMap::IsEmpty() { if (GetLength() == 0) { return JSTaggedValue::True(); } else { return JSTaggedValue::False(); } } void JSAPILightWeightMap::Clear(JSThread *thread, const JSHandle &lightWeightMap) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle hashArray = JSHandle(factory->NewTaggedArray(DEFAULT_CAPACITY_LENGTH)); JSHandle keyArray = JSHandle(factory->NewTaggedArray(DEFAULT_CAPACITY_LENGTH)); JSHandle valueArray = JSHandle(factory->NewTaggedArray(DEFAULT_CAPACITY_LENGTH)); lightWeightMap->SetHashes(thread, hashArray); lightWeightMap->SetKeys(thread, keyArray); lightWeightMap->SetValues(thread, valueArray); lightWeightMap->SetLength(0); } JSTaggedValue JSAPILightWeightMap::SetValueAt(JSThread *thread, const JSHandle &lightWeightMap, int32_t index, const JSHandle &value) { int32_t length = lightWeightMap->GetSize(); if (index < 0 || length <= index) { THROW_RANGE_ERROR_AND_RETURN(thread, "index is not exits", JSTaggedValue::False()); } SetValue(thread, lightWeightMap, index, value, AccossorsKind::VALUE); return JSTaggedValue::True(); } int32_t JSAPILightWeightMap::GetHashIndex(JSThread *thread, const JSHandle &lightWeightMap, int32_t hash, const JSTaggedValue &key, int32_t size) { JSHandle hashArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::HASH); JSHandle keyArray = GetArrayByKind(thread, lightWeightMap, AccossorsKind::KEY); int32_t index = BinarySearchHashes(hashArray, hash, size); if (index < 0) { index ^= HASH_REBELLION; return index; } if ((index < size) && (JSTaggedValue::SameValue(keyArray->Get(index), key))) { return index; } JSTaggedValue dealKey = key; HashParams params { hashArray, keyArray, &dealKey }; return AvoidHashCollision(params, index, size, hash); } int32_t JSAPILightWeightMap::AvoidHashCollision(HashParams ¶ms, int32_t index, int32_t size, int32_t hash) { int32_t right = index; while ((right < size) && ((params.hashArray)->Get(right).GetInt() == hash)) { if (JSTaggedValue::SameValue((params.keyArray)->Get(right), *(params.key))) { return right; } right++; } int32_t left = index - 1; while ((left >= 0) && ((params.hashArray)->Get(left).GetInt() == hash)) { if (JSTaggedValue::SameValue((params.keyArray)->Get(left), *(params.key))) { return left; } left--; } int32_t res = (-right) ^ HASH_REBELLION; return res; } JSTaggedValue JSAPILightWeightMap::GetIteratorObj(JSThread *thread, const JSHandle &obj, IterationKind type) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle iter(factory->NewJSAPILightWeightMapIterator(obj, type)); return iter.GetTaggedValue(); } JSTaggedValue JSAPILightWeightMap::ToString(JSThread *thread, const JSHandle &lightWeightMap) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); std::u16string sepStr = std::wstring_convert, char16_t> {}.from_bytes(","); std::u16string colonStr = std::wstring_convert, char16_t> {}.from_bytes(":"); uint32_t length = lightWeightMap->GetLength(); std::u16string concatStr; std::u16string concatStrNew; JSMutableHandle valueHandle(thread, JSTaggedValue::Undefined()); JSMutableHandle keyHandle(thread, JSTaggedValue::Undefined()); for (uint32_t k = 0; k < length; k++) { std::u16string valueStr; valueHandle.Update(lightWeightMap->GetValueAt(thread, lightWeightMap, k)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!valueHandle->IsUndefined() && !valueHandle->IsNull()) { JSHandle valueStringHandle = JSTaggedValue::ToString(thread, valueHandle); uint32_t valueLen = valueStringHandle->GetLength(); if (valueStringHandle->IsUtf16()) { valueStr = base::StringHelper::Utf16ToU16String(valueStringHandle->GetDataUtf16(), valueLen); } else { valueStr = base::StringHelper::Utf8ToU16String(valueStringHandle->GetDataUtf8(), valueLen); } } std::u16string keyStr; keyHandle.Update(lightWeightMap->GetKeyAt(thread, lightWeightMap, k)); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); if (!keyHandle->IsUndefined() && !keyHandle->IsNull()) { JSHandle keyStringHandle = JSTaggedValue::ToString(thread, keyHandle); uint32_t keyLen = keyStringHandle->GetLength(); if (keyStringHandle->IsUtf16()) { keyStr = base::StringHelper::Utf16ToU16String(keyStringHandle->GetDataUtf16(), keyLen); } else { keyStr = base::StringHelper::Utf8ToU16String(keyStringHandle->GetDataUtf8(), keyLen); } } std::u16string nextStr = base::StringHelper::Append(keyStr, colonStr); nextStr = base::StringHelper::Append(nextStr, valueStr); if (k > 0) { concatStrNew = base::StringHelper::Append(concatStr, sepStr); concatStr = base::StringHelper::Append(concatStrNew, nextStr); continue; } concatStr = base::StringHelper::Append(concatStr, nextStr); } char16_t *char16tData = concatStr.data(); auto *uint16tData = reinterpret_cast(char16tData); uint32_t u16strSize = concatStr.size(); return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue(); } JSHandle JSAPILightWeightMap::GrowCapacity(const JSThread *thread, const JSHandle &oldArray, uint32_t needCapacity) { uint32_t oldLength = oldArray->GetLength(); if (needCapacity <= oldLength) { return oldArray; } uint32_t newCapacity = ComputeCapacity(needCapacity); JSHandle newArray = thread->GetEcmaVM()->GetFactory()->CopyArray(oldArray, oldLength, newCapacity); return newArray; } JSHandle JSAPILightWeightMap::GetArrayByKind(const JSThread *thread, const JSHandle &lightWeightMap, AccossorsKind kind) { JSHandle array; switch (kind) { case AccossorsKind::HASH: array = JSHandle(thread, lightWeightMap->GetHashes()); break; case AccossorsKind::KEY: array = JSHandle(thread, lightWeightMap->GetKeys()); break; case AccossorsKind::VALUE: array = JSHandle(thread, lightWeightMap->GetValues()); break; default: UNREACHABLE(); } return array; } int32_t JSAPILightWeightMap::Hash(JSTaggedValue key) { if (key.IsDouble() && key.GetDouble() == 0.0) { key = JSTaggedValue(0); } if (key.IsSymbol()) { auto symbolString = JSSymbol::Cast(key.GetTaggedObject()); return symbolString->GetHashField(); } if (key.IsString()) { auto keyString = EcmaString::Cast(key.GetTaggedObject()); return keyString->GetHashcode(); } if (key.IsECMAObject()) { int32_t hash = ECMAObject::Cast(key.GetTaggedObject())->GetHash(); if (hash == 0) { uint64_t keyValue = key.GetRawData(); hash = GetHash32(reinterpret_cast(&keyValue), sizeof(keyValue) / sizeof(uint8_t)); ECMAObject::Cast(key.GetTaggedObject())->SetHash(hash); } return hash; } if (key.IsInt()) { int32_t hash = key.GetInt(); return hash; } uint64_t keyValue = key.GetRawData(); return GetHash32(reinterpret_cast(&keyValue), sizeof(keyValue) / sizeof(uint8_t)); } int32_t JSAPILightWeightMap::BinarySearchHashes(JSHandle &array, int32_t hash, int32_t size) { int32_t low = 0; int32_t high = size - 1; while (low <= high) { uint32_t mid = static_cast(low + high) >> 1U; int32_t midHash = array->Get(mid).GetInt(); if (midHash < hash) { low = static_cast(mid) + 1; } else { if (midHash == hash) { return mid; } high = static_cast(mid) - 1; } } return -(low + 1); } } // namespace panda::ecmascript