* Copyright (c) 2023 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/elements.h"
#include "ecmascript/js_tagged_value-inl.h"
#include "ecmascript/tagged_array-inl.h"
namespace panda::ecmascript {
std::string Elements::GetString(ElementsKind kind)
{
return std::to_string(ToUint(kind));
}
bool Elements::IsInt(ElementsKind kind)
{
return kind == ElementsKind::INT;
}
bool Elements::IsNumber(ElementsKind kind)
{
return kind == ElementsKind::NUMBER;
}
bool Elements::IsTagged(ElementsKind kind)
{
return kind == ElementsKind::TAGGED;
}
bool Elements::IsObject(ElementsKind kind)
{
return kind == ElementsKind::OBJECT;
}
bool Elements::IsHole(ElementsKind kind)
{
static constexpr uint8_t EVEN_NUMBER = 2;
return ToUint(kind) % EVEN_NUMBER == 1;
}
ElementsKind Elements::MergeElementsKind(ElementsKind curKind, ElementsKind newKind)
{
auto result = ElementsKind(static_cast<uint8_t>(curKind) | static_cast<uint8_t>(newKind));
result = FixElementsKind(result);
return result;
}
ElementsKind Elements::FixElementsKind(ElementsKind oldKind)
{
auto result = oldKind;
switch (result) {
case ElementsKind::NONE:
case ElementsKind::INT:
case ElementsKind::NUMBER:
case ElementsKind::STRING:
case ElementsKind::OBJECT:
case ElementsKind::HOLE:
case ElementsKind::HOLE_INT:
case ElementsKind::HOLE_NUMBER:
case ElementsKind::HOLE_STRING:
case ElementsKind::HOLE_OBJECT:
break;
default:
if (IsHole(result)) {
result = ElementsKind::HOLE_TAGGED;
} else {
result = ElementsKind::TAGGED;
}
break;
}
return result;
}
ElementsKind Elements::ToElementsKind(JSTaggedValue value, ElementsKind kind)
{
return MergeElementsKind(ToElementsKind(value), kind);
}
void Elements::HandleIntKindMigration(const JSThread *thread, const JSHandle<JSObject> &object,
const ElementsKind newKind, bool needCOW)
{
if (IsStringOrNoneOrHole(newKind)) {
JSTaggedValue newElements = MigrateFromRawValueToHeapValue(thread, object, needCOW, true);
object->SetElements(thread, newElements);
} else if (newKind == ElementsKind::NUMBER || newKind == ElementsKind::HOLE_NUMBER) {
MigrateFromHoleIntToHoleNumber(thread, object);
}
}
bool Elements::IsNumberKind(const ElementsKind kind)
{
return ToUint(kind) >= Elements::ToUint(ElementsKind::NUMBER) &&
ToUint(kind) <= Elements::ToUint(ElementsKind::HOLE_NUMBER);
}
bool Elements::IsStringOrNoneOrHole(const ElementsKind kind)
{
return ToUint(kind) >= ToUint(ElementsKind::STRING) ||
kind == ElementsKind::NONE || kind == ElementsKind::HOLE;
}
void Elements::HandleNumberKindMigration(const JSThread *thread, const JSHandle<JSObject> &object,
const ElementsKind newKind, bool needCOW)
{
if (IsStringOrNoneOrHole(newKind)) {
JSTaggedValue newElements = MigrateFromRawValueToHeapValue(thread, object, needCOW, false);
object->SetElements(thread, newElements);
} else if (newKind == ElementsKind::INT || newKind == ElementsKind::HOLE_INT) {
MigrateFromHoleNumberToHoleInt(thread, object);
}
}
void Elements::HandleOtherKindMigration(const JSThread *thread, const JSHandle<JSObject> &object,
const ElementsKind newKind, bool needCOW)
{
if (newKind == ElementsKind::INT || newKind == ElementsKind::HOLE_INT) {
JSTaggedValue newElements = MigrateFromHeapValueToRawValue(thread, object, needCOW, true);
object->SetElements(thread, newElements);
} else if (IsNumberKind(newKind)) {
JSTaggedValue newElements = MigrateFromHeapValueToRawValue(thread, object, needCOW, false);
object->SetElements(thread, newElements);
}
}
void Elements::MigrateArrayWithKind(const JSThread *thread, const JSHandle<JSObject> &object,
const ElementsKind oldKind, const ElementsKind newKind)
{
if (!thread->IsEnableMutantArray()) {
return;
}
if (oldKind == newKind ||
(oldKind == ElementsKind::INT && newKind == ElementsKind::HOLE_INT) ||
(oldKind == ElementsKind::NUMBER && newKind == ElementsKind::HOLE_NUMBER)) {
return;
}
bool needCOW = object->GetElements(thread).IsCOWArray();
if (oldKind == ElementsKind::INT || oldKind == ElementsKind::HOLE_INT) {
HandleIntKindMigration(thread, object, newKind, needCOW);
} else if ((IsNumberKind(oldKind))) {
HandleNumberKindMigration(thread, object, newKind, needCOW);
} else {
HandleOtherKindMigration(thread, object, newKind, needCOW);
}
}
JSTaggedValue Elements::MigrateFromRawValueToHeapValue(const JSThread *thread, const JSHandle<JSObject> object,
bool needCOW, bool isIntKind)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<MutantTaggedArray> elements = JSHandle<MutantTaggedArray>(thread, object->GetElements(thread));
uint32_t length = elements->GetLength();
JSMutableHandle<TaggedArray> newElements(thread, JSTaggedValue::Undefined());
if (needCOW) {
newElements.Update(factory->NewCOWTaggedArray(length));
} else {
newElements.Update(factory->NewTaggedArray(length));
}
for (uint32_t i = 0; i < length; i++) {
JSTaggedType value = elements->Get(thread, i).GetRawData();
if (value == base::SPECIAL_HOLE) {
newElements->Set(thread, i, JSTaggedValue::Hole());
} else if (isIntKind) {
int convertedValue = static_cast<int>(value);
newElements->Set(thread, i, JSTaggedValue(convertedValue));
} else {
double convertedValue = base::bit_cast<double>(value);
newElements->Set(thread, i, JSTaggedValue(convertedValue));
}
}
return newElements.GetTaggedValue();
}
JSTaggedValue Elements::MigrateFromHeapValueToRawValue(const JSThread *thread, const JSHandle<JSObject> object,
bool needCOW, bool isIntKind)
{
ObjectFactory *factory = thread->GetEcmaVM()->GetFactory();
JSHandle<TaggedArray> elements = JSHandle<TaggedArray>(thread, object->GetElements(thread));
uint32_t length = elements->GetLength();
JSMutableHandle<MutantTaggedArray> newElements(thread, JSTaggedValue::Undefined());
if (needCOW) {
newElements.Update(factory->NewCOWMutantTaggedArray(length));
} else {
newElements.Update(factory->NewMutantTaggedArray(length));
}
for (uint32_t i = 0; i < length; i++) {
JSTaggedValue value = elements->Get(thread, i);
JSTaggedType convertedValue = 0;
if (value.IsHole()) {
convertedValue = base::SPECIAL_HOLE;
} else if (isIntKind) {
convertedValue = static_cast<JSTaggedType>(value.GetInt());
} else if (value.IsInt()) {
int intValue = value.GetInt();
convertedValue = base::bit_cast<JSTaggedType>(static_cast<double>(intValue));
} else {
convertedValue = base::bit_cast<JSTaggedType>(value.GetDouble());
}
newElements->Set<false>(thread, i, JSTaggedValue(convertedValue));
}
return newElements.GetTaggedValue();
}
void Elements::MigrateFromHoleIntToHoleNumber(const JSThread *thread, const JSHandle<JSObject> object)
{
JSHandle<MutantTaggedArray> elements = JSHandle<MutantTaggedArray>(thread, object->GetElements(thread));
uint32_t length = elements->GetLength();
for (uint32_t i = 0; i < length; i++) {
JSTaggedType value = elements->Get(thread, i).GetRawData();
if (value == base::SPECIAL_HOLE) {
continue;
}
int intValue = static_cast<int>(elements->Get(thread, i).GetRawData());
double convertedValue = static_cast<double>(intValue);
elements->Set<false>(thread, i, JSTaggedValue(base::bit_cast<JSTaggedType>(convertedValue)));
}
}
void Elements::MigrateFromHoleNumberToHoleInt(const JSThread *thread, const JSHandle<JSObject> object)
{
JSHandle<MutantTaggedArray> elements = JSHandle<MutantTaggedArray>(thread, object->GetElements(thread));
uint32_t length = elements->GetLength();
for (uint32_t i = 0; i < length; i++) {
JSTaggedType value = elements->Get(thread, i).GetRawData();
if (value == base::SPECIAL_HOLE) {
continue;
}
double intValue = base::bit_cast<double>(elements->Get(thread, i).GetRawData());
int64_t convertedValue = static_cast<int64_t>(intValue);
elements->Set<false>(thread, i, JSTaggedValue(base::bit_cast<JSTaggedType>(convertedValue)));
}
}
}