/* * Copyright (c) 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_array.h" #include "ecmascript/global_env.h" #include "ecmascript/tests/test_helper.h" using namespace panda::ecmascript; namespace panda::test { class BarrierTest : public BaseTestWithScope { }; HWTEST_F_L0(BarrierTest, YoungToYoungBatchCopy) { ObjectFactory* factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 10; JSHandle srcArray = factory->NewTaggedArray(arrayLength); JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); for (uint32_t i = 0; i < arrayLength; i++) { JSHandle newFun = factory->NewJSFunction(env); srcArray->Set(thread, i, newFun); } JSHandle dstArray = factory->NewTaggedArray(arrayLength); std::set LocalToShareBeforeCopy; Region* dstRegion = Region::ObjectAddressToRange(dstArray.GetObject()); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { LocalToShareBeforeCopy.emplace(ToUintPtr(mem)); return true; }); JSTaggedValue* to = reinterpret_cast(ToUintPtr(dstArray->GetData())); JSTaggedValue* from = reinterpret_cast(ToUintPtr(srcArray->GetData())); Barriers::CopyObject(thread, *dstArray, to, from, arrayLength); // young to young, all the bitset should not be changed. dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { EXPECT_TRUE(LocalToShareBeforeCopy.count(ToUintPtr(mem))); return true; }); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray->Get(thread, i), srcArray->Get(thread, i)); } } HWTEST_F_L0(BarrierTest, BatchCopyNoBarrier) { ObjectFactory* factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 10; JSHandle srcArray = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { srcArray->Set(thread, i, JSTaggedValue(i)); } JSHandle dstArray = factory->NewTaggedArray(arrayLength); std::set LocalToShareBeforeCopy; Region* dstRegion = Region::ObjectAddressToRange(dstArray.GetObject()); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { LocalToShareBeforeCopy.emplace(ToUintPtr(mem)); return true; }); JSTaggedValue* to = reinterpret_cast(ToUintPtr(dstArray->GetData())); JSTaggedValue* from = reinterpret_cast(ToUintPtr(srcArray->GetData())); Barriers::CopyObjectPrimitive(to, from, arrayLength); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { EXPECT_TRUE(LocalToShareBeforeCopy.count(ToUintPtr(mem))); return true; }); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray->Get(thread, i), srcArray->Get(thread, i)); } JSHandle dstArray2 = factory->NewTaggedArray(arrayLength); JSTaggedValue* to2 = reinterpret_cast(ToUintPtr(dstArray2->GetData())); JSTaggedValue* from2 = reinterpret_cast(ToUintPtr(srcArray->GetData())); // barrier should also work for no heap value Barriers::CopyObject(thread, *dstArray, to2, from2, arrayLength); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray2->Get(thread, i), srcArray->Get(thread, i)); } } HWTEST_F_L0(BarrierTest, LocalToShareBatchCopy) { ObjectFactory* factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 10; JSHandle srcArray = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // string longer than 1 will be in sweepable shared heap srcArray->Set(thread, i, str); } JSHandle dstArray = factory->NewTaggedArray(arrayLength); std::set LocalToShareBeforeCopy; Region* dstRegion = Region::ObjectAddressToRange(dstArray.GetObject()); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { LocalToShareBeforeCopy.emplace(ToUintPtr(mem)); return true; }); JSTaggedValue* to = reinterpret_cast(ToUintPtr(dstArray->GetData())); JSTaggedValue* from = reinterpret_cast(ToUintPtr(srcArray->GetData())); Barriers::CopyObject(thread, *dstArray, to, from, arrayLength); std::set LocalToShareSlot; for (uint32_t i = 0; i < arrayLength; i++) { LocalToShareSlot.insert(ToUintPtr(dstArray->GetData() + i)); } // young to young, all the bitset should not be changed. dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot, &LocalToShareBeforeCopy, &dstArray, arrayLength]( void* mem) { if (!LocalToShareBeforeCopy.count(ToUintPtr(mem))) { EXPECT_GE(ToUintPtr(mem), ToUintPtr(dstArray->GetData())); EXPECT_LT(ToUintPtr(mem), ToUintPtr(dstArray->GetData()+arrayLength)); LocalToShareSlot.erase(ToUintPtr(mem)); } else { EXPECT_TRUE(LocalToShareBeforeCopy.count(ToUintPtr(mem))); } return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray->Get(thread, i), srcArray->Get(thread, i)); } } HWTEST_F_L0(BarrierTest, LocalToReadOnlyShareBatchCopy) { ObjectFactory* factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 10; JSHandle srcArray = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { JSHandle str = factory->NewFromStdString(std::to_string(i)); // One byte string is in readonly srcArray->Set(thread, i, str); } JSHandle dstArray = factory->NewTaggedArray(arrayLength); std::set LocalToShareBeforeCopy; Region* dstRegion = Region::ObjectAddressToRange(dstArray.GetObject()); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { LocalToShareBeforeCopy.emplace(ToUintPtr(mem)); return true; }); JSTaggedValue* to = reinterpret_cast(ToUintPtr(dstArray->GetData())); JSTaggedValue* from = reinterpret_cast(ToUintPtr(srcArray->GetData())); Barriers::CopyObject(thread, *dstArray, to, from, arrayLength); std::set LocalToShareSlot; for (uint32_t i = 0; i < arrayLength; i++) { LocalToShareSlot.insert(ToUintPtr(dstArray->GetData() + i)); } // young to young, all the bitset should not be changed. dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot, &LocalToShareBeforeCopy]( void* mem) { EXPECT_FALSE(LocalToShareSlot.count(ToUintPtr(mem))); EXPECT_TRUE(LocalToShareBeforeCopy.count(ToUintPtr(mem))); return true; }); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray->Get(thread, i), srcArray->Get(thread, i)); } } HWTEST_F_L0(BarrierTest, LocalToShareMixBatchCopy) { ObjectFactory* factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 10; JSHandle srcArray = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 == 0) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // One byte string is in readonly srcArray->Set(thread, i, str); } else { srcArray->Set(thread, i, JSTaggedValue(i)); } } JSHandle dstArray = factory->NewTaggedArray(arrayLength); std::set LocalToShareBeforeCopy; Region* dstRegion = Region::ObjectAddressToRange(dstArray.GetObject()); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { LocalToShareBeforeCopy.emplace(ToUintPtr(mem)); return true; }); JSTaggedValue* to = reinterpret_cast(ToUintPtr(dstArray->GetData())); JSTaggedValue* from = reinterpret_cast(ToUintPtr(srcArray->GetData())); Barriers::CopyObject(thread, *dstArray, to, from, arrayLength); std::set LocalToShareSlot; for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 == 0) { LocalToShareSlot.insert(ToUintPtr(dstArray->GetData() + i)); } } // young to young, all the bitset should not be changed. dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot, &LocalToShareBeforeCopy, &dstArray, arrayLength]( void* mem) { if (!LocalToShareBeforeCopy.count(ToUintPtr(mem))) { EXPECT_GE(ToUintPtr(mem), ToUintPtr(dstArray->GetData())); EXPECT_LT(ToUintPtr(mem), ToUintPtr(dstArray->GetData()+arrayLength)); LocalToShareSlot.erase(ToUintPtr(mem)); } else { EXPECT_TRUE(LocalToShareBeforeCopy.count(ToUintPtr(mem))); } return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray->Get(thread, i), srcArray->Get(thread, i)); } } HWTEST_F_L0(BarrierTest, OldToNewBatchCopy) { ObjectFactory* factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 10; // length 50 will be in old JSHandle srcArray = factory->NewOldSpaceTaggedArray(arrayLength); JSHandle dstArray = factory->NewOldSpaceTaggedArray(arrayLength); JSHandle env = thread->GetEcmaVM()->GetGlobalEnv(); JSHandle newFun = factory->NewJSFunction(env); // in young for (uint32_t i = 0; i < 10; i++) { srcArray->Set(thread, i, newFun); } std::set OldToNewSlot; for (uint32_t i = 0; i < arrayLength; i++) { if (Region::ObjectAddressToRange(srcArray->Get(thread, i).GetTaggedObject())->InYoungSpace()) { OldToNewSlot.insert(ToUintPtr(dstArray->GetData() + i)); } } EXPECT_TRUE(!OldToNewSlot.empty()); std::set OldToNewBeforeCopy; std::set LocalToShareBeforeCopy; Region* dstRegion = Region::ObjectAddressToRange(dstArray.GetObject()); dstRegion->IterateAllOldToNewBits([&OldToNewBeforeCopy](void* mem) { OldToNewBeforeCopy.emplace(ToUintPtr(mem)); return true; }); dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { LocalToShareBeforeCopy.emplace(ToUintPtr(mem)); return true; }); JSTaggedValue* to = reinterpret_cast(ToUintPtr(dstArray->GetData())); JSTaggedValue* from = reinterpret_cast(ToUintPtr(srcArray->GetData())); Barriers::CopyObject(thread, *dstArray, to, from, arrayLength); // young to young, all the bitset should not be changed. dstRegion->IterateAllLocalToShareBits([&LocalToShareBeforeCopy](void* mem) { EXPECT_TRUE(LocalToShareBeforeCopy.count(ToUintPtr(mem))); return true; }); // check for (uint32_t i = 0; i < arrayLength; i++) { EXPECT_EQ(dstArray->Get(thread, i), srcArray->Get(thread, i)); } } HWTEST_F_L0(BarrierTest, LocalToShareUnshift) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 40; JSHandle array = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // string longer than 1 will be in sweepable shared heap array->Set(thread, i, str); } JSHandle jsArray = JSArray::CreateArrayFromList(thread, array); JSHandle unshiftStr = factory->NewFromStdString("unshift"); auto unshiftFunc = JSTaggedValue::GetProperty(thread, JSHandle(jsArray), JSHandle(unshiftStr)).GetValue(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, unshiftFunc.GetTaggedValue(), jsArray.GetTaggedValue(), thread->GlobalConstants()->GetUndefined(), 1); info->SetCallArg(thread->GlobalConstants()->GetUndefined()); EcmaInterpreter::Execute(info); array = JSHandle(thread, jsArray->GetElements()); Region *dstRegion = Region::ObjectAddressToRange(array.GetObject()); std::set LocalToShareSlot; for (uint32_t i = 1; i < arrayLength + 1; i++) { LocalToShareSlot.insert(ToUintPtr(array->GetData() + i)); } dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot](void *mem) { LocalToShareSlot.erase(ToUintPtr(mem)); return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); } HWTEST_F_L0(BarrierTest, UnshiftBarrierMoveForward) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 3; JSHandle array = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // string longer than 1 will be in sweepable shared heap array->Set(thread, i, str); } JSHandle jsArray = JSArray::CreateArrayFromList(thread, array); JSHandle unshiftStr = factory->NewFromStdString("unshift"); auto unshiftFunc = JSTaggedValue::GetProperty(thread, JSHandle(jsArray), JSHandle(unshiftStr)).GetValue(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, unshiftFunc.GetTaggedValue(), jsArray.GetTaggedValue(), thread->GlobalConstants()->GetUndefined(), 3); info->SetCallArg(thread->GlobalConstants()->GetUndefined(), thread->GlobalConstants()->GetUndefined(), thread->GlobalConstants()->GetUndefined()); EcmaInterpreter::Execute(info); array = JSHandle(thread, jsArray->GetElements()); Region *dstRegion = Region::ObjectAddressToRange(array.GetObject()); std::set LocalToShareSlot; for (uint32_t i = 3; i < arrayLength + 3; i++) { LocalToShareSlot.insert(ToUintPtr(array->GetData() + i)); } dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot](void *mem) { LocalToShareSlot.erase(ToUintPtr(mem)); return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); } HWTEST_F_L0(BarrierTest, UnshiftBarrierMoveBackward) { if (!thread->IsAsmInterpreter()) { return; } #ifdef ENABLE_NEXT_OPTIMIZATION ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 20; JSHandle array = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 == 0) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // string longer than 1 will be in sweepable shared heap array->Set(thread, i, str); continue; } array->Set(thread, i, JSTaggedValue(1)); } JSHandle jsArray = JSArray::CreateArrayFromList(thread, array); JSHandle unshiftStr = factory->NewFromStdString("unshift"); auto unshiftFunc = JSTaggedValue::GetProperty(thread, JSHandle(jsArray), JSHandle(unshiftStr)).GetValue(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, unshiftFunc.GetTaggedValue(), jsArray.GetTaggedValue(), thread->GlobalConstants()->GetUndefined(), 1); info->SetCallArg(thread->GlobalConstants()->GetUndefined()); EcmaInterpreter::Execute(info); array = JSHandle(thread, jsArray->GetElements()); Region *dstRegion = Region::ObjectAddressToRange(array.GetObject()); std::set LocalToShareSlot; std::set LocalTaggedIntSlot; for (uint32_t i = 1; i < arrayLength + 1; i++) { if (i % 2 != 0) { LocalToShareSlot.insert(ToUintPtr(array->GetData() + i)); continue; } LocalTaggedIntSlot.insert(ToUintPtr(array->GetData() + i)); } dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot, &LocalTaggedIntSlot](void *mem) { EXPECT_FALSE(LocalTaggedIntSlot.count(ToUintPtr(mem))); LocalToShareSlot.erase(ToUintPtr(mem)); return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); #endif } HWTEST_F_L0(BarrierTest, UnshiftBarrierMoveForward1) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 3; JSHandle array = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 != 0) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // string longer than 1 will be in sweepable shared heap array->Set(thread, i, str); continue; } array->Set(thread, i, JSTaggedValue(1)); } JSHandle jsArray = JSArray::CreateArrayFromList(thread, array); JSHandle unshiftStr = factory->NewFromStdString("unshift"); auto unshiftFunc = JSTaggedValue::GetProperty(thread, JSHandle(jsArray), JSHandle(unshiftStr)).GetValue(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, unshiftFunc.GetTaggedValue(), jsArray.GetTaggedValue(), thread->GlobalConstants()->GetUndefined(), 3); info->SetCallArg(thread->GlobalConstants()->GetUndefined(), thread->GlobalConstants()->GetUndefined(), thread->GlobalConstants()->GetUndefined()); EcmaInterpreter::Execute(info); array = JSHandle(thread, jsArray->GetElements()); Region *dstRegion = Region::ObjectAddressToRange(array.GetObject()); std::set LocalToShareSlot; std::set LocalTaggedIntSlot; for (uint32_t i = 3; i < arrayLength + 3; i++) { if (i % 2 == 0) { LocalToShareSlot.insert(ToUintPtr(array->GetData() + i)); continue; } LocalTaggedIntSlot.insert(ToUintPtr(array->GetData() + i)); } dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot, &LocalTaggedIntSlot](void *mem) { EXPECT_FALSE(LocalTaggedIntSlot.count(ToUintPtr(mem))); LocalToShareSlot.erase(ToUintPtr(mem)); return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); } HWTEST_F_L0(BarrierTest, SliceBarrierMove) { ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 20; JSHandle array = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 == 0) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); // string longer than 1 will be in sweepable shared heap array->Set(thread, i, str); continue; } array->Set(thread, i, JSTaggedValue(1)); } JSHandle jsArray = JSArray::CreateArrayFromList(thread, array); JSHandle sliceStr = factory->NewFromStdString("slice"); auto unshiftFunc = JSTaggedValue::GetProperty(thread, JSHandle(jsArray), JSHandle(sliceStr)).GetValue(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, unshiftFunc.GetTaggedValue(), jsArray.GetTaggedValue(), thread->GlobalConstants()->GetUndefined(), 1); info->SetCallArg(JSTaggedValue(10)); JSTaggedValue result = EcmaInterpreter::Execute(info); JSArray *resultArray = JSArray::Cast(result.GetTaggedObject()); array = JSHandle(thread, resultArray->GetElements()); Region *dstRegion = Region::ObjectAddressToRange(array.GetObject()); std::set LocalToShareSlot; std::set LocalTaggedIntSlot; for (uint32_t i = 0; i < 10; i++) { if (i % 2 == 0) { LocalToShareSlot.insert(ToUintPtr(array->GetData() + i)); continue; } LocalTaggedIntSlot.insert(ToUintPtr(array->GetData() + i)); } dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot, &LocalTaggedIntSlot](void *mem) { EXPECT_FALSE(LocalTaggedIntSlot.count(ToUintPtr(mem))); LocalToShareSlot.erase(ToUintPtr(mem)); return true; }); EXPECT_TRUE(LocalToShareSlot.empty()); } HWTEST_F_L0(BarrierTest, LocalToShareReverse) { if (!thread->IsAsmInterpreter()) { return; } #ifdef ENABLE_NEXT_OPTIMIZATION ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); uint32_t arrayLength = 40; JSHandle array = factory->NewTaggedArray(arrayLength); for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 == 0) { JSHandle str = factory->NewFromStdString(std::to_string(i) + "_" + std::to_string(i)); array->Set(thread, i, str); } else { array->Set(thread, i, JSTaggedValue(i)); } } JSHandle jsArray = JSArray::CreateArrayFromList(thread, array); JSHandle reverseStr = factory->NewFromStdString("reverse"); auto reverseFunc = JSTaggedValue::GetProperty(thread, JSHandle(jsArray), JSHandle(reverseStr)).GetValue(); EcmaRuntimeCallInfo *info = EcmaInterpreter::NewRuntimeCallInfo(thread, reverseFunc.GetTaggedValue(), jsArray.GetTaggedValue(), thread->GlobalConstants()->GetUndefined(), 0); EcmaInterpreter::Execute(info); array = JSHandle(thread, jsArray->GetElements()); Region *dstRegion = Region::ObjectAddressToRange(array.GetObject()); std::set LocalToShareSlot1; std::set LocalToShareSlot2; for (uint32_t i = 0; i < arrayLength; i++) { if (i % 2 == 1) { LocalToShareSlot1.insert(ToUintPtr(array->GetData() + i)); } else { LocalToShareSlot2.insert(ToUintPtr(array->GetData() + i)); } } dstRegion->IterateAllLocalToShareBits([&LocalToShareSlot1, &LocalToShareSlot2](void *mem) { LocalToShareSlot1.erase(ToUintPtr(mem)); LocalToShareSlot2.erase(ToUintPtr(mem)); return true; }); EXPECT_TRUE(LocalToShareSlot1.empty()); EXPECT_EQ(LocalToShareSlot2.size(), arrayLength / 2); #endif } } // namespace panda::ecmascript