diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index bda67a8556adfb38b90d10fc50848080a85883f1..e5dbcba76f66ba576a8b966f493b19f8fa67ba12 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -353,7 +353,8 @@ void Builtins::InitializeGlobalObject(const JSHandle &env, const JSHa // Global object test SetFunction(env, globalObject, "print", Global::PrintEntrypoint, 0); - SetFunction(env, globalObject, "gc", Global::GcEntrypoint, 0); + SetFunction(env, globalObject, "gc", Global::GCEntrypoint, 0); + SetFunction(env, globalObject, "startGC", Global::SpecifiedGCEntrypoint, 1); #if ECMASCRIPT_ENABLE_RUNTIME_STAT SetFunction(env, globalObject, "startRuntimeStat", Global::StartRuntimeStat, 0); SetFunction(env, globalObject, "stopRuntimeStat", Global::StopRuntimeStat, 0); diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index d0a0e903806c75e976c29b6b288341e6b0010468..0cb09802074e7a3d46b1d430d018a6c479a99412 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -25,6 +25,7 @@ #include "plugins/ecmascript/runtime/interpreter/slow_runtime_helper.h" #include "plugins/ecmascript/runtime/js_invoker.h" #include "plugins/ecmascript/runtime/tagged_array-inl.h" +#include "include/thread_scopes.h" namespace panda::ecmascript::builtins { using NumberHelper = ecmascript::base::NumberHelper; @@ -652,7 +653,7 @@ JSTaggedValue BuiltinsGlobal::PrintEntrypoint(EcmaRuntimeCallInfo *msg) return JSTaggedValue::Undefined(); } -JSTaggedValue BuiltinsGlobal::GcEntrypoint(EcmaRuntimeCallInfo *msg) +JSTaggedValue BuiltinsGlobal::GCEntrypoint(EcmaRuntimeCallInfo *msg) { if (msg == nullptr) { return JSTaggedValue::Undefined(); @@ -660,7 +661,7 @@ JSTaggedValue BuiltinsGlobal::GcEntrypoint(EcmaRuntimeCallInfo *msg) JSThread *thread = msg->GetThread(); ASSERT(thread != nullptr); [[maybe_unused]] EcmaHandleScope handleScope(thread); - BUILTINS_API_TRACE(thread, Global, GcEntryPoint); + BUILTINS_API_TRACE(thread, Global, GCEntrypoint); thread->GetEcmaVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE, thread)); thread->SafepointPoll(); @@ -668,6 +669,38 @@ JSTaggedValue BuiltinsGlobal::GcEntrypoint(EcmaRuntimeCallInfo *msg) return JSTaggedValue::Undefined(); } +JSTaggedValue BuiltinsGlobal::SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg) +{ + ASSERT(msg != nullptr); + + JSThread *thread = msg->GetThread(); + ASSERT(thread != nullptr); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + BUILTINS_API_TRACE(thread, Global, SpecifiedGCEntrypoint); + + GCTaskCause reason; + auto young = thread->GlobalConstants()->GetHandledYoungGCString(); + auto mixed = thread->GlobalConstants()->GetHandledMixedGCString(); + auto full = thread->GlobalConstants()->GetHandledFullGCString(); + auto collectionType = GetCallArg(msg, 0); + + if (JSTaggedValue::StrictEqual(thread, young, collectionType)) + reason = GCTaskCause::YOUNG_GC_CAUSE; + else if (JSTaggedValue::StrictEqual(thread, mixed, collectionType)) + reason = GCTaskCause::MIXED; + else if (JSTaggedValue::StrictEqual(thread, full, collectionType)) + reason = GCTaskCause::OOM_CAUSE; + else + reason = GCTaskCause::INVALID_CAUSE; + + auto task = MakePandaUnique(reason, thread); + + ScopedNativeCodeThread s(thread); + thread->GetEcmaVM()->GetGC()->Trigger(std::move(task)); + + return JSTaggedValue::Undefined(); +} + JSTaggedValue BuiltinsGlobal::CallJsBoundFunction(EcmaRuntimeCallInfo *msg) { JSThread *thread = msg->GetThread(); diff --git a/runtime/builtins/builtins_global.h b/runtime/builtins/builtins_global.h index d269f644a2390d57a523911da43dcaccc976b3bc..3de105f8d78df22a2d454c714514debd398cc52c 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -45,7 +45,8 @@ public: static JSTaggedValue EncodeURIComponent(EcmaRuntimeCallInfo *msg); static JSTaggedValue PrintEntrypoint(EcmaRuntimeCallInfo *msg); - static JSTaggedValue GcEntrypoint(EcmaRuntimeCallInfo *msg); + static JSTaggedValue GCEntrypoint(EcmaRuntimeCallInfo *msg); + static JSTaggedValue SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue CallJsBoundFunction(EcmaRuntimeCallInfo *msg); static JSTaggedValue CallJsProxy(EcmaRuntimeCallInfo *msg); #if ECMASCRIPT_ENABLE_RUNTIME_STAT diff --git a/runtime/global_env_constants.cpp b/runtime/global_env_constants.cpp index 8ecb034f4a6702b296a788186a1ef3e2645e5f7d..a4d16a00e0276a5dbd3c5bb6e962fa58b704c97b 100644 --- a/runtime/global_env_constants.cpp +++ b/runtime/global_env_constants.cpp @@ -498,6 +498,9 @@ void GlobalEnvConstants::InitGlobalConstant(JSThread *thread) SetConstant(ConstantIndex::UNICODE_INDEX, factory->NewFromCanBeCompressString("unicode").GetTaggedValue()); SetConstant(ConstantIndex::ZERO_INDEX, factory->NewFromCanBeCompressString("0").GetTaggedValue()); SetConstant(ConstantIndex::VALUES_INDEX, factory->NewFromCanBeCompressString("values").GetTaggedValue()); + SetConstant(ConstantIndex::YOUNG_GC, factory->NewFromCanBeCompressString("young").GetTaggedValue()); + SetConstant(ConstantIndex::MIXED_GC, factory->NewFromCanBeCompressString("mixed").GetTaggedValue()); + SetConstant(ConstantIndex::FULL_GC, factory->NewFromCanBeCompressString("full").GetTaggedValue()); auto accessor = factory->NewInternalAccessor(reinterpret_cast(JSFunction::PrototypeSetter), reinterpret_cast(JSFunction::PrototypeGetter)); diff --git a/runtime/global_env_constants.h b/runtime/global_env_constants.h index d0d7e705c8aa9649d4c50ec6f6adacea81c2837e..4090151e1488f5be520ca6c26efe3829b128830f 100644 --- a/runtime/global_env_constants.h +++ b/runtime/global_env_constants.h @@ -298,7 +298,10 @@ class JSThread; V(JSTaggedValue, IndexString, INDEX_INDEX, index) \ V(JSTaggedValue, InputString, INPUT_INDEX, input) \ V(JSTaggedValue, UnicodeString, UNICODE_INDEX, unicode) \ - V(JSTaggedValue, ZeroString, ZERO_INDEX, zero) + V(JSTaggedValue, ZeroString, ZERO_INDEX, zero) \ + V(JSTaggedValue, YoungGCString, YOUNG_GC, young) \ + V(JSTaggedValue, MixedGCString, MIXED_GC, mixed) \ + V(JSTaggedValue, FullGCString, FULL_GC, full) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define GLOBAL_ENV_CONSTANT_ACCESSOR(V) \ diff --git a/runtime/runtime_call_id.h b/runtime/runtime_call_id.h index 9fd144983bfdea5349d27d9245b2782e08c7cef3..8c0c495013fa7b573a661f0fc5d0c51c51ca2100 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -320,7 +320,8 @@ namespace panda::ecmascript { V(Global, Escape) \ V(Global, Unescape) \ V(Global, PrintEntryPoint) \ - V(Global, GcEntryPoint) \ + V(Global, GCEntrypoint) \ + V(Global, SpecifiedGCEntrypoint) \ V(Global, NewobjDynrange) \ V(Global, CallJsBoundFunction) \ V(Global, CallJsProxy) \ diff --git a/tests/runtime/mem/CMakeLists.txt b/tests/runtime/mem/CMakeLists.txt index e564d75d000a64d6f3201dfdb8a93e7bba395078..3ecbe1753d8f46e08b5b8e719bd858787c4f7efa 100644 --- a/tests/runtime/mem/CMakeLists.txt +++ b/tests/runtime/mem/CMakeLists.txt @@ -15,6 +15,7 @@ cmake_minimum_required(VERSION 3.10) set(MEMTESTS_JS_SOURCES weakContainers.js + gcTests.js ) panda_add_gtest( @@ -28,6 +29,17 @@ panda_add_gtest( ${PANDA_SANITIZERS_LIST} ) +panda_add_gtest( + NO_CORES + NAME arkruntime4ecmascript_mem_gc_tests + SOURCES + gc_test.cpp + LIBRARIES + arkruntime arkassembler + SANITIZERS + ${PANDA_SANITIZERS_LIST} +) + foreach(JS_SOURCE ${MEMTESTS_JS_SOURCES}) string(REGEX REPLACE "[.]js$" ".abc" ABC_FILE ${JS_SOURCE}) add_custom_command( diff --git a/tests/runtime/mem/gcTests.js b/tests/runtime/mem/gcTests.js new file mode 100644 index 0000000000000000000000000000000000000000..064dededc8aa4ced0b41ae7c6ac075d0a37917f7 --- /dev/null +++ b/tests/runtime/mem/gcTests.js @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +var counter = 0 +function assert(result) { + if (result != true) { + throw new Error("Failed assertion №" + counter) + } + counter += 1 +} + +function fillMapWithRefs(weakMap, strongMap, count) { + for (let i = 0; i < count; ++i) { + let obj = { field: i } + weakMap.set(obj, i) + strongMap.set(obj, i) + } +} + +function checkFullGCOnWeakRefs() +{ + // only full gc clears refs + + let weakMap = new WeakMap(); + let strongMap = new Map() + fillMapWithRefs(weakMap, strongMap, 20) + assert(getContainerSize(weakMap) == 20) + + startGC("full") + + assert(getContainerSize(weakMap) == 20) + + strongMap.clear() + startGC("full") + + assert(getContainerSize(weakMap) == 0) +} + +function checkFullGCOnFinRefs() +{ + const registerFinalizer = new FinalizationRegistry(message => { + console.log(message) + }); + + let arr = new Array(100000).fill("A") + registerFinalizer.register(arr, 'Huge array is now cleaned up') + + startGC("full") + + assert(typeof arr == 'undefined') +} + +function checkStoppingGC() +{ + // young + startGC("young") + + assert(getGCPhase() == 0) + + // mixed + startGC("mixed") + + assert(getGCPhase() == 0) + + // full + startGC("full") + + assert(getGCPhase() == 0) + +} + +checkFullGCOnWeakRefs(); \ No newline at end of file diff --git a/tests/runtime/mem/gc_test.cpp b/tests/runtime/mem/gc_test.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9a0f7e0d2516bd8e8f694df60d5fb57249db7ce1 --- /dev/null +++ b/tests/runtime/mem/gc_test.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "plugins/ecmascript/tests/runtime/common/test_helper.h" + +#include + +#include "plugins/ecmascript/runtime/ecma_vm.h" +#include "plugins/ecmascript/runtime/js_thread.h" +#include "plugins/ecmascript/runtime/napi/include/jsnapi.h" +#include "plugins/ecmascript/runtime/napi/jsnapi_helper-inl.h" +#include "plugins/ecmascript/runtime/object_factory.h" +#include "plugins/ecmascript/runtime/js_weak_container.h" +#include "plugins/ecmascript/runtime/js_tagged_value-inl.h" + +#include "libpandabase/os/thread.h" + +using namespace panda; +using namespace panda::ecmascript; + +namespace panda::test { +class StartGCTests : public testing::Test { +public: + void SetUp() override + { + panda::Logger::ComponentMask component_mask; + component_mask.set(Logger::Component::GC); + + Logger::InitializeStdLogging(Logger::Level::INFO, component_mask); + + RuntimeOption option; + option.SetGcType(RuntimeOption::GC_TYPE::G1_GC); + option.SetLogLevel(RuntimeOption::LOG_LEVEL::ERROR); + vm_ = JSNApi::CreateJSVM(option); + ASSERT_TRUE(vm_ != nullptr) << "Cannot create Runtime"; + thread_ = vm_->GetAssociatedJSThread(); + vm_->GetFactory()->SetTriggerGc(true); + } + + void TearDown() override + { + vm_->GetFactory()->SetTriggerGc(false); + JSNApi::DestroyJSVM(vm_); + Logger::Destroy(); + } + +protected: + JSThread *thread_ = nullptr; + EcmaVM *vm_ = nullptr; +}; + +Local GetContainerSize(EcmaVM *vm, Local, const Local *args, int32_t argc, void *) +{ + if (argc != 1) { + LOG_ECMA(FATAL) << "Unexpected number of arguments: " << argc; + } + JSHandle value = JSNApiHelper::ToJSHandle(args[0]); + if (!value->IsHeapObject()) { + LOG_ECMA(FATAL) << "Argument must be heap object"; + } + auto hclass = value->GetTaggedObject()->GetClass(); + if (!hclass->IsJSWeakMap() && !hclass->IsJSWeakSet()) { + LOG_ECMA(FATAL) << "Argument is not weak container"; + } + if (hclass->IsJSWeakMap()) { + auto *weakMap = JSWeakMap::Cast(value->GetHeapObject()); + return IntegerRef::New(vm, weakMap->GetSize()); + } else if (hclass->IsJSWeakSet()) { + auto *weakSet = JSWeakSet::Cast(value->GetHeapObject()); + return IntegerRef::New(vm, weakSet->GetSize()); + } + UNREACHABLE(); +} + +Local GetGCPhase(EcmaVM *vm, Local, [[maybe_unused]] const Local *args, + [[maybe_unused]] int32_t argc, void *) +{ + return IntegerRef::New(vm, static_cast(vm->GetGC()->GetGCPhase())); +} + +bool RegisterFunction(EcmaVM *vm, Local &globalObject, panda::FunctionCallback callback, const char *name) +{ + Local functionRef = FunctionRef::New(vm, callback, nullptr); + if (functionRef.IsEmpty()) { + return false; + } + Local key = StringRef::NewFromUtf8(vm, name); + bool result = globalObject->Set(vm, key, functionRef); + return result; +} + +TEST_F(StartGCTests, StartGC) +{ + const char mainFunc[] = "_GLOBAL::func_main_0"; + JSExecutionScope executionScope(vm_); + LocalScope scope(vm_); + + Local globalObject = JSNApi::GetGlobalObject(vm_); + + ASSERT_TRUE(RegisterFunction(vm_, globalObject, GetContainerSize, "getContainerSize")); + ASSERT_TRUE(RegisterFunction(vm_, globalObject, GetGCPhase, "getGCPhase")); + + const char fileName[] = "gcTests.abc"; + bool ret2 = JSNApi::Execute(vm_, fileName, mainFunc); + ASSERT_EQ(ret2, true); + Local exception = JSNApi::GetUncaughtException(vm_); + if (!exception.IsEmpty() && !exception->IsHole()) { + Local msgKey = StringRef::NewFromUtf8(vm_, "message"); + auto msg = exception->Get(vm_, msgKey)->ToString(vm_); + FAIL() << msg->ToString(); + } +} +} // namespace panda::test diff --git a/tests/runtime/mem/weakContainers.js b/tests/runtime/mem/weakContainers.js index d13e1b6b1a7efd4aa041be60afae55f0cc7687e1..5cdd8935cb50632288f6073682ed56febe9b3645 100644 --- a/tests/runtime/mem/weakContainers.js +++ b/tests/runtime/mem/weakContainers.js @@ -82,4 +82,4 @@ assert(getContainerSize(weakSet) == 1) strongSet.clear() collectGarbage() assert(getContainerSize(weakSet) == 0) -assert(validateSet(weakSet)) +assert(validateSet(weakSet)) \ No newline at end of file