/* * Copyright (c) 2022-2024 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_api/js_api_lightweightset.h" #include "ecmascript/containers/containers_errors.h" #include "ecmascript/interpreter/interpreter.h" #include "ecmascript/js_array.h" #include "ecmascript/js_function.h" #include namespace panda::ecmascript { using ContainerError = containers::ContainerError; using ErrorFlag = containers::ErrorFlag; bool JSAPILightWeightSet::Add(JSThread *thread, const JSHandle &obj, const JSHandle &value) { CheckAndCopyValues(thread, obj); uint32_t hashCode = obj->Hash(thread, value.GetTaggedValue()); JSHandle hashArray(thread, obj->GetHashes()); JSHandle valueArray(thread, obj->GetValues()); int32_t size = static_cast(obj->GetLength()); int32_t index = obj->GetHashIndex(thread, value, size); if (index >= 0) { return false; } index ^= JSAPILightWeightSet::HASH_REBELLION; if (index < size) { obj->AdjustArray(thread, hashArray, index, size, true); obj->AdjustArray(thread, valueArray, index, size, true); } uint32_t capacity = hashArray->GetLength(); if (size + 1 >= static_cast(capacity)) { // need expanding uint32_t newCapacity = capacity << 1U; hashArray = thread->GetEcmaVM()->GetFactory()->CopyArray(hashArray, capacity, newCapacity); valueArray = thread->GetEcmaVM()->GetFactory()->CopyArray(valueArray, capacity, newCapacity); obj->SetHashes(thread, hashArray); obj->SetValues(thread, valueArray); } hashArray->Set(thread, index, JSTaggedValue(hashCode)); valueArray->Set(thread, index, value.GetTaggedValue()); size++; obj->SetLength(size); return true; } JSTaggedValue JSAPILightWeightSet::Get(const uint32_t index) { TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject()); return valueArray->Get(index); } JSHandle JSAPILightWeightSet::CreateSlot(const JSThread *thread, const uint32_t capacity) { ASSERT_PRINT(capacity > 0, "size must be a non-negative integer"); ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle taggedArray = factory->NewTaggedArray(capacity); for (uint32_t i = 0; i < capacity; i++) { taggedArray->Set(thread, i, JSTaggedValue::Hole()); } return taggedArray; } int32_t JSAPILightWeightSet::GetHashIndex(const JSThread *thread, const JSHandle &value, int32_t size) { uint32_t hashCode = Hash(thread, value.GetTaggedValue()); int32_t index = BinarySearchHashes(hashCode, size); if (index < 0) { return index; } TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject()); if (index < size && (JSTaggedValue::SameValue(valueArray->Get(index), value.GetTaggedValue()))) { return index; } TaggedArray *hashArray = TaggedArray::Cast(GetHashes().GetTaggedObject()); int32_t right = index; while (right < size && (hashArray->Get(right).GetNumber() == hashCode)) { if (JSTaggedValue::SameValue(valueArray->Get(right), value.GetTaggedValue())) { return right; } right++; } int32_t left = index - 1; while (left >= 0 && ((hashArray->Get(left).GetNumber() == hashCode))) { if (JSTaggedValue::SameValue(valueArray->Get(left), value.GetTaggedValue())) { return left; } left--; } return -right; } int32_t JSAPILightWeightSet::BinarySearchHashes(uint32_t hash, int32_t size) { int32_t low = 0; int32_t high = size - 1; TaggedArray *hashArray = TaggedArray::Cast(GetHashes().GetTaggedObject()); while (low <= high) { int32_t mid = (low + high) >> 1U; uint32_t midVal = (uint32_t)(hashArray->Get(static_cast(mid)).GetNumber()); if (midVal < hash) { low = mid + 1; } else { if (midVal <= hash) { return mid; } high = mid - 1; } } return -(low + 1); } bool JSAPILightWeightSet::AddAll(JSThread *thread, const JSHandle &obj, const JSHandle &value) { bool changed = false; JSHandle srcLightWeightSet = JSHandle::Cast(value); uint32_t srcSize = srcLightWeightSet->GetSize(); uint32_t size = obj->GetSize(); obj->EnsureCapacity(thread, obj, size + srcSize); JSMutableHandle element(thread, JSTaggedValue::Undefined()); for (uint32_t i = 0; i < srcSize; i++) { element.Update(srcLightWeightSet->GetValueAt(i)); changed |= JSAPILightWeightSet::Add(thread, obj, element); } return changed; } void JSAPILightWeightSet::EnsureCapacity(const JSThread *thread, const JSHandle &obj, uint32_t minimumCapacity) { TaggedArray *hashes = TaggedArray::Cast(obj->GetValues().GetTaggedObject()); uint32_t capacity = hashes->GetLength(); uint32_t newCapacity = capacity; if (capacity > minimumCapacity) { return; } // adjust while (newCapacity <= minimumCapacity) { newCapacity = newCapacity << 1U; } obj->SizeCopy(thread, obj, capacity, newCapacity); } void JSAPILightWeightSet::SizeCopy(const JSThread *thread, const JSHandle &obj, uint32_t capacity, uint32_t newCapacity) { JSHandle hashArray(thread, obj->GetHashes()); JSHandle valueArray(thread, obj->GetValues()); hashArray = thread->GetEcmaVM()->GetFactory()->CopyArray(hashArray, capacity, newCapacity); valueArray = thread->GetEcmaVM()->GetFactory()->CopyArray(valueArray, capacity, newCapacity); obj->SetValues(thread, hashArray); obj->SetHashes(thread, valueArray); } bool JSAPILightWeightSet::IsEmpty() { return GetLength() == 0; } JSTaggedValue JSAPILightWeightSet::GetValueAt(int32_t index) { int32_t size = static_cast(GetLength()); if (index < 0 || index >= size) { return JSTaggedValue::Undefined(); } TaggedArray *values = TaggedArray::Cast(GetValues().GetTaggedObject()); return values->Get(index); } JSTaggedValue JSAPILightWeightSet::GetHashAt(int32_t index) { int32_t size = static_cast(GetLength()); if (index < 0 || index >= size) { return JSTaggedValue::Undefined(); } TaggedArray *values = TaggedArray::Cast(GetHashes().GetTaggedObject()); return values->Get(index); } bool JSAPILightWeightSet::HasAll(const JSHandle &value) { bool result = false; uint32_t relocate = 0; JSAPILightWeightSet *lightweightSet = JSAPILightWeightSet::Cast(value.GetTaggedValue().GetTaggedObject()); uint32_t size = GetLength(); uint32_t destSize = lightweightSet->GetLength(); TaggedArray *hashes = TaggedArray::Cast(GetHashes().GetTaggedObject()); TaggedArray *destHashes = TaggedArray::Cast(lightweightSet->GetHashes().GetTaggedObject()); if (destSize > size) { return result; } for (uint32_t i = 0; i < destSize; i++) { uint32_t destHashCode = destHashes->Get(i).GetNumber(); result = false; for (uint32_t j = relocate; j < size; j++) { uint32_t hashCode = hashes->Get(j).GetNumber(); if (destHashCode == hashCode) { result = true; relocate = j + 1; break; } } if (!result) { break; } } return result; } bool JSAPILightWeightSet::Has(const JSThread *thread, const JSHandle &value) { uint32_t size = GetLength(); int32_t index = GetHashIndex(thread, value, size); if (index < 0) { return false; } return true; } bool JSAPILightWeightSet::HasHash(const JSHandle &hashCode) { uint32_t size = GetLength(); int32_t index = BinarySearchHashes(hashCode.GetTaggedValue().GetNumber(), size); if (index < 0) { return false; } return true; } bool JSAPILightWeightSet::Equal(JSThread *thread, const JSHandle &obj, const JSHandle &value) { bool result = false; JSHandle destHashes(thread, obj->GetValues()); uint32_t destSize = obj->GetLength(); uint32_t srcSize = 0; JSMutableHandle srcHashes(thread, obj->GetHashes()); if (value.GetTaggedValue().IsJSAPILightWeightSet()) { JSAPILightWeightSet *srcLightWeightSet = JSAPILightWeightSet::Cast(value.GetTaggedValue().GetTaggedObject()); srcSize = srcLightWeightSet->GetLength(); if (srcSize == 0 || destSize == 0) { return false; } srcHashes.Update(srcLightWeightSet->GetHashes()); } if (value.GetTaggedValue().IsJSArray()) { srcHashes.Update(JSArray::ToTaggedArray(thread, value)); srcSize = srcHashes->GetLength(); if (srcSize == 0 || destSize == 0) { return false; } } if (srcSize != destSize) { return false; } for (uint32_t i = 0; i < destSize; i++) { JSTaggedValue compareValue = destHashes->Get(i); JSTaggedValue values = srcHashes->Get(i); if (compareValue.IsNumber() && values.IsNumber()) { result = JSTaggedValue::SameValueNumberic(compareValue, values); } if (compareValue.IsString() && values.IsString()) { result = JSTaggedValue::StringCompare(EcmaString::Cast(compareValue.GetTaggedObject()), EcmaString::Cast(values.GetTaggedObject())); } if (!result) { return result; } } return result; } void JSAPILightWeightSet::IncreaseCapacityTo(JSThread *thread, const JSHandle &obj, int32_t minCapacity) { uint32_t capacity = TaggedArray::Cast(obj->GetValues().GetTaggedObject())->GetLength(); int32_t intCapacity = static_cast(capacity); if (minCapacity <= 0 || intCapacity >= minCapacity) { std::ostringstream oss; oss << "The value of \"minimumCapacity\" is out of range. It must be > " << intCapacity << ". Received value is: " << minCapacity; JSTaggedValue error = ContainerError::BusinessError(thread, ErrorFlag::RANGE_ERROR, oss.str().c_str()); THROW_NEW_ERROR_AND_RETURN(thread, error); } JSHandle hashArray(thread, obj->GetHashes()); JSHandle newElements = thread->GetEcmaVM()->GetFactory()->NewAndCopyTaggedArray(hashArray, static_cast(minCapacity), capacity); obj->SetHashes(thread, newElements); } JSHandle JSAPILightWeightSet::GetIteratorObj(JSThread *thread, const JSHandle &obj, IterationKind kind) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); JSHandle iter = JSHandle::Cast(factory->NewJSAPILightWeightSetIterator(obj, kind)); return iter; } JSTaggedValue JSAPILightWeightSet::ForEach(JSThread *thread, const JSHandle &thisHandle, const JSHandle &callbackFn, const JSHandle &thisArg) { JSHandle lightweightset = JSHandle::Cast(thisHandle); CheckAndCopyValues(thread, lightweightset); uint32_t length = lightweightset->GetSize(); JSHandle undefined = thread->GlobalConstants()->GetHandledUndefined(); for (uint32_t k = 0; k < length; k++) { JSTaggedValue kValue = lightweightset->GetValueAt(k); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFn, thisArg, undefined, 3); // 3:three args RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, JSTaggedValue::Exception()); info->SetCallArg(kValue, kValue, thisHandle.GetTaggedValue()); JSTaggedValue funcResult = JSFunction::Call(info); RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult); if (lightweightset->GetSize() != length) { // prevent length change length = lightweightset->GetSize(); } } return JSTaggedValue::Undefined(); } int32_t JSAPILightWeightSet::GetIndexOf(const JSThread *thread, JSHandle &value) { uint32_t size = GetLength(); int32_t index = GetHashIndex(thread, value, size); return index; } JSTaggedValue JSAPILightWeightSet::Remove(JSThread *thread, JSHandle &value) { uint32_t size = GetLength(); TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject()); int32_t index = GetHashIndex(thread, value, size); if (index < 0) { return JSTaggedValue::Undefined(); } JSTaggedValue result = valueArray->Get(index); RemoveAt(thread, index); return result; } bool JSAPILightWeightSet::RemoveAt(JSThread *thread, int32_t index) { uint32_t size = GetLength(); if (index < 0 || index >= static_cast(size)) { return false; } JSHandle valueArray(thread, GetValues()); JSHandle hashArray(thread, GetHashes()); RemoveValue(thread, hashArray, static_cast(index), true); RemoveValue(thread, valueArray, static_cast(index)); SetLength(size - 1); return true; } void JSAPILightWeightSet::RemoveValue(const JSThread *thread, JSHandle &taggedArray, uint32_t index, bool isHash) { uint32_t len = GetLength(); ASSERT(index < len); TaggedArray::RemoveElementByIndex(thread, taggedArray, index, len, isHash); } void JSAPILightWeightSet::AdjustArray(JSThread *thread, JSHandle srcArray, uint32_t fromIndex, uint32_t toIndex, bool direction) { uint32_t size = GetLength(); uint32_t idx = size - 1; if (direction) { while (fromIndex < toIndex) { JSTaggedValue value = srcArray->Get(idx); srcArray->Set(thread, idx + 1, value); idx--; fromIndex++; } } else { uint32_t moveSize = size - fromIndex; for (uint32_t i = 0; i < moveSize; i++) { if ((fromIndex + i) < size) { JSTaggedValue value = srcArray->Get(fromIndex + i); srcArray->Set(thread, toIndex + i, value); } else { srcArray->Set(thread, toIndex + i, JSTaggedValue::Hole()); } } } } JSTaggedValue JSAPILightWeightSet::ToString(JSThread *thread, const JSHandle &obj) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); std::u16string sepStr = std::wstring_convert, char16_t> {}.from_bytes(","); uint32_t length = obj->GetSize(); JSHandle valueArray(thread, obj->GetValues()); std::u16string concatStr; JSMutableHandle values(thread, JSTaggedValue::Undefined()); for (uint32_t k = 0; k < length; k++) { std::u16string nextStr; values.Update(valueArray->Get(k)); if (!values->IsUndefined() && !values->IsNull()) { JSHandle nextStringHandle = JSTaggedValue::ToString(thread, values); RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); nextStr = EcmaStringAccessor(nextStringHandle).ToU16String(); } if (k > 0) { concatStr.append(sepStr); concatStr.append(nextStr); continue; } concatStr.append(nextStr); } char16_t *char16tData = concatStr.data(); auto *uint16tData = reinterpret_cast(char16tData); uint32_t u16strSize = concatStr.size(); return factory->NewFromUtf16Literal(uint16tData, u16strSize).GetTaggedValue(); } void JSAPILightWeightSet::Clear(JSThread *thread) { TaggedArray *hashArray = TaggedArray::Cast(GetHashes().GetTaggedObject()); TaggedArray *valueArray = TaggedArray::Cast(GetValues().GetTaggedObject()); uint32_t size = GetLength(); for (uint32_t index = 0; index < size; index++) { hashArray->Set(thread, index, JSTaggedValue::Hole()); valueArray->Set(thread, index, JSTaggedValue::Hole()); } SetLength(0); } uint32_t JSAPILightWeightSet::Hash(const JSThread *thread, 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 EcmaStringAccessor(keyString).GetHashcode(); } if (key.IsECMAObject()) { uint32_t hash = static_cast(ECMAObject::Cast(key.GetTaggedObject())->GetHash()); if (hash == 0) { hash = static_cast(base::RandomGenerator::GenerateIdentityHash()); JSHandle ecmaObj(thread, key); ECMAObject::SetHash(thread, hash, ecmaObj); } return hash; } if (key.IsInt()) { uint32_t hash = static_cast(key.GetInt()); return hash; } uint64_t keyValue = key.GetRawData(); return GetHash32(reinterpret_cast(&keyValue), sizeof(keyValue) / sizeof(uint8_t)); } void JSAPILightWeightSet::CheckAndCopyValues(const JSThread *thread, JSHandle obj) { JSHandle values(thread, obj->GetValues()); // Check whether array is shared in the nonmovable space before set properties and elements. // If true, then really copy array in the semi space. ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); if (values.GetTaggedValue().IsCOWArray()) { auto newArray = factory->CopyArray(values, values->GetLength(), values->GetLength(), JSTaggedValue::Hole(), MemSpaceType::SEMI_SPACE); obj->SetValues(thread, newArray.GetTaggedValue()); } } } // namespace panda::ecmascript