* Copyright (c) 2021-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_stable_array.h"
#include "ecmascript/base/config.h"
#include "ecmascript/base/sort_helper.h"
#include "ecmascript/base/typed_array_helper-inl.h"
#include "ecmascript/interpreter/fast_runtime_stub-inl.h"
namespace panda::ecmascript {
using TypedArrayHelper = base::TypedArrayHelper;
using TypedArrayKind = base::TypedArrayKind;
using BuiltinsArrayBuffer = builtins::BuiltinsArrayBuffer;
using BuiltinsSendableArrayBuffer = builtins::BuiltinsSendableArrayBuffer;
template<TypedArrayKind typedArrayKind>
using BuiltinsArrayBufferType = base::BuiltinsArrayBufferType<typedArrayKind>;
JSTaggedValue JSStableArray::Push(JSHandle<JSSharedArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
uint32_t argc = argv->GetArgsNumber();
uint32_t oldLength = receiver->GetArrayLength();
uint32_t newLength = argc + oldLength;
JSHandle<JSObject> thisObjHandle(receiver);
if (newLength > ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
JSObject::GrowElementsCapacity(thread, JSHandle<JSObject>::Cast(receiver), newLength, true);
}
bool needTransition = true;
for (uint32_t k = 0; k < argc; k++) {
JSHandle<JSTaggedValue> value = argv->GetCallArg(k);
ElementAccessor::Set(thread, thisObjHandle, oldLength + k, value, needTransition);
}
receiver->SetArrayLength(thread, newLength);
return JSTaggedValue(newLength);
}
JSTaggedValue JSStableArray::Push(JSHandle<JSArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
uint32_t argc = argv->GetArgsNumber();
uint32_t oldLength = receiver->GetArrayLength();
uint32_t newLength = argc + oldLength;
JSHandle<JSObject> thisObjHandle(receiver);
if (newLength > ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
JSObject::GrowElementsCapacity(thread, JSHandle<JSObject>::Cast(receiver), newLength, true);
}
bool needTransition = true;
for (uint32_t k = 0; k < argc; k++) {
JSHandle<JSTaggedValue> value = argv->GetCallArg(k);
ElementAccessor::Set(thread, thisObjHandle, oldLength + k, value, needTransition);
}
receiver->SetArrayLength(thread, newLength);
return JSTaggedValue(newLength);
}
JSTaggedValue JSStableArray::Pop(JSHandle<JSSharedArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
uint32_t length = receiver->GetArrayLength();
if (length == 0) {
return JSTaggedValue::Undefined();
}
JSHandle<JSTaggedValue> holeHandle(thread, JSTaggedValue::Hole());
JSSharedArray::CheckAndCopyArray(thread, receiver);
JSHandle<JSObject> obj(receiver);
uint32_t capacity = ElementAccessor::GetElementsLength(thread, obj);
uint32_t index = length - 1;
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Hole());
if (index < capacity) {
result.Update(ElementAccessor::Get(thread, obj, index));
}
if (!result->IsHole()) {
if (TaggedArray::ShouldTrim(capacity, index)) {
TaggedArray *elements = TaggedArray::Cast(receiver->GetElements(thread).GetTaggedObject());
elements->Trim(thread, index);
} else {
ElementAccessor::Set(thread, obj, index, holeHandle, false);
}
} else {
JSHandle<JSTaggedValue> thisObjVal(receiver);
result.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, index).GetTaggedValue());
}
receiver->SetArrayLength(thread, index);
return result->IsHole() ? JSTaggedValue::Undefined() : result.GetTaggedValue();
}
JSTaggedValue JSStableArray::Pop(JSHandle<JSArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
uint32_t length = receiver->GetArrayLength();
if (length == 0) {
return JSTaggedValue::Undefined();
}
JSHandle<JSTaggedValue> holeHandle(thread, JSTaggedValue::Hole());
JSArray::CheckAndCopyArray(thread, receiver);
JSHandle<JSObject> obj(receiver);
uint32_t capacity = ElementAccessor::GetElementsLength(thread, obj);
uint32_t index = length - 1;
JSMutableHandle<JSTaggedValue> result(thread, JSTaggedValue::Hole());
if (index < capacity) {
result.Update(ElementAccessor::Get(thread, obj, index));
}
if (!result->IsHole()) {
if (TaggedArray::ShouldTrim(capacity, index)) {
TaggedArray *elements = TaggedArray::Cast(receiver->GetElements(thread).GetTaggedObject());
elements->Trim(thread, index);
} else {
ElementAccessor::Set(thread, obj, index, holeHandle, false);
}
} else {
JSHandle<JSTaggedValue> thisObjVal(receiver);
result.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, index).GetTaggedValue());
}
receiver->SetArrayLength(thread, index);
return result->IsHole() ? JSTaggedValue::Undefined() : result.GetTaggedValue();
}
void JSStableArray::HandleArray(JSHandle<JSObject> &newArrayHandle, uint32_t &actualDeleteCount,
JSThread *thread, uint32_t &start, JSHandle<JSObject> &thisObjHandle,
JSHandle<JSTaggedValue> &holeHandle)
{
TaggedArray *destElements = TaggedArray::Cast(newArrayHandle->GetElements(thread).GetTaggedObject());
if (actualDeleteCount > ElementAccessor::GetElementsLength(thread, newArrayHandle)) {
destElements = *JSObject::GrowElementsCapacity(thread, newArrayHandle, actualDeleteCount);
}
for (uint32_t idx = 0; idx < actualDeleteCount; idx++) {
if ((start + idx) >= ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
ElementAccessor::Set(thread, newArrayHandle, idx, holeHandle, true);
} else {
JSHandle<JSTaggedValue> valueHandle(thread, ElementAccessor::Get(thread, thisObjHandle, start + idx));
ElementAccessor::Set(thread, newArrayHandle, idx, valueHandle, true);
}
}
JSHandle<JSArray>::Cast(newArrayHandle)->SetArrayLength(thread, actualDeleteCount);
}
JSTaggedValue JSStableArray::UpdateArrayCapacity(JSHandle<JSObject> &thisObjHandle, uint32_t &len,
uint32_t &insertCount, uint32_t &actualDeleteCount,
JSHandle<JSArray> &receiver, uint32_t &start,
JSThread *thread, bool &needTransition,
JSHandle<JSTaggedValue> &holeHandle,
EcmaRuntimeCallInfo *argv, JSHandle<JSTaggedValue> &thisObjVal,
JSHandle<JSTaggedValue> &lengthKey)
{
uint32_t oldCapacity = ElementAccessor::GetElementsLength(thread, thisObjHandle);
ASSERT(len + insertCount >= actualDeleteCount);
uint32_t newCapacity = len - actualDeleteCount + insertCount;
TaggedArray *srcElements = TaggedArray::Cast(thisObjHandle->GetElements(thread).GetTaggedObject());
JSMutableHandle<TaggedArray> srcElementsHandle(thread, srcElements);
uint32_t argc = argv->GetArgsNumber();
if (newCapacity > oldCapacity) {
srcElementsHandle.Update(JSObject::GrowElementsCapacity(thread, thisObjHandle, newCapacity));
}
if (insertCount < actualDeleteCount) {
JSArray::CheckAndCopyArray(thread, receiver);
srcElementsHandle.Update(receiver->GetElements(thread));
for (uint32_t idx = start; idx < len - actualDeleteCount; idx++) {
JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Hole());
if ((idx + actualDeleteCount) < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
element.Update(ElementAccessor::Get(thread, thisObjHandle, idx + actualDeleteCount));
}
if ((idx + insertCount) < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
ElementAccessor::Set(thread, thisObjHandle, idx + insertCount, element, needTransition);
}
}
if ((oldCapacity > newCapacity) && TaggedArray::ShouldTrim(oldCapacity, newCapacity)) {
srcElementsHandle->Trim(thread, newCapacity);
} else {
for (uint32_t idx = newCapacity; idx < len; idx++) {
if (idx < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
ElementAccessor::Set(thread, thisObjHandle, idx, holeHandle, needTransition);
}
}
}
} else {
ASSERT(len >= actualDeleteCount);
for (uint32_t idx = len - actualDeleteCount; idx > start; idx--) {
JSHandle<JSTaggedValue> element(thread,
ElementAccessor::Get(thread, thisObjHandle, idx + actualDeleteCount - 1));
ElementAccessor::Set(thread, thisObjHandle, idx + insertCount - 1, element, needTransition);
}
}
for (uint32_t i = 2, idx = start; i < argc; i++, idx++) {
ElementAccessor::Set(thread, thisObjHandle, idx, argv->GetCallArg(i), needTransition);
}
JSHandle<JSTaggedValue> newLenHandle(thread, JSTaggedValue(newCapacity));
JSTaggedValue::SetProperty(thread, thisObjVal, lengthKey, newLenHandle, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return JSTaggedValue::Undefined();
}
JSTaggedValue JSStableArray::Splice(JSHandle<JSArray> receiver, EcmaRuntimeCallInfo *argv,
uint32_t start, uint32_t insertCount, uint32_t actualDeleteCount,
JSHandle<JSObject> newArrayHandle, uint32_t len)
{
JSThread *thread = argv->GetThread();
JSHandle<JSTaggedValue> holeHandle(thread, JSTaggedValue::Hole());
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSArray::CheckAndCopyArray(thread, receiver);
JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
bool needTransition = true;
if (newArrayHandle.GetTaggedValue().IsStableJSArray(thread)) {
HandleArray(newArrayHandle, actualDeleteCount, thread, start, thisObjHandle, holeHandle);
} else {
JSMutableHandle<JSTaggedValue> fromKey(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> toKey(thread, JSTaggedValue::Undefined());
uint32_t k = 0;
while (k < actualDeleteCount) {
uint32_t from = start + k;
fromKey.Update(JSTaggedValue(from));
bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, fromKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (exists) {
JSHandle<JSTaggedValue> fromValue = JSArray::FastGetPropertyByValue(thread, thisObjVal, fromKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
toKey.Update(JSTaggedValue(k));
if (newArrayHandle->IsJSProxy()) {
toKey.Update(JSTaggedValue::ToString(thread, toKey).GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, toKey, fromValue);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
k++;
}
JSHandle<JSTaggedValue> deleteCount(thread, JSTaggedValue(actualDeleteCount));
JSTaggedValue::SetProperty(thread, JSHandle<JSTaggedValue>::Cast(newArrayHandle), lengthKey, deleteCount,
true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
UpdateArrayCapacity(thisObjHandle, len, insertCount, actualDeleteCount, receiver, start,
thread, needTransition, holeHandle, argv, thisObjVal, lengthKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return newArrayHandle.GetTaggedValue();
}
JSTaggedValue JSStableArray::Splice(JSHandle<JSSharedArray> receiver, EcmaRuntimeCallInfo *argv,
uint32_t start, uint32_t insertCount, uint32_t actualDeleteCount,
JSHandle<JSObject> newArrayHandle, uint32_t len)
{
JSThread *thread = argv->GetThread();
uint32_t argc = argv->GetArgsNumber();
JSHandle<JSTaggedValue> holeHandle(thread, JSTaggedValue::Hole());
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSSharedArray::CheckAndCopyArray(thread, receiver);
JSHandle<JSTaggedValue> lengthKey = thread->GlobalConstants()->GetHandledLengthString();
TaggedArray *srcElements = TaggedArray::Cast(thisObjHandle->GetElements(thread).GetTaggedObject());
JSMutableHandle<TaggedArray> srcElementsHandle(thread, srcElements);
bool needTransition = true;
if (newArrayHandle.GetTaggedValue().IsStableJSArray(thread)) {
TaggedArray *destElements = TaggedArray::Cast(newArrayHandle->GetElements(thread).GetTaggedObject());
if (actualDeleteCount > ElementAccessor::GetElementsLength(thread, newArrayHandle)) {
destElements = *JSObject::GrowElementsCapacity(thread, newArrayHandle, actualDeleteCount);
}
for (uint32_t idx = 0; idx < actualDeleteCount; idx++) {
if ((start + idx) >= ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
ElementAccessor::Set(thread, newArrayHandle, idx, holeHandle, needTransition);
} else {
JSHandle<JSTaggedValue> valueHandle(thread, ElementAccessor::Get(thread, thisObjHandle, start + idx));
ElementAccessor::Set(thread, newArrayHandle, idx, valueHandle, needTransition);
}
}
JSHandle<JSSharedArray>::Cast(newArrayHandle)->SetArrayLength(thread, actualDeleteCount);
} else {
JSMutableHandle<JSTaggedValue> fromKey(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> toKey(thread, JSTaggedValue::Undefined());
uint32_t k = 0;
while (k < actualDeleteCount) {
uint32_t from = start + k;
fromKey.Update(JSTaggedValue(from));
bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, fromKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (exists) {
JSHandle<JSTaggedValue> fromValue = JSArray::FastGetPropertyByValue(thread, thisObjVal, fromKey);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
toKey.Update(JSTaggedValue(k));
if (newArrayHandle->IsJSProxy()) {
toKey.Update(JSTaggedValue::ToString(thread, toKey).GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, toKey, fromValue);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
k++;
}
JSHandle<JSTaggedValue> deleteCount(thread, JSTaggedValue(actualDeleteCount));
JSTaggedValue::SetProperty(thread, JSHandle<JSTaggedValue>::Cast(newArrayHandle), lengthKey, deleteCount,
true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
uint32_t oldCapacity = ElementAccessor::GetElementsLength(thread, thisObjHandle);
ASSERT(len + insertCount >= actualDeleteCount);
uint32_t newCapacity = len - actualDeleteCount + insertCount;
if (newCapacity > oldCapacity) {
srcElementsHandle.Update(JSObject::GrowElementsCapacity(thread, thisObjHandle, newCapacity));
}
if (insertCount < actualDeleteCount) {
JSSharedArray::CheckAndCopyArray(thread, receiver);
srcElementsHandle.Update(receiver->GetElements(thread));
for (uint32_t idx = start; idx < len - actualDeleteCount; idx++) {
JSMutableHandle<JSTaggedValue> element(thread, JSTaggedValue::Hole());
if ((idx + actualDeleteCount) < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
element.Update(ElementAccessor::Get(thread, thisObjHandle, idx + actualDeleteCount));
}
if ((idx + insertCount) < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
ElementAccessor::Set(thread, thisObjHandle, idx + insertCount, element, needTransition);
}
}
if ((oldCapacity > newCapacity) && TaggedArray::ShouldTrim(oldCapacity, newCapacity)) {
srcElementsHandle->Trim(thread, newCapacity);
} else {
for (uint32_t idx = newCapacity; idx < len; idx++) {
if (idx < ElementAccessor::GetElementsLength(thread, thisObjHandle)) {
ElementAccessor::Set(thread, thisObjHandle, idx, holeHandle, needTransition);
}
}
}
} else {
ASSERT(len >= actualDeleteCount);
for (uint32_t idx = len - actualDeleteCount; idx > start; idx--) {
JSHandle<JSTaggedValue> element(thread,
ElementAccessor::Get(thread, thisObjHandle, idx + actualDeleteCount - 1));
ElementAccessor::Set(thread, thisObjHandle, idx + insertCount - 1, element, needTransition);
}
}
for (uint32_t i = 2, idx = start; i < argc; i++, idx++) {
ElementAccessor::Set(thread, thisObjHandle, idx, argv->GetCallArg(i), needTransition);
}
JSHandle<JSTaggedValue> newLenHandle(thread, JSTaggedValue(newCapacity));
JSTaggedValue::SetProperty(thread, thisObjVal, lengthKey, newLenHandle, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return newArrayHandle.GetTaggedValue();
}
JSTaggedValue JSStableArray::Shift(JSHandle<JSSharedArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> holeHandle(thread, JSTaggedValue::Hole());
uint32_t length = receiver->GetArrayLength();
if (length == 0) {
return JSTaggedValue::Undefined();
}
JSSharedArray::CheckAndCopyArray(thread, receiver);
TaggedArray *elements = TaggedArray::Cast(receiver->GetElements(thread).GetTaggedObject());
JSHandle<JSTaggedValue> result(thread, ElementAccessor::Get(thread, thisObjHandle, 0));
bool needTransition = false;
for (uint32_t k = 1; k < length; k++) {
JSHandle<JSTaggedValue> kValue(thread, ElementAccessor::Get(thread, thisObjHandle, k));
ElementAccessor::Set(thread, thisObjHandle, k - 1, kValue, needTransition);
}
uint32_t capacity = ElementAccessor::GetElementsLength(thread, thisObjHandle);
uint32_t index = length - 1;
if (TaggedArray::ShouldTrim(capacity, index)) {
elements->Trim(thread, index);
} else {
ElementAccessor::Set(thread, thisObjHandle, index, holeHandle, needTransition);
}
receiver->SetArrayLength(thread, index);
return result->IsHole() ? JSTaggedValue::Undefined() : result.GetTaggedValue();
}
JSTaggedValue JSStableArray::Shift(JSHandle<JSArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> holeHandle(thread, JSTaggedValue::Hole());
uint32_t length = receiver->GetArrayLength();
if (length == 0) {
return JSTaggedValue::Undefined();
}
JSArray::CheckAndCopyArray(thread, receiver);
JSHandle<TaggedArray> elements(thread, TaggedArray::Cast(receiver->GetElements(thread).GetTaggedObject()));
JSHandle<JSTaggedValue> result(thread, ElementAccessor::Get(thread, thisObjHandle, 0));
bool needTransition = false;
for (uint32_t k = 1; k < length; k++) {
JSHandle<JSTaggedValue> kValue(thread, ElementAccessor::Get(thread, thisObjHandle, k));
ElementAccessor::Set(thread, thisObjHandle, k - 1, kValue, needTransition);
}
uint32_t capacity = ElementAccessor::GetElementsLength(thread, thisObjHandle);
uint32_t index = length - 1;
if (TaggedArray::ShouldTrim(capacity, index)) {
elements->Trim(thread, index);
} else {
ElementAccessor::Set(thread, thisObjHandle, index, holeHandle, needTransition);
}
receiver->SetArrayLength(thread, index);
return result->IsHole() ? JSTaggedValue::Undefined() : result.GetTaggedValue();
}
bool JSStableArray::WorthUseTreeString(uint32_t sepLength, size_t allocateLength, uint32_t len)
{
if (allocateLength >= TREE_STRING_THRESHOLD) {
size_t treeStringElementNum = (sepLength == 0) ? (len - 1) : (2 * (len - 1));
if (treeStringElementNum * TreeString::SIZE <= allocateLength) {
return true;
}
}
return false;
}
template <typename Container>
JSTaggedValue JSStableArray::JoinUseTreeString(const JSThread *thread, const JSHandle<JSTaggedValue> receiverValue,
const JSHandle<EcmaString> sepStringHandle, uint32_t sepLength,
Container &arrElements, uint32_t elemNum)
{
if (sepLength != 0 && elemNum > 1) {
for (uint32_t k = 0; k < elemNum - 1; k++) {
arrElements[k] = JSHandle<EcmaString>(
thread, EcmaStringAccessor::Concat(thread->GetEcmaVM(), arrElements[k], sepStringHandle));
RETURN_EXCEPTION_AND_POP_JOINSTACK(thread, receiverValue);
}
}
while (elemNum > 1) {
uint32_t newNum = (elemNum + 1) / NUM_2;
for (uint32_t i = 0; i < elemNum / NUM_2; ++i) {
arrElements[i] = JSHandle<EcmaString>(
thread,
EcmaStringAccessor::Concat(thread->GetEcmaVM(), arrElements[NUM_2 * i], arrElements[NUM_2 * i + 1])
);
RETURN_EXCEPTION_AND_POP_JOINSTACK(thread, receiverValue);
}
if (elemNum % NUM_2 == 1) {
arrElements[newNum - 1] = arrElements[elemNum - 1];
}
elemNum = newNum;
}
ArrayJoinStack::Pop(thread, receiverValue);
return arrElements[0].GetTaggedValue();
}
template <typename Container>
void JSStableArray::ProcessElements(JSThread *thread, JSHandle<JSTaggedValue> receiverValue, uint32_t len,
Container &arrElements, bool &isOneByte, uint64_t &allocateLength)
{
JSMutableHandle<JSTaggedValue> elementHandle(thread, JSTaggedValue::Undefined());
JSHandle<JSObject> obj(thread, receiverValue.GetTaggedValue());
JSTaggedValue element = JSTaggedValue::Undefined();
for (uint32_t k = 0; k < len; k++) {
if (receiverValue->IsStableJSArray(thread) || receiverValue->IsJSSharedArray()) {
element = k < ElementAccessor::GetElementsLength(thread, obj) ? ElementAccessor::Get(thread, obj, k)
: JSTaggedValue::Hole();
} else {
element = JSArray::FastGetPropertyByValue(thread, receiverValue, k).GetTaggedValue();
RETURN_IF_ABRUPT_COMPLETION(thread);
}
if (!element.IsUndefinedOrNull() && !element.IsHole()) {
if (!element.IsString()) {
elementHandle.Update(element);
JSHandle<EcmaString> strElement = JSTaggedValue::ToString(thread, elementHandle);
RETURN_IF_ABRUPT_COMPLETION(thread);
element = strElement.GetTaggedValue();
}
auto nextStr = EcmaString::Cast(element.GetTaggedObject());
arrElements[k] = JSHandle<EcmaString>(thread, nextStr);
isOneByte = isOneByte & EcmaStringAccessor(nextStr).IsUtf8();
allocateLength += EcmaStringAccessor(nextStr).GetLength();
} else {
arrElements[k] = JSHandle<EcmaString>(thread->GlobalConstants()->GetHandledEmptyString());
}
}
}
template <typename Container>
JSTaggedValue JSStableArray::DoStableArrayJoin(JSThread *thread, JSHandle<JSTaggedValue> receiverValue, uint32_t len,
Container &arrElements, bool &isOneByte, uint32_t sep,
uint32_t sepLength, JSHandle<EcmaString> sepStringHandle)
{
uint64_t allocateLength = 0;
ProcessElements(thread, receiverValue, len, arrElements, isOneByte, allocateLength);
RETURN_EXCEPTION_AND_POP_JOINSTACK(thread, receiverValue);
if (len > 0) {
allocateLength += static_cast<uint64_t>(sepLength) * (len - 1);
}
if (allocateLength > BaseString::MAX_STRING_LENGTH) {
ArrayJoinStack::Pop(thread, receiverValue);
THROW_RANGE_ERROR_AND_RETURN(thread, "Invalid string length", JSTaggedValue::Exception());
}
if (WorthUseTreeString(sepLength, allocateLength, len)) {
return JoinUseTreeString(thread, receiverValue, sepStringHandle, sepLength, arrElements, len);
}
auto newString =
EcmaStringAccessor::CreateLineString(thread->GetEcmaVM(), static_cast<size_t>(allocateLength), isOneByte);
int current = 0;
{
DISALLOW_GARBAGE_COLLECTION;
for (uint32_t k = 0; k < len; k++) {
if (k > 0) {
if (sepLength == 1) {
EcmaStringAccessor(newString).Set(current, static_cast<uint16_t>(sep));
} else if (sepLength > 1) {
EcmaStringAccessor::ReadData(thread, newString, *sepStringHandle, current,
allocateLength - static_cast<uint32_t>(current), sepLength);
}
current += static_cast<int>(sepLength);
}
JSHandle<EcmaString> nextStr = arrElements[k];
int nextLength = static_cast<int>(EcmaStringAccessor(nextStr).GetLength());
EcmaStringAccessor::ReadData(thread, newString, *nextStr, current,
allocateLength - static_cast<uint32_t>(current), nextLength);
current += nextLength;
}
}
ASSERT_PRINT(isOneByte == EcmaStringAccessor::CanBeCompressed(newString),
"isOneByte does not match the real value!");
JSHandle<JSTaggedValue> stringValue(thread, newString);
ArrayJoinStack::Pop(thread, receiverValue);
return stringValue.GetTaggedValue();
}
JSTaggedValue JSStableArray::Join(JSHandle<JSTaggedValue> receiverValue, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
const GlobalEnvConstants *globalConst = thread->GlobalConstants();
JSHandle<JSObject> obj(thread, receiverValue.GetTaggedValue());
int64_t len = base::ArrayHelper::GetArrayLength(thread, receiverValue);
int sep = ',';
uint32_t sepLength = 1;
JSHandle<JSTaggedValue> sepHandle = base::BuiltinsBase::GetCallArg(argv, 0);
JSHandle<EcmaString> sepStringHandle;
if (sepHandle->IsUndefined()) {
sepHandle = globalConst->GetHandledCommaString();
sepStringHandle = JSTaggedValue::ToString(thread, sepHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else {
sepStringHandle = JSTaggedValue::ToString(thread, sepHandle);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
sepLength = EcmaStringAccessor(sepStringHandle).GetLength();
if (sepLength == 1) {
sep = EcmaStringAccessor(sepStringHandle).Get<false>(thread, 0);
}
}
bool isOneByte = EcmaStringAccessor(sepStringHandle).IsUtf8();
if (len == 0 || !ArrayJoinStack::Push(thread, receiverValue)) {
return globalConst->GetEmptyString();
}
if (len == 1) {
isOneByte = true;
}
if (len <= USE_STACK_MEMORY_THRESHOLD) {
std::array<JSHandle<EcmaString>, USE_STACK_MEMORY_THRESHOLD> arr;
return DoStableArrayJoin(thread, receiverValue, len, arr, isOneByte, sep, sepLength, sepStringHandle);
} else {
CVector<JSHandle<EcmaString>> vec(len);
return DoStableArrayJoin(thread, receiverValue, len, vec, isOneByte, sep, sepLength, sepStringHandle);
}
}
JSTaggedValue JSStableArray::HandleFindIndexOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSHandle<JSTaggedValue> thisArgHandle, uint32_t &k)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
uint64_t len = static_cast<uint64_t>(base::ArrayHelper::GetArrayLength(thread, thisObjVal));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSTaggedValue callResult = base::BuiltinsBase::GetTaggedBoolean(false);
const int32_t argsLength = 3;
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
while (k < len) {
JSTaggedValue val = ElementAccessor::Get(thread, thisObjHandle, k);
if (val.IsHole()) {
auto res = JSArray::FastGetPropertyByValue(thread, thisObjVal, k).GetTaggedValue();
if (res.IsHole()) {
kValue.Update(JSTaggedValue::Undefined());
} else {
kValue.Update(res);
}
} else {
kValue.Update(val);
}
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, callResult);
if (callResult.ToBoolean()) {
return callResult;
}
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
return callResult;
}
}
return callResult;
}
JSTaggedValue JSStableArray::HandleFindLastIndexOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSHandle<JSTaggedValue> thisArgHandle, int64_t &k)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSTaggedValue callResult = base::BuiltinsBase::GetTaggedBoolean(false);
const int32_t argsLength = 3;
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
while (k >= 0) {
JSTaggedValue val = ElementAccessor::Get(thread, thisObjHandle, k);
if (val.IsHole()) {
auto res = JSArray::FastGetPropertyByValue(thread, thisObjVal, k).GetTaggedValue();
if (res.IsHole()) {
kValue.Update(JSTaggedValue::Undefined());
} else {
kValue.Update(res);
}
} else {
kValue.Update(val);
}
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, callResult);
if (callResult.ToBoolean()) {
return callResult;
}
k--;
if (base::ArrayHelper::GetArrayLength(thread, thisObjVal) <= k) {
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
return callResult;
}
if (!thisObjVal->IsStableJSArray(thread)) {
return callResult;
}
}
return callResult;
}
JSTaggedValue JSStableArray::HandleEveryOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSHandle<JSTaggedValue> thisArgHandle, uint32_t &k)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
uint64_t len = static_cast<uint64_t>(base::ArrayHelper::GetArrayLength(thread, thisObjVal));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
const int32_t argsLength = 3;
JSTaggedValue callResult = base::BuiltinsBase::GetTaggedBoolean(true);
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
while (k < len) {
kValue.Update(ElementAccessor::Get(thread, thisObjHandle, k));
if (!kValue.GetTaggedValue().IsHole()) {
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
} else if (JSTaggedValue::HasProperty(thread, thisObjVal, k)) {
JSHandle<JSTaggedValue> kValue1 = JSArray::FastGetPropertyByValue(thread, thisObjVal, k);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue1.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
if (!callResult.ToBoolean()) {
return base::BuiltinsBase::GetTaggedBoolean(false);
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
return base::BuiltinsBase::GetTaggedBoolean(true);
}
}
return base::BuiltinsBase::GetTaggedBoolean(true);
}
JSTaggedValue JSStableArray::HandleSomeOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSHandle<JSTaggedValue> thisArgHandle, uint32_t &k)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
uint64_t len = static_cast<uint64_t>(base::ArrayHelper::GetArrayLength(thread, thisObjVal));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
const int32_t argsLength = 3;
JSTaggedValue callResult = base::BuiltinsBase::GetTaggedBoolean(false);
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
while (k < len) {
kValue.Update(ElementAccessor::Get(thread, thisObjHandle, k));
if (!kValue.GetTaggedValue().IsHole()) {
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
} else if (JSTaggedValue::HasProperty(thread, thisObjVal, k)) {
JSHandle<JSTaggedValue> kValue1 = JSArray::FastGetPropertyByValue(thread, thisObjVal, k);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue1.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
if (callResult.ToBoolean()) {
return base::BuiltinsBase::GetTaggedBoolean(true);
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
return base::BuiltinsBase::GetTaggedBoolean(false);
}
}
return base::BuiltinsBase::GetTaggedBoolean(false);
}
JSTaggedValue JSStableArray::HandleforEachOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSHandle<JSTaggedValue> thisArgHandle, uint32_t len, uint32_t &k)
{
JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
const int32_t argsLength = 3;
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) <= k) {
return base::BuiltinsBase::GetTaggedBoolean(false);
}
while (k < len) {
kValue.Update(ElementAccessor::Get(thread, thisObjHandle, k));
if (!kValue.GetTaggedValue().IsHole()) {
key.Update(JSTaggedValue(k));
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), key.GetTaggedValue(), thisObjVal.GetTaggedValue());
JSTaggedValue funcResult = JSFunction::Call(info);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult);
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
} else if (JSTaggedValue::HasProperty(thread, thisObjVal, k)) {
key.Update(JSTaggedValue(k));
JSHandle<JSTaggedValue> kValue1 = JSArray::FastGetPropertyByValue(thread, thisObjVal, k);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue1.GetTaggedValue(), key.GetTaggedValue(), thisObjVal.GetTaggedValue());
JSTaggedValue funcResult = JSFunction::Call(info);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, funcResult);
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
break;
}
}
return base::BuiltinsBase::GetTaggedBoolean(true);
}
template <RBMode mode, class Predicate>
const JSTaggedType *JSStableArray::IndexOfElements(JSThread *thread, Span<const JSTaggedType> elements,
IndexOfOptions options, Predicate predicate)
{
static_assert(std::is_invocable_r_v<bool, Predicate, JSTaggedType>, "Invalid call signature.");
if (options.reversedOrder) {
for (auto cur = elements.end() - 1; cur >= elements.begin(); --cur) {
if (UNLIKELY(std::invoke(predicate, Barriers::GetTaggedValue<mode>(thread, ToUintPtr(cur))))) {
return cur;
}
}
} else {
for (auto cur = elements.begin(); cur < elements.end(); ++cur) {
if (UNLIKELY(std::invoke(predicate, Barriers::GetTaggedValue<mode>(thread, ToUintPtr(cur))))) {
return cur;
}
}
}
return nullptr;
}
const JSTaggedType *JSStableArray::IndexOfUndefined(JSThread *thread, Span<const JSTaggedType> elements,
IndexOfOptions options, bool isMutant)
{
if (isMutant) {
if (!options.holeAsUndefined) {
return nullptr;
}
return IndexOfElements(thread, elements, options,
[](JSTaggedType rawValue) { return rawValue == base::SPECIAL_HOLE; });
}
if (!options.holeAsUndefined) {
return IndexOfElements(thread, elements, options,
[](JSTaggedType taggedValue) { return JSTaggedValue(taggedValue).IsUndefined(); });
}
return IndexOfElements(thread, elements, options, [](JSTaggedType taggedValue) {
return JSTaggedValue(taggedValue).IsHole() || JSTaggedValue(taggedValue).IsUndefined();
});
}
const JSTaggedType *JSStableArray::IndexOfTaggedZero(JSThread *thread, Span<const JSTaggedType> elements,
IndexOfOptions options)
{
return IndexOfElements(thread, elements, options,
[](JSTaggedType taggedValue) { return JSTaggedValue(taggedValue).IsExactlyZero(); });
}
const JSTaggedType *JSStableArray::IndexOfInt(JSThread *thread, Span<const JSTaggedType> elements,
JSTaggedValue searchElement, IndexOfOptions options,
bool isMutantInt32Array)
{
ASSERT(!searchElement.IsUndefined());
int32_t searchValue;
if (searchElement.IsInt()) {
searchValue = searchElement.GetInt();
} else if (searchElement.WithinInt32(true)) {
searchValue = static_cast<int32_t>(searchElement.GetDouble());
} else {
return nullptr;
}
if (isMutantInt32Array) {
return IndexOfElements(thread, elements, options, [searchValue](JSTaggedType rawValue) {
return rawValue != base::SPECIAL_HOLE && searchValue == static_cast<int32_t>(rawValue);
});
}
if (searchValue == 0) {
return IndexOfTaggedZero(thread, elements, options);
}
JSTaggedType taggedInt32 = JSTaggedValue(searchValue).GetRawData();
JSTaggedType taggedDouble = JSTaggedValue(static_cast<double>(searchValue)).GetRawData();
return IndexOfElements(thread, elements, options, [taggedInt32, taggedDouble](JSTaggedType taggedValue) {
return taggedValue == taggedInt32 || taggedValue == taggedDouble;
});
}
const JSTaggedType *JSStableArray::IndexOfDouble(JSThread *thread, Span<const JSTaggedType> elements,
JSTaggedValue searchElement, IndexOfOptions options,
bool isMutantDoubleArray)
{
ASSERT(!searchElement.IsUndefined());
if (!searchElement.IsNumber()) {
return nullptr;
}
double searchValue = searchElement.GetNumber();
if (std::isnan(searchValue)) {
if (options.compType != ComparisonType::SAME_VALUE_ZERO) {
return nullptr;
}
if (isMutantDoubleArray) {
return IndexOfElements(thread, elements, options, [](JSTaggedType rawValue) {
return rawValue != base::SPECIAL_HOLE && std::isnan(base::bit_cast<double>(rawValue));
});
}
return IndexOfElements(thread, elements, options,
[](JSTaggedType taggedValue) { return JSTaggedValue(taggedValue).IsNaN(); });
}
if (isMutantDoubleArray) {
return IndexOfElements(thread, elements, options, [searchValue](JSTaggedType rawValue) {
return searchValue == base::bit_cast<double>(rawValue);
});
}
if (searchValue == 0.0) {
return IndexOfTaggedZero(thread, elements, options);
}
JSTaggedType taggedDouble = JSTaggedValue(searchValue).GetRawData();
if (JSTaggedValue(taggedDouble).WithinInt32()) {
JSTaggedType taggedInt32 = JSTaggedValue(static_cast<int32_t>(searchValue)).GetRawData();
return IndexOfElements(thread, elements, options, [taggedDouble, taggedInt32](JSTaggedType taggedValue) {
return taggedValue == taggedDouble || taggedValue == taggedInt32;
});
}
return IndexOfElements(thread, elements, options,
[taggedDouble](JSTaggedType taggedValue) { return taggedValue == taggedDouble; });
}
const JSTaggedType *JSStableArray::IndexOfString(JSThread *thread, Span<const JSTaggedType> elements,
JSTaggedValue searchElement, IndexOfOptions options)
{
ASSERT(!searchElement.IsUndefined());
if (!searchElement.IsString()) {
return nullptr;
}
if (UNLIKELY(thread->NeedReadBarrier())) {
return IndexOfElements<RBMode::FAST_CMC_RB>(thread, elements, options,
[searchElement, thread](JSTaggedType cur) {
if (searchElement.GetRawData() == cur) {
return true;
}
JSTaggedValue curValue(cur);
if (!curValue.IsString()) {
return false;
}
return JSTaggedValue::StringCompare<RBMode::FAST_CMC_RB>(thread,
EcmaString::Cast(curValue.GetTaggedObject()),
EcmaString::Cast(searchElement.GetTaggedObject()));
});
} else {
return IndexOfElements<RBMode::FAST_NO_RB>(thread, elements, options,
[searchElement, thread](JSTaggedType cur) {
if (searchElement.GetRawData() == cur) {
return true;
}
JSTaggedValue curValue(cur);
if (!curValue.IsString()) {
return false;
}
return JSTaggedValue::StringCompare<RBMode::FAST_NO_RB>(thread,
EcmaString::Cast(curValue.GetTaggedObject()),
EcmaString::Cast(searchElement.GetTaggedObject()));
});
}
}
const JSTaggedType *JSStableArray::IndexOfBigInt(JSThread *thread, Span<const JSTaggedType> elements,
JSTaggedValue searchElement, IndexOfOptions options)
{
ASSERT(searchElement.IsBigInt());
return IndexOfElements(thread, elements, options, [searchElement](JSTaggedType cur) {
if (searchElement.GetRawData() == cur) {
return true;
}
JSTaggedValue curValue(cur);
if (!curValue.IsBigInt()) {
return false;
}
return BigInt::Equal(curValue, searchElement);
});
}
const JSTaggedType *JSStableArray::IndexOfObjectAddress(JSThread *thread, Span<const JSTaggedType> elements,
JSTaggedValue searchElement, IndexOfOptions options)
{
ASSERT(searchElement.IsObject());
JSTaggedType targetAddress = searchElement.GetRawData();
return IndexOfElements(thread, elements, options,
[targetAddress](JSTaggedType cur) { return cur == targetAddress; });
}
JSTaggedValue JSStableArray::IndexOfDispatch(JSThread *thread, JSHandle<JSTaggedValue> receiver,
JSHandle<JSTaggedValue> searchElementHandle, uint32_t from, uint32_t len,
IndexOfOptions options)
{
DISALLOW_GARBAGE_COLLECTION;
const JSTaggedType *data = nullptr;
JSTaggedValue elementsValue = JSHandle<JSObject>::Cast(receiver)->GetElements(thread);
bool isMutant = elementsValue.IsMutantTaggedArray();
if (isMutant) {
JSHandle<MutantTaggedArray> elements(thread, elementsValue);
data = elements->GetData();
} else {
JSHandle<TaggedArray> elements(thread, elementsValue);
data = elements->GetData();
}
Span<const JSTaggedType> range;
if (options.reversedOrder) {
range = {data, data + from + 1};
} else {
range = {data + from, data + len};
}
ElementsKind kind = JSHandle<JSObject>::Cast(receiver)->GetClass()->GetElementsKind();
JSTaggedValue searchElement = searchElementHandle.GetTaggedValue();
const JSTaggedType *foundPos = nullptr;
if (searchElement.IsUndefined()) {
foundPos = IndexOfUndefined(thread, range, options, isMutant);
} else if (isMutant) {
LOG_DEBUGGER(DEBUG) << "IndexOfDispatch: isMutant";
if (Elements::IsIntOrHoleInt(kind)) {
foundPos = IndexOfInt(thread, range, searchElement, options, true);
} else {
ASSERT(Elements::IsInNumbers(kind));
foundPos = IndexOfDouble(thread, range, searchElement, options, true);
}
} else if (searchElement.IsInt() || Elements::IsIntOrHoleInt(kind)) {
foundPos = IndexOfInt(thread, range, searchElement, options, false);
} else if (searchElement.IsDouble() || Elements::IsNumberOrHoleNumber(kind)) {
foundPos = IndexOfDouble(thread, range, searchElement, options, false);
} else if (searchElement.IsString() || Elements::IsStringOrHoleString(kind)) {
foundPos = IndexOfString(thread, range, searchElement, options);
} else if (searchElement.IsBigInt()) {
foundPos = IndexOfBigInt(thread, range, searchElement, options);
} else {
foundPos = IndexOfObjectAddress(thread, range, searchElement, options);
}
if (options.returnType == IndexOfReturnType::TAGGED_FOUND_INDEX) {
return foundPos == nullptr ? JSTaggedValue(-1) : JSTaggedValue(static_cast<int32_t>(foundPos - data));
} else {
ASSERT(options.returnType == IndexOfReturnType::TAGGED_FOUND_OR_NOT);
return JSTaggedValue(foundPos != nullptr);
}
}
JSTaggedValue JSStableArray::Includes(JSThread *thread, JSHandle<JSTaggedValue> receiver,
JSHandle<JSTaggedValue> searchElement, uint32_t from, uint32_t len)
{
IndexOfOptions options;
options.compType = ComparisonType::SAME_VALUE_ZERO;
options.returnType = IndexOfReturnType::TAGGED_FOUND_OR_NOT;
options.holeAsUndefined = true;
options.reversedOrder = false;
return IndexOfDispatch(thread, receiver, searchElement, from, len, options);
}
JSTaggedValue JSStableArray::IndexOf(JSThread *thread, JSHandle<JSTaggedValue> receiver,
JSHandle<JSTaggedValue> searchElement, uint32_t from, uint32_t len)
{
IndexOfOptions options;
options.compType = ComparisonType::STRICT_EQUAL;
options.returnType = IndexOfReturnType::TAGGED_FOUND_INDEX;
options.holeAsUndefined = false;
options.reversedOrder = false;
return IndexOfDispatch(thread, receiver, searchElement, from, len, options);
}
JSTaggedValue JSStableArray::LastIndexOf(JSThread *thread, JSHandle<JSTaggedValue> receiver,
JSHandle<JSTaggedValue> searchElement, uint32_t from, uint32_t len)
{
IndexOfOptions options;
options.compType = ComparisonType::STRICT_EQUAL;
options.returnType = IndexOfReturnType::TAGGED_FOUND_INDEX;
options.holeAsUndefined = false;
options.reversedOrder = true;
return IndexOfDispatch(thread, receiver, searchElement, from, len, options);
}
JSTaggedValue JSStableArray::Filter(JSHandle<JSObject> newArrayHandle, JSHandle<JSObject> thisObjHandle,
EcmaRuntimeCallInfo *argv, uint32_t &k, uint32_t &toIndex)
{
JSThread *thread = argv->GetThread();
JSHandle<JSTaggedValue> callbackFnHandle = base::BuiltinsBase::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisArgHandle = base::BuiltinsBase::GetCallArg(argv, 1);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> toIndexHandle(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
const int32_t argsLength = 3;
uint64_t len = static_cast<uint64_t>(base::ArrayHelper::GetArrayLength(thread, thisObjVal));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
while (k < len) {
JSTaggedValue value = ElementAccessor::Get(thread, thisObjHandle, k);
if (value.IsHole() && JSTaggedValue::HasProperty(thread, thisObjVal, k)) {
value = JSArray::FastGetPropertyByValue(thread, thisObjVal, k).GetTaggedValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
kValue.Update(value);
if (!kValue.GetTaggedValue().IsHole()) {
key.Update(JSTaggedValue(k));
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), key.GetTaggedValue(), thisObjVal.GetTaggedValue());
JSTaggedValue callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
bool boolResult = callResult.ToBoolean();
if (boolResult) {
if (LIKELY(newArrayHandle->IsExtensible())) {
JSArray::TryFastCreateDataProperty(thread, newArrayHandle, toIndex, kValue);
} else {
toIndexHandle.Update(JSTaggedValue(toIndex));
JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, toIndexHandle, kValue);
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
toIndex++;
}
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
break;
}
}
return base::BuiltinsBase::GetTaggedDouble(true);
}
JSTaggedValue JSStableArray::Map(JSHandle<JSObject> newArrayHandle, JSHandle<JSObject> thisObjHandle,
EcmaRuntimeCallInfo *argv, uint32_t &k, uint32_t len)
{
JSThread *thread = argv->GetThread();
JSHandle<JSTaggedValue> callbackFnHandle = base::BuiltinsBase::GetCallArg(argv, 0);
JSHandle<JSTaggedValue> thisArgHandle = base::BuiltinsBase::GetCallArg(argv, 1);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSMutableHandle<JSTaggedValue> mapResultHandle(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Undefined());
const int32_t argsLength = 3;
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
while (k < len) {
JSTaggedValue value = ElementAccessor::Get(thread, thisObjHandle, k);
if (value.IsHole() && JSTaggedValue::HasProperty(thread, thisObjVal, k)) {
value = JSArray::FastGetPropertyByValue(thread, thisObjVal, k).GetTaggedValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
kValue.Update(value);
if (!kValue.GetTaggedValue().IsHole()) {
key.Update(JSTaggedValue(k));
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), key.GetTaggedValue(), thisObjVal.GetTaggedValue());
JSTaggedValue mapResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
mapResultHandle.Update(mapResult);
if (LIKELY(newArrayHandle->IsExtensible())) {
JSArray::TryFastCreateDataProperty(thread, newArrayHandle, k, mapResultHandle);
} else {
JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, k, mapResultHandle);
}
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) < len) {
len = ElementAccessor::GetElementsLength(thread, thisObjHandle);
}
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
break;
}
}
return base::BuiltinsBase::GetTaggedDouble(true);
}
JSTaggedValue JSStableArray::Reverse(JSThread *thread, JSHandle<JSObject> thisObjHandle,
int64_t &lower, uint32_t len)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
if (thisObjHandle->IsJSArray()) {
JSArray::CheckAndCopyArray(thread, JSHandle<JSArray>::Cast(thisObjHandle));
}
ElementsKind kind = thisObjHandle->GetClass()->GetElementsKind();
JSHandle<TaggedArray> elements(thread, thisObjHandle->GetElements(thread));
if (thread->IsEnableMutantArray()) {
if (kind == ElementsKind::INT || kind == ElementsKind::HOLE_INT) {
return FastReverse(thread, elements, lower, len, ElementsKind::INT);
} else if (kind == ElementsKind::NUMBER || kind == ElementsKind::HOLE_NUMBER) {
return FastReverse(thread, elements, lower, len, ElementsKind::NUMBER);
}
}
return FastReverse(thread, elements, lower, len, ElementsKind::TAGGED);
}
JSTaggedValue JSStableArray::FastReverse(JSThread *thread, JSHandle<TaggedArray> elements,
int64_t &lower, uint32_t len, ElementsKind kind)
{
JSMutableHandle<JSTaggedValue> lowerValueHandle(thread, JSTaggedValue::Undefined());
JSMutableHandle<JSTaggedValue> upperValueHandle(thread, JSTaggedValue::Undefined());
int64_t middle = std::floor(len / 2);
while (lower != middle) {
if (elements->GetLength() != len) {
break;
}
int64_t upper = static_cast<int64_t>(len) - lower - 1;
lowerValueHandle.Update(ElementAccessor::FastGet(thread, elements, lower, kind));
upperValueHandle.Update(ElementAccessor::FastGet(thread, elements, upper, kind));
ElementAccessor::FastSet(thread, elements, lower, upperValueHandle, kind);
ElementAccessor::FastSet(thread, elements, upper, lowerValueHandle, kind);
lower++;
}
return base::BuiltinsBase::GetTaggedDouble(true);
}
JSTaggedValue JSStableArray::Concat(JSThread *thread, JSHandle<JSObject> newArrayHandle,
JSHandle<JSObject> thisObjHandle, int64_t &k, int64_t &n)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
int64_t thisLen = base::ArrayHelper::GetArrayLength(thread, thisObjVal);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSMutableHandle<JSTaggedValue> toKey(thread, JSTaggedValue::Undefined());
while (k < thisLen) {
if (ElementAccessor::GetElementsLength(thread, thisObjHandle) != thisLen) {
break;
}
toKey.Update(JSTaggedValue(n));
JSTaggedValue kValue = ElementAccessor::Get(thread, thisObjHandle, k);
if (!kValue.IsHole()) {
JSObject::CreateDataPropertyOrThrow(thread, newArrayHandle, toKey, JSHandle<JSTaggedValue>(thread, kValue));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
n++;
k++;
}
return base::BuiltinsBase::GetTaggedDouble(true);
}
template JSTaggedValue JSStableArray::FastCopyFromArrayToTypedArray<TypedArrayKind::SHARED>(
JSThread *thread, JSHandle<JSTypedArray> &targetArray, DataViewType targetType,
uint64_t targetOffset, uint32_t srcLength, JSHandle<JSObject> &obj);
template JSTaggedValue JSStableArray::FastCopyFromArrayToTypedArray<TypedArrayKind::NON_SHARED>(
JSThread *thread, JSHandle<JSTypedArray> &targetArray, DataViewType targetType,
uint64_t targetOffset, uint32_t srcLength, JSHandle<JSObject> &obj);
template<TypedArrayKind typedArrayKind>
JSTaggedValue JSStableArray::FastCopyFromArrayToTypedArray(JSThread *thread, JSHandle<JSTypedArray> &targetArray,
DataViewType targetType, uint64_t targetOffset,
uint32_t srcLength, JSHandle<JSObject> &obj)
{
JSHandle<JSTaggedValue> targetBuffer(thread, targetArray->GetViewedArrayBufferOrByteArray(thread));
if (BuiltinsArrayBufferType<typedArrayKind>::Type::IsDetachedBuffer(thread, targetBuffer.GetTaggedValue())) {
THROW_TYPE_ERROR_AND_RETURN(thread, "The targetBuffer of This value is detached buffer.",
JSTaggedValue::Exception());
}
uint32_t targetLength = targetArray->GetArrayLength();
uint32_t targetByteOffset = targetArray->GetByteOffset();
uint32_t targetElementSize = TypedArrayHelper::GetSizeFromType(targetType);
if (srcLength + targetOffset > targetLength) {
THROW_RANGE_ERROR_AND_RETURN(thread, "The sum of length and targetOffset is greater than targetLength.",
JSTaggedValue::Exception());
}
uint32_t targetByteIndex = static_cast<uint32_t>(targetOffset * targetElementSize + targetByteOffset);
uint32_t elemLen = ElementAccessor::GetElementsLength(thread, obj);
if (UNLIKELY(targetArray->ContentTypeIsBigInt())) {
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Hole());
JSMutableHandle<JSTaggedValue> elem(thread, JSTaggedValue::Hole());
for (uint32_t i = 0; i < srcLength; i++) {
if (i < elemLen) {
elem.Update(ElementAccessor::Get(thread, obj, i));
} else {
elem.Update(JSTaggedValue::Hole());
}
kValue.Update(JSTaggedValue::ToBigInt(thread, elem));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
BuiltinsArrayBufferType<typedArrayKind>::Type::SetValueInBuffer(
thread, targetBuffer.GetTaggedValue(), targetByteIndex, targetType, kValue, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetByteIndex += targetElementSize;
}
} else {
double val = 0.0;
uint32_t copyLen = srcLength > elemLen ? elemLen : srcLength;
for (uint32_t i = 0; i < copyLen; i++) {
JSTaggedValue taggedVal = ElementAccessor::Get(thread, obj, i);
if (!taggedVal.IsNumber()) {
JSTaggedNumber taggedNumber = JSTaggedValue::ToNumber(thread, taggedVal);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
val = taggedNumber.GetNumber();
} else {
val = taggedVal.GetNumber();
}
BuiltinsArrayBufferType<typedArrayKind>::Type::FastSetValueInBuffer(
thread, targetBuffer.GetTaggedValue(), targetByteIndex, targetType, val, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetByteIndex += targetElementSize;
}
for (uint32_t i = copyLen; i < srcLength; i++) {
val = JSTaggedNumber(base::NAN_VALUE).GetNumber();
BuiltinsArrayBufferType<typedArrayKind>::Type::FastSetValueInBuffer(
thread, targetBuffer.GetTaggedValue(), targetByteIndex, targetType, val, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetByteIndex += targetElementSize;
}
}
return JSTaggedValue::Undefined();
}
template JSTaggedValue JSStableArray::CopyArrayToTypedArrayForCtor<TypedArrayKind::SHARED>(
JSThread *thread, JSHandle<JSTypedArray> &targetArray, DataViewType targetType,
uint64_t targetOffset, uint32_t srcLength, JSHandle<JSObject> &obj);
template JSTaggedValue JSStableArray::CopyArrayToTypedArrayForCtor<TypedArrayKind::NON_SHARED>(
JSThread *thread, JSHandle<JSTypedArray> &targetArray, DataViewType targetType,
uint64_t targetOffset, uint32_t srcLength, JSHandle<JSObject> &obj);
template<TypedArrayKind typedArrayKind>
JSTaggedValue JSStableArray::CopyArrayToTypedArrayForCtor(JSThread *thread, JSHandle<JSTypedArray> &targetArray,
DataViewType targetType, uint64_t targetOffset,
uint32_t srcLength, JSHandle<JSObject> &obj)
{
JSHandle<JSTaggedValue> targetBuffer(thread, targetArray->GetViewedArrayBufferOrByteArray(thread));
if (BuiltinsArrayBufferType<typedArrayKind>::Type::IsDetachedBuffer(thread, targetBuffer.GetTaggedValue())) {
THROW_TYPE_ERROR_AND_RETURN(thread, "The targetBuffer of This value is detached buffer.",
JSTaggedValue::Exception());
}
uint32_t targetLength = targetArray->GetArrayLength();
uint32_t targetByteOffset = targetArray->GetByteOffset();
uint32_t targetElementSize = TypedArrayHelper::GetSizeFromType(targetType);
if (srcLength + targetOffset > targetLength) {
THROW_RANGE_ERROR_AND_RETURN(thread, "The sum of length and targetOffset is greater than targetLength.",
JSTaggedValue::Exception());
}
uint32_t targetByteIndex = static_cast<uint32_t>(targetOffset * targetElementSize + targetByteOffset);
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> elements(thread, obj->GetElements(thread));
uint32_t elemLen = elements->GetLength();
if (UNLIKELY(targetArray->ContentTypeIsBigInt())) {
JSHandle<TaggedArray> copyElememts = factory->CopyArray(elements, elemLen, elemLen);
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Hole());
JSMutableHandle<JSTaggedValue> elem(thread, JSTaggedValue::Hole());
for (uint32_t i = 0; i < srcLength; i++) {
if (i < elemLen) {
elem.Update(copyElememts->Get(thread, i));
} else {
elem.Update(JSTaggedValue::Hole());
}
kValue.Update(JSTaggedValue::ToBigInt(thread, elem));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
BuiltinsArrayBufferType<typedArrayKind>::Type::SetValueInBuffer(
thread, targetBuffer.GetTaggedValue(), targetByteIndex, targetType, kValue, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetByteIndex += targetElementSize;
}
} else {
double val = 0.0;
uint32_t copyLen = srcLength > elemLen ? elemLen : srcLength;
JSHandle<TaggedArray> copyElememts = factory->CopyArray(elements, copyLen, copyLen);
for (uint32_t i = 0; i < copyLen; i++) {
JSTaggedValue taggedVal = copyElememts->Get(thread, i);
if (!taggedVal.IsNumber()) {
JSTaggedNumber taggedNumber = JSTaggedValue::ToNumber(thread, taggedVal);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
val = taggedNumber.GetNumber();
} else {
val = taggedVal.GetNumber();
}
BuiltinsArrayBufferType<typedArrayKind>::Type::FastSetValueInBuffer(
thread, targetBuffer.GetTaggedValue(), targetByteIndex, targetType, val, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetByteIndex += targetElementSize;
}
for (uint32_t i = copyLen; i < srcLength; i++) {
val = JSTaggedNumber(base::NAN_VALUE).GetNumber();
BuiltinsArrayBufferType<typedArrayKind>::Type::FastSetValueInBuffer(
thread, targetBuffer.GetTaggedValue(), targetByteIndex, targetType, val, true);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
targetByteIndex += targetElementSize;
}
}
return JSTaggedValue::Undefined();
}
JSTaggedValue JSStableArray::At(JSHandle<JSArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
uint32_t thisLen = receiver->GetArrayLength();
if (thisLen == 0) {
return JSTaggedValue::Undefined();
}
JSTaggedNumber index = JSTaggedValue::ToInteger(thread, base::BuiltinsBase::GetCallArg(argv, 0));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int64_t relativeIndex = index.GetNumber();
int64_t k = 0;
if (relativeIndex >= 0) {
k = relativeIndex;
} else {
k = static_cast<int64_t>(thisLen) + relativeIndex;
}
if (k < 0 || k >= thisLen) {
return JSTaggedValue::Undefined();
}
auto result = JSTaggedValue::Hole();
result = ElementAccessor::Get(thread, JSHandle<JSObject>::Cast(receiver), k);
return result.IsHole() ? JSTaggedValue::Undefined() : result;
}
JSTaggedValue JSStableArray::At(JSHandle<JSSharedArray> receiver, EcmaRuntimeCallInfo *argv)
{
JSThread *thread = argv->GetThread();
uint32_t thisLen = receiver->GetArrayLength();
if (thisLen == 0) {
return JSTaggedValue::Undefined();
}
JSTaggedNumber index = JSTaggedValue::ToInteger(thread, base::BuiltinsBase::GetCallArg(argv, 0));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
int64_t relativeIndex = index.GetNumber();
int64_t k = 0;
if (relativeIndex >= 0) {
k = relativeIndex;
} else {
k = static_cast<int64_t>(thisLen) + relativeIndex;
}
if (k < 0 || k >= thisLen) {
return JSTaggedValue::Undefined();
}
auto result = JSTaggedValue::Hole();
result = ElementAccessor::Get(thread, JSHandle<JSObject>::Cast(receiver), k);
return result.IsHole() ? JSTaggedValue::Undefined() : result;
}
JSTaggedValue JSStableArray::With(JSThread *thread, JSHandle<JSArray> receiver,
int64_t insertCount, int64_t index, JSHandle<JSTaggedValue> value)
{
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> undefinedHandle(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> newArray = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSObject> newArrayHandle(newArray);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
int64_t len = base::ArrayHelper::GetLength(thread, thisObjVal);
TaggedArray *destElements = TaggedArray::Cast(newArrayHandle->GetElements(thread).GetTaggedObject());
if (insertCount > ElementAccessor::GetElementsLength(thread, newArrayHandle)) {
destElements = *JSObject::GrowElementsCapacity(thread, newArrayHandle, insertCount);
}
ASSERT(!newArrayHandle->GetJSHClass()->IsDictionaryMode());
bool needTransition = true;
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Hole());
for (uint32_t idx = 0; idx < insertCount; idx++) {
if (idx == index) {
ElementAccessor::Set(thread, newArrayHandle, idx, value, needTransition);
} else {
if (idx < len) {
kValue.Update(ElementAccessor::Get(thread, thisObjHandle, idx));
} else {
kValue.Update(JSTaggedValue::Hole());
}
if (kValue->IsHole()) {
ElementAccessor::Set(thread, newArrayHandle, idx, undefinedHandle, needTransition);
} else {
ElementAccessor::Set(thread, newArrayHandle, idx, kValue, needTransition);
}
}
}
JSHandle<JSArray>::Cast(newArrayHandle)->SetArrayLength(thread, insertCount);
return newArrayHandle.GetTaggedValue();
}
Data Flow Diagram:
--------------------------------------------------------------------------------------------
Original Array: [0.........(actualStart)...(skipedStartIndex).........(realLen-1)] |
│ │ │ │ |
├─────────┤ │ │ |
│ │ │ │ |
New Array: [Part 1 ][Part 2 ][Part 3............][Possible undefined fill] |
0 actualStart Insert end insertCount-1 |
--------------------------------------------------------------------------------------------
Original Array: [0.........(realLen-1)] # realLen < actualStart |
│ │ |
├──────────────────────┤ |
│ │ |
New Array: [Copied elements........][Undefined fill..........] |
0 realLen actualStart-1 |
--------------------------------------------------------------------------------------------
Original Array: [0.........(realLen-1)] # realLen <= skipedStartIndex |
│ │ |
├─────────┬────────────┤ |
│ │ │ |
New Array: [Part 1 ][Part 2 ][Part 3: undefined fill...] |
0 actualStart i (current) insertCount-1 |
--------------------------------------------------------------------------------------------
*/
JSTaggedValue JSStableArray::ToSpliced(JSHandle<JSArray> receiver, EcmaRuntimeCallInfo *argv,
int64_t argc, int64_t actualStart, int64_t actualSkipCount, int64_t insertCount)
{
JSThread *thread = argv->GetThread();
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> undefinedHandle(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> newArray = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSObject> newArrayHandle(newArray);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
TaggedArray *destElements = TaggedArray::Cast(newArrayHandle->GetElements(thread).GetTaggedObject());
if (insertCount > ElementAccessor::GetElementsLength(thread, newArrayHandle)) {
destElements = *JSObject::GrowElementsCapacity(thread, newArrayHandle, insertCount);
}
ASSERT(!newArrayHandle->GetJSHClass()->IsDictionaryMode());
int64_t i = 0;
int64_t skipedStartIndex = actualStart + actualSkipCount;
int64_t realLen = base::ArrayHelper::GetArrayLength(thread, thisObjVal);
bool needTransition = true;
int64_t asWithEle = std::min(realLen, actualStart);
for (; i < asWithEle; i++) {
JSHandle<JSTaggedValue> kValue(thread, ElementAccessor::Get(thread, thisObjHandle, i));
if (kValue->IsHole()) {
ElementAccessor::Set(thread, newArrayHandle, i, undefinedHandle, needTransition);
} else {
ElementAccessor::Set(thread, newArrayHandle, i, kValue, needTransition);
}
}
while (i < actualStart) {
ElementAccessor::Set(thread, newArrayHandle, i++, undefinedHandle, needTransition);
}
for (uint32_t pos = 2; pos < argc; ++pos) {
auto element = base::BuiltinsBase::GetCallArg(argv, pos);
ElementAccessor::Set(thread, newArrayHandle, i, element, needTransition);
++i;
}
if (skipedStartIndex >= realLen) {
for (; i < insertCount; i++) {
ElementAccessor::Set(thread, newArrayHandle, i, undefinedHandle, needTransition);
}
JSHandle<JSArray>::Cast(newArrayHandle)->SetArrayLength(thread, insertCount);
return newArrayHandle.GetTaggedValue();
}
int64_t skipedWithEle = std::min(insertCount, realLen - skipedStartIndex + i);
for (; i < skipedWithEle; skipedStartIndex++, i++) {
JSHandle<JSTaggedValue> kValue(thread, ElementAccessor::Get(thread, thisObjHandle, skipedStartIndex));
if (kValue->IsHole()) {
ElementAccessor::Set(thread, newArrayHandle, i, undefinedHandle, needTransition);
} else {
ElementAccessor::Set(thread, newArrayHandle, i, kValue, needTransition);
}
}
while (i < insertCount) {
ElementAccessor::Set(thread, newArrayHandle, i++, undefinedHandle, needTransition);
}
JSHandle<JSArray>::Cast(newArrayHandle)->SetArrayLength(thread, insertCount);
return newArrayHandle.GetTaggedValue();
}
JSTaggedValue JSStableArray::ToReversed(JSThread *thread, JSHandle<JSArray> receiver,
int64_t insertCount)
{
JSHandle<JSObject> thisObjHandle(receiver);
JSHandle<JSTaggedValue> undefinedHandle(thread, JSTaggedValue::Undefined());
JSHandle<JSTaggedValue> newArray = JSArray::ArrayCreate(thread, JSTaggedNumber(0));
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
JSHandle<JSObject> newArrayHandle(newArray);
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
TaggedArray *destElements = TaggedArray::Cast(newArrayHandle->GetElements(thread).GetTaggedObject());
if (insertCount > ElementAccessor::GetElementsLength(thread, newArrayHandle)) {
destElements = *JSObject::GrowElementsCapacity(thread, newArrayHandle, insertCount);
}
ASSERT(!newArrayHandle->GetJSHClass()->IsDictionaryMode());
bool needTransition = true;
for (uint32_t idx = 0; idx < insertCount; idx++) {
JSHandle<JSTaggedValue> kValue(thread, ElementAccessor::Get(thread, thisObjHandle, idx));
if (kValue->IsHole()) {
ElementAccessor::Set(thread, newArrayHandle, insertCount - idx - 1,
undefinedHandle, needTransition);
} else {
ElementAccessor::Set(thread, newArrayHandle, insertCount - idx - 1, kValue, needTransition);
}
}
JSHandle<JSArray>::Cast(newArrayHandle)->SetArrayLength(thread, insertCount);
return newArrayHandle.GetTaggedValue();
}
void JSStableArray::Reduce(JSThread *thread, JSHandle<JSObject> thisObjHandle, JSHandle<JSTaggedValue> callbackFnHandle,
JSMutableHandle<JSTaggedValue> accumulator, int64_t &k, int64_t len)
{
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSTaggedValue callResult = JSTaggedValue::Undefined();
while (k < len) {
if (UNLIKELY(k >= base::ArrayHelper::GetArrayLength(thread, thisObjVal))) {
k = len;
break;
}
JSTaggedValue kValue(ElementAccessor::Get(thread, thisObjHandle, k));
if (!kValue.IsHole()) {
const uint32_t argsLength = 4;
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, undefined, undefined, argsLength);
RETURN_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(accumulator.GetTaggedValue(), kValue, JSTaggedValue(k),
thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_IF_ABRUPT_COMPLETION(thread);
accumulator.Update(callResult);
}
k++;
if (!thisObjVal->IsStableJSArray(thread)) {
break;
}
}
}
JSTaggedValue JSStableArray::Slice(JSThread *thread, JSHandle<JSObject> thisObjHandle,
int64_t &k, int64_t &count)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
int64_t len = static_cast<int64_t>(ElementAccessor::GetElementsLength(thread, thisObjHandle));
int64_t oldLen;
if (len > k + count) {
oldLen = count;
} else {
oldLen = std::max<int64_t>(len - k, 0);
}
JSHandle<JSObject> arrayObj = factory->NewAndCopyJSArrayObject(thisObjHandle, count, oldLen, k);
for (int i = 0; i < count; i++) {
JSMutableHandle<JSTaggedValue> value(thread, ElementAccessor::Get(thread, arrayObj, i));
if (value->IsHole() && JSTaggedValue::HasProperty(thread, thisObjVal, i + k)) {
value.Update(JSArray::FastGetPropertyByValue(thread, thisObjVal, i + k).GetTaggedValue());
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
ElementAccessor::Set(thread, arrayObj, i, value, true);
}
}
return arrayObj.GetTaggedValue();
}
JSHandle<TaggedArray> JSStableArray::SortIndexedProperties(JSThread *thread, const JSHandle<JSTaggedValue> &thisObjVal,
int64_t len, const JSHandle<JSTaggedValue> &callbackFnHandle,
base::HolesType holes)
{
JSHandle<JSObject> thisObj(thread, thisObjVal.GetTaggedValue());
JSHandle<TaggedArray> elements(thread, thisObj->GetElements(thread));
ElementsKind kind = thisObj->GetClass()->GetElementsKind();
if (!elements->GetClass()->IsMutantTaggedArray()) {
kind = ElementsKind::GENERIC;
}
JSHandle<TaggedArray> items(thread->GetEcmaVM()->GetFactory()->NewTaggedArray(len));
bool kRead = false;
int64_t tmp = 0;
for (int k = 0; k < len; k++) {
JSTaggedValue kValue = ElementAccessor::FastGet(thread, elements, k, kind);
if (holes == base::HolesType::SKIP_HOLES) {
kRead = (kValue != JSTaggedValue::Hole());
} else {
ASSERT(holes == base::HolesType::READ_THROUGH_HOLES);
kRead = true;
}
if (kRead) {
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
items->Set(thread, tmp++, kValue);
}
}
if (len > tmp) {
items->Trim(thread, tmp);
}
base::TimSort::Sort(thread, items, callbackFnHandle);
RETURN_VALUE_IF_ABRUPT_COMPLETION(thread, items);
return items;
}
JSTaggedValue JSStableArray::CopySortedListToReceiver(JSThread *thread, const JSHandle<JSTaggedValue> &thisObjVal,
JSHandle<TaggedArray> sortedList, uint32_t len)
{
uint32_t itemCount = sortedList->GetLength();
JSHandle<JSObject> thisObj(thisObjVal);
uint32_t newLength = std::max(JSHandle<JSArray>::Cast(thisObjVal)->GetArrayLength(), itemCount);
TaggedArray *elements = TaggedArray::Cast(thisObj->GetElements(thread).GetTaggedObject());
if (newLength > ElementAccessor::GetElementsLength(thread, thisObj)) {
elements = *JSObject::GrowElementsCapacity(thread, thisObj, newLength, true);
} else {
JSArray::CheckAndCopyArray(thread, JSHandle<JSArray>::Cast(thisObjVal));
}
JSMutableHandle<JSTaggedValue> valueHandle(thread, JSTaggedValue::Undefined());
bool needTransition = true;
for (uint32_t j = 0; j < itemCount; j++) {
valueHandle.Update(sortedList->Get(thread, j));
ElementAccessor::Set(thread, thisObj, j, valueHandle, needTransition);
}
valueHandle.Update(JSTaggedValue::Hole());
for (uint32_t j = itemCount; j < newLength; j++) {
ElementAccessor::Set(thread, thisObj, j, valueHandle, needTransition);
}
JSHandle<JSArray>::Cast(thisObj)->SetArrayLength(thread, newLength);
return thisObj.GetTaggedValue();
}
JSTaggedValue JSStableArray::Sort(JSThread *thread, const JSHandle<JSTaggedValue> &thisObjVal,
const JSHandle<JSTaggedValue> &callbackFnHandle)
{
int64_t len = base::ArrayHelper::GetArrayLength(thread, thisObjVal);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (len == 0 || len == 1) {
return thisObjVal.GetTaggedValue();
}
if (callbackFnHandle->IsUndefined()) {
JSArray::SortElementsByObject(thread, JSHandle<JSObject>::Cast(thisObjVal), callbackFnHandle);
return thisObjVal.GetTaggedValue();
}
JSHandle<TaggedArray> sortedList = JSStableArray::SortIndexedProperties(
thread, thisObjVal, len, callbackFnHandle, base::HolesType::SKIP_HOLES);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (thisObjVal->IsStableJSArray(thread)) {
CopySortedListToReceiver(thread, thisObjVal, sortedList, len);
} else {
JSArray::CopySortedListToReceiver(thread, thisObjVal, sortedList, len);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
}
return thisObjVal.GetTaggedValue();
}
JSTaggedValue JSStableArray::Fill(JSThread *thread, const JSHandle<JSObject> &thisObj,
const JSHandle<JSTaggedValue> &value, int64_t start,
int64_t end)
{
JSArray::CheckAndCopyArray(thread, JSHandle<JSArray>::Cast(thisObj));
uint32_t length = ElementAccessor::GetElementsLength(thread, thisObj);
ElementsKind oldKind = thisObj->GetClass()->GetElementsKind();
if (start == 0 && end == length) {
if (oldKind != ElementsKind::GENERIC) {
JSHClass::TransitToElementsKindUncheck(thread, thisObj, Elements::ToElementsKind(value.GetTaggedValue()));
}
} else if (JSHClass::TransitToElementsKind(thread, thisObj, value)) {
ElementsKind newKind = thisObj->GetClass()->GetElementsKind();
Elements::MigrateArrayWithKind(thread, thisObj, oldKind, newKind);
}
if (length >= end) {
if (thisObj->GetElements(thread).IsMutantTaggedArray()) {
ElementsKind kind = thisObj->GetClass()->GetElementsKind();
TaggedArray *elements = TaggedArray::Cast(thisObj->GetElements(thread));
JSTaggedValue migratedValue = JSTaggedValue(ElementAccessor::ConvertTaggedValueWithElementsKind(
value.GetTaggedValue(), kind));
for (int64_t idx = start; idx < end; idx++) {
elements->Set<false>(thread, idx, migratedValue);
}
} else {
TaggedArray *elements = TaggedArray::Cast(thisObj->GetElements(thread));
for (int64_t idx = start; idx < end; idx++) {
elements->Set(thread, idx, value);
}
}
} else {
LOG_ECMA(FATAL) << "this branch is unreachable";
UNREACHABLE();
}
if (JSHandle<JSArray>::Cast(thisObj)->GetArrayLength() < end) {
JSHandle<JSArray>::Cast(thisObj)->SetArrayLength(thread, end);
}
return thisObj.GetTaggedValue();
}
JSTaggedValue JSStableArray::HandleFindLastOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSHandle<JSTaggedValue> thisArgHandle,
JSMutableHandle<JSTaggedValue> &kValue, int64_t &k)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
const uint32_t argsLength = 3;
JSTaggedValue callResult = base::BuiltinsBase::GetTaggedBoolean(false);
while (k >= 0) {
JSTaggedValue val = ElementAccessor::Get(thread, thisObjHandle, k);
if (!val.IsHole()) {
kValue.Update(val);
} else if (JSTaggedValue::HasProperty(thread, thisObjVal, k)) {
auto res = JSArray::FastGetPropertyByValue(thread, thisObjVal, k).GetTaggedValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
kValue.Update(res);
} else {
kValue.Update(JSTaggedValue::Undefined());
}
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(kValue.GetTaggedValue(), JSTaggedValue(k), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (callResult.ToBoolean()) {
return callResult;
}
k--;
if (base::ArrayHelper::GetArrayLength(thread, thisObjVal) <= k) {
break;
}
if (!thisObjVal->IsStableJSArray(thread)) {
break;
}
}
return callResult;
}
JSTaggedValue JSStableArray::HandleReduceRightOfStable(JSThread *thread, JSHandle<JSObject> thisObjHandle,
JSHandle<JSTaggedValue> callbackFnHandle,
JSMutableHandle<JSTaggedValue> &accumulator,
JSHandle<JSTaggedValue> thisArgHandle, int64_t &k)
{
JSHandle<JSTaggedValue> thisObjVal(thisObjHandle);
JSMutableHandle<JSTaggedValue> kValue(thread, JSTaggedValue::Hole());
JSMutableHandle<JSTaggedValue> key(thread, JSTaggedValue::Undefined());
JSTaggedValue callResult = JSTaggedValue::Undefined();
JSHandle<JSTaggedValue> undefined = thread->GlobalConstants()->GetHandledUndefined();
const int32_t argsLength = 4;
int64_t len = static_cast<int64_t>(base::ArrayHelper::GetArrayLength(thread, thisObjVal));
while (k >= 0) {
key.Update(JSTaggedValue(k));
kValue.Update(ElementAccessor::Get(thread, thisObjHandle, k));
if (!kValue.GetTaggedValue().IsHole()) {
EcmaRuntimeCallInfo *info =
EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle, thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(accumulator.GetTaggedValue(), kValue.GetTaggedValue(),
key.GetTaggedValue(), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
accumulator.Update(callResult);
} else {
bool exists = JSTaggedValue::HasProperty(thread, thisObjVal, key);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
if (exists) {
auto res = JSArray::FastGetPropertyByValue(thread, thisObjVal, key).GetTaggedValue();
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
kValue.Update(res);
EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, callbackFnHandle,
thisArgHandle, undefined, argsLength);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
info->SetCallArg(accumulator.GetTaggedValue(), kValue.GetTaggedValue(),
key.GetTaggedValue(), thisObjVal.GetTaggedValue());
callResult = JSFunction::Call(info);
RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread);
accumulator.Update(callResult);
}
}
k--;
int64_t newLen = static_cast<int64_t>(base::ArrayHelper::GetArrayLength(thread, thisObjVal));
if (!thisObjVal->IsStableJSArray(thread) || newLen != len) {
return base::BuiltinsBase::GetTaggedBoolean(false);
}
}
return base::BuiltinsBase::GetTaggedBoolean(true);
}
}