From aa7fb2c0e7c146e361c6101c7aa21a090be189cb Mon Sep 17 00:00:00 2001 From: Sannikov Roman Date: Fri, 26 Aug 2022 18:03:30 +0300 Subject: [PATCH 1/3] Add GC start and finish methods Change-Id: I9e244bb8c2b24beb1eddf3bba87c12fd1ce10494 Signed-off-by: Sannikov Roman --- runtime/builtins.cpp | 3 +- runtime/builtins/builtins_global.cpp | 50 ++++++++++- runtime/builtins/builtins_global.h | 3 +- runtime/global_env_constants.cpp | 4 + runtime/global_env_constants.h | 6 +- runtime/runtime_call_id.h | 3 +- tests/runtime/mem/CMakeLists.txt | 12 +++ tests/runtime/mem/gcTests.js | 84 ++++++++++++++++++ tests/runtime/mem/gc_test.cpp | 124 +++++++++++++++++++++++++++ tests/runtime/mem/weakContainers.js | 2 +- 10 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 tests/runtime/mem/gcTests.js create mode 100644 tests/runtime/mem/gc_test.cpp diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index fafd1278a..cdfd71dbd 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -354,7 +354,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 d0a0e9038..66543b6c0 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,51 @@ JSTaggedValue BuiltinsGlobal::GcEntrypoint(EcmaRuntimeCallInfo *msg) return JSTaggedValue::Undefined(); } +static GCTaskCause GCCauseFromString(JSThread *thread, const JSHandle &str_cause) +{ + auto young = thread->GlobalConstants()->GetHandledYoungGCString(); + auto threshold = thread->GlobalConstants()->GetHandledThresholdGCString(); + auto mixed = thread->GlobalConstants()->GetHandledMixedGCString(); + auto full = thread->GlobalConstants()->GetHandledFullGCString(); + + if (JSTaggedValue::StrictEqual(thread, young, str_cause)) { + return GCTaskCause::YOUNG_GC_CAUSE; + } + if (JSTaggedValue::StrictEqual(thread, threshold, str_cause)) { + return GCTaskCause::HEAP_USAGE_THRESHOLD_CAUSE; + } + if (JSTaggedValue::StrictEqual(thread, mixed, str_cause)) { + return GCTaskCause::MIXED; + } + if (JSTaggedValue::StrictEqual(thread, full, str_cause)) { + return GCTaskCause::OOM_CAUSE; + } + return GCTaskCause::INVALID_CAUSE; +} + +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 = GCCauseFromString(thread, GetCallArg(msg, 0)); + if (reason == GCTaskCause::INVALID_CAUSE) { + JSHandle err = + thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC cause"); + THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); + } + 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 d269f644a..3de105f8d 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 5bdfbd97a..eea1fcc3c 100644 --- a/runtime/global_env_constants.cpp +++ b/runtime/global_env_constants.cpp @@ -504,6 +504,10 @@ 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::THRESHOLD_GC, factory->NewFromCanBeCompressString("threshold").GetTaggedValue()); + SetConstant(ConstantIndex::MIXED_GC, factory->NewFromCanBeCompressString("mixed").GetTaggedValue()); + SetConstant(ConstantIndex::FULL_GC, factory->NewFromCanBeCompressString("full").GetTaggedValue()); SetConstant(ConstantIndex::FLAGS_INDEX, factory->NewFromCanBeCompressString("flags").GetTaggedValue()); SetConstant(ConstantIndex::BACKSLASH_INDEX, factory->NewFromCanBeCompressString("/").GetTaggedValue()); diff --git a/runtime/global_env_constants.h b/runtime/global_env_constants.h index a775df693..7f153ccb1 100644 --- a/runtime/global_env_constants.h +++ b/runtime/global_env_constants.h @@ -305,7 +305,11 @@ class JSThread; V(JSTaggedValue, GString, G_INDEX, g) \ V(JSTaggedValue, GroupsString, GROUPS_STRING_INDEX, groups) \ V(JSTaggedValue, YString, Y_INDEX, y) \ - V(JSTaggedValue, DollarString, DOLLAR_INDEX, dollar) + V(JSTaggedValue, DollarString, DOLLAR_INDEX, dollar) \ + V(JSTaggedValue, YoungGCString, YOUNG_GC, young) \ + V(JSTaggedValue, ThresholdGCString, THRESHOLD_GC, threshold) \ + 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 b9041b586..0979daaf2 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 e564d75d0..3ecbe1753 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 000000000..064dededc --- /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 000000000..2d4ff0cf6 --- /dev/null +++ b/tests/runtime/mem/gc_test.cpp @@ -0,0 +1,124 @@ +/* + * 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" + +namespace panda::ecmascript { +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; // NOLINT(misc-non-private-member-variables-in-classes) + EcmaVM *vm_ = nullptr; // NOLINT(misc-non-private-member-variables-in-classes) +}; + +Local GetContainerSize(EcmaVM *vm, [[maybe_unused]] Local ref, const Local *args, + int32_t argc, [[maybe_unused]] void *ptr) +{ + if (argc != 1) { + LOG_ECMA(FATAL) << "Unexpected number of arguments: " << argc; + } + // NOLINTNEXTLINE(cppcoreguideline-pro-bounds-pointer-arithmetic) + 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()); + } + if (hclass->IsJSWeakSet()) { + auto *weakSet = JSWeakSet::Cast(value->GetHeapObject()); + return IntegerRef::New(vm, weakSet->GetSize()); + } + UNREACHABLE(); +} + +Local GetGCPhase(EcmaVM *vm, [[maybe_unused]] Local ref, + [[maybe_unused]] const Local *args, [[maybe_unused]] int32_t argc, + [[maybe_unused]] void *ptr) +{ + 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) +{ + 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")); + + bool ret2 = JSNApi::Execute(vm_, "gcTests.abc", "_GLOBAL::func_main_0"); + 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::ecmascript diff --git a/tests/runtime/mem/weakContainers.js b/tests/runtime/mem/weakContainers.js index d13e1b6b1..5cdd8935c 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 -- Gitee From d7ad014032d124886d3d5e679f7ada210b1e965d Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Fri, 30 Sep 2022 10:07:11 +0300 Subject: [PATCH 2/3] Rewrite startGc test Change-Id: I07fe27c7a42e14c3252c076cffc268efd6cd8ec0 --- runtime/builtins/builtins_global.cpp | 3 - tests/CMakeLists.txt | 25 ++++++ tests/checked/CMakeLists.txt | 25 ------ tests/runtime/common/CMakeLists.txt | 46 ++++++++++ tests/runtime/common/startGc.js | 82 ++++++++++++++++++ tests/runtime/mem/CMakeLists.txt | 12 --- tests/runtime/mem/gcTests.js | 84 ------------------ tests/runtime/mem/gc_test.cpp | 124 --------------------------- 8 files changed, 153 insertions(+), 248 deletions(-) create mode 100644 tests/runtime/common/startGc.js delete mode 100644 tests/runtime/mem/gcTests.js delete mode 100644 tests/runtime/mem/gc_test.cpp diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index 66543b6c0..b6db4b38c 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -693,8 +693,6 @@ static GCTaskCause GCCauseFromString(JSThread *thread, const JSHandleGetThread(); ASSERT(thread != nullptr); [[maybe_unused]] EcmaHandleScope handleScope(thread); @@ -708,7 +706,6 @@ JSTaggedValue BuiltinsGlobal::SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg) } auto task = MakePandaUnique(reason, thread); - ScopedNativeCodeThread s(thread); thread->GetEcmaVM()->GetGC()->Trigger(std::move(task)); return JSTaggedValue::Undefined(); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e80c9018..106def2f8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -261,6 +261,31 @@ if (CMAKE_CROSSCOMPILING AND PANDA_TARGET_ARM64) # add_test_file_ecma(FILE "${CMAKE_CURRENT_SOURCE_DIR}/ecmascript-tests/js-bitops-bitwise-and.pa" COMPILER_OPTIONS --compiler-hotness-threshold=0) endif() +function(compile_file_ecma) + set(prefix ARG) + set(singleValues FILE OUTPUT_FILE WORKING_DIR) + cmake_parse_arguments(${prefix} "" "${singleValues}" "${multiValues}" ${ARGN}) + + get_filename_component(FILE_NAME_WE "${ARG_FILE}" NAME_WE) + get_filename_component(SOURCE_DIR "${ARG_FILE}" DIRECTORY) + + get_filename_component(FILE_TYPE "${ARG_FILE}" EXT) + if (${FILE_TYPE} MATCHES "js") + add_custom_command(OUTPUT "${ARG_OUTPUT_FILE}" + COMMENT "Run es2panda for ${ARG_FILE}" + COMMAND ${es2panda_bin} --opt-level 0 --output ${ARG_OUTPUT_FILE} "${ARG_FILE}" + DEPENDS es2panda "${ARG_FILE}" + WORKING_DIRECTORY "${ARG_WORKING_DIR}") + else() + # Compile assembly file + add_custom_command(OUTPUT "${ARG_OUTPUT_FILE}" + COMMENT "Building ${TEST_NAME}" + COMMAND ${PANDA_RUN_PREFIX} $ --log-file ${BUILD_LOG} ${ARG_FILE} ${ARG_OUTPUT_FILE} + DEPENDS ${assembler} "${ARG_FILE}" + WORKING_DIRECTORY "${ARG_WORKING_DIR}") + endif() +endfunction() + # ARM32 is not supported for ecmascript if(NOT PANDA_TARGET_ARM32) add_subdirectory(runtime) diff --git a/tests/checked/CMakeLists.txt b/tests/checked/CMakeLists.txt index 7f09a016d..73ab5a00d 100644 --- a/tests/checked/CMakeLists.txt +++ b/tests/checked/CMakeLists.txt @@ -11,31 +11,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -function(compile_file_ecma) - set(prefix ARG) - set(singleValues FILE OUTPUT_FILE WORKING_DIR) - cmake_parse_arguments(${prefix} "" "${singleValues}" "${multiValues}" ${ARGN}) - - get_filename_component(FILE_NAME_WE "${ARG_FILE}" NAME_WE) - get_filename_component(SOURCE_DIR "${ARG_FILE}" DIRECTORY) - - get_filename_component(FILE_TYPE "${ARG_FILE}" EXT) - if (${FILE_TYPE} MATCHES "js") - add_custom_command(OUTPUT "${ARG_OUTPUT_FILE}" - COMMENT "Run es2panda for ${ARG_FILE}" - COMMAND ${es2panda_bin} --opt-level 0 --output ${ARG_OUTPUT_FILE} "${ARG_FILE}" - DEPENDS es2panda "${ARG_FILE}" - WORKING_DIRECTORY "${ARG_WORKING_DIR}") - else() - # Compile assembly file - add_custom_command(OUTPUT "${ARG_OUTPUT_FILE}" - COMMENT "Building ${TEST_NAME}" - COMMAND ${PANDA_RUN_PREFIX} $ --log-file ${BUILD_LOG} ${ARG_FILE} ${ARG_OUTPUT_FILE} - DEPENDS ${assembler} "${ARG_FILE}" - WORKING_DIRECTORY "${ARG_WORKING_DIR}") - endif() -endfunction() - function(panda_add_checked_test_ecma) set(prefix ARG) set(singleValues NAME FILE SUPPORT_RELEASE LIBCORE) diff --git a/tests/runtime/common/CMakeLists.txt b/tests/runtime/common/CMakeLists.txt index d6221d3ac..d2a0ad25f 100644 --- a/tests/runtime/common/CMakeLists.txt +++ b/tests/runtime/common/CMakeLists.txt @@ -1,6 +1,52 @@ +# 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. + + add_custom_target(ecmascript_common_tests) add_dependencies(tests ecmascript_common_tests) +function(panda_add_ecma_test) + set(prefix ARG) + set(singleValues FILE) + set(multiValues OPTIONS) + cmake_parse_arguments(${prefix} "" "${singleValues}" "${multiValues}" ${ARGN}) + + get_filename_component(TEST_NAME "${ARG_FILE}" NAME_WE) + get_filename_component(TEST_FILE "${ARG_FILE}" REALPATH) + set(TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}") + set(BINARY_FILE "${TEST_DIR}/test.abc") + + file(MAKE_DIRECTORY "${TEST_DIR}") + compile_file_ecma(FILE ${TEST_FILE} OUTPUT_FILE ${BINARY_FILE} WORKING_DIR ${TEST_DIR}) + + set(OPTIONS "--load-runtimes=ecmascript" "${ARG_OPTIONS}") + + add_custom_target(${TEST_NAME} + COMMAND $ + ${OPTIONS} + ${BINARY_FILE} + _GLOBAL::func_main_0 + DEPENDS ${TEST_FILE} + WORKING_DIRECTORY ${TEST_DIR} + COMMENT "Running ${TEST_NAME} test" + DEPENDS ${BINARY_FILE}) + + add_dependencies(${TEST_NAME} es2panda ark) + add_dependencies(ecmascript_common_tests ${TEST_NAME}) +endfunction() + +panda_add_ecma_test(FILE startGc.js OPTIONS "--run-gc-in-place") + add_subdirectory(helloworld) add_subdirectory(newobjdynrange) add_subdirectory(bitwiseop) diff --git a/tests/runtime/common/startGc.js b/tests/runtime/common/startGc.js new file mode 100644 index 000000000..f233b4966 --- /dev/null +++ b/tests/runtime/common/startGc.js @@ -0,0 +1,82 @@ +/* + * 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. + */ + +function newWeakRefInYoung() { + // use a separate function because temporary object 'Object' + // are kept in vreg and GC treat it as a root. + return new WeakRef(new Object()); +} + +// allocatea an object of size in bytes at least 'sizeInBytes' +function allocLargeObject() { + // Ctor Array(length) doesn't allocates the internal JSTaggedValue array. + // Only length setter does this. + let big = new Array(); + for (let i = 0; i < 10; ++i) { + let a = new Array(); + a.length = 1024; + big[i] = a; + } + return big; +} + +function makeTenuredCollectible() +{ + // G1GC allocates a region in tenured space and moves objects to the region. + // The region is not subject to collect till it get full.` + // So we need to fill it up by moving more objects to this region. + // When the region gets filled GC allocates a new region and the old region + // can be collected. + let tmp1 = allocLargeObject(); + let tmp2 = allocLargeObject(); + let tmp3 = allocLargeObject(); + startGC("young"); +} + +function newWeakRefInCollectibleTenured() { + let obj = allocLargeObject(); + startGC("young"); // promote obj from young to tenured + makeTenuredCollectible(); + return new WeakRef(obj); +} + +// test young GC +let ref = newWeakRefInYoung(); +startGC("young"); +if (ref.deref() !== undefined) { + print("Young GC didn't collect weak ref"); + throw Error(); +} + +// test mixed GC +ref = newWeakRefInCollectibleTenured(); +startGC("mixed"); +if (ref.deref() !== undefined) { + print("Mixed GC didn't collect weak ref"); + throw Error(); +} + +// test full GC +ref = newWeakRefInCollectibleTenured(); +let ref2 = newWeakRefInYoung(); +startGC("full"); +if (ref.deref() !== undefined) { + print("Full GC didn't collect weak ref in tenured"); + throw Error(); +} +if (ref2.deref() !== undefined) { + print("Full GC didn't collect weak ref in young"); + throw Error(); +} diff --git a/tests/runtime/mem/CMakeLists.txt b/tests/runtime/mem/CMakeLists.txt index 3ecbe1753..e564d75d0 100644 --- a/tests/runtime/mem/CMakeLists.txt +++ b/tests/runtime/mem/CMakeLists.txt @@ -15,7 +15,6 @@ cmake_minimum_required(VERSION 3.10) set(MEMTESTS_JS_SOURCES weakContainers.js - gcTests.js ) panda_add_gtest( @@ -29,17 +28,6 @@ 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 deleted file mode 100644 index 064dededc..000000000 --- a/tests/runtime/mem/gcTests.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 deleted file mode 100644 index 2d4ff0cf6..000000000 --- a/tests/runtime/mem/gc_test.cpp +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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" - -namespace panda::ecmascript { -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; // NOLINT(misc-non-private-member-variables-in-classes) - EcmaVM *vm_ = nullptr; // NOLINT(misc-non-private-member-variables-in-classes) -}; - -Local GetContainerSize(EcmaVM *vm, [[maybe_unused]] Local ref, const Local *args, - int32_t argc, [[maybe_unused]] void *ptr) -{ - if (argc != 1) { - LOG_ECMA(FATAL) << "Unexpected number of arguments: " << argc; - } - // NOLINTNEXTLINE(cppcoreguideline-pro-bounds-pointer-arithmetic) - 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()); - } - if (hclass->IsJSWeakSet()) { - auto *weakSet = JSWeakSet::Cast(value->GetHeapObject()); - return IntegerRef::New(vm, weakSet->GetSize()); - } - UNREACHABLE(); -} - -Local GetGCPhase(EcmaVM *vm, [[maybe_unused]] Local ref, - [[maybe_unused]] const Local *args, [[maybe_unused]] int32_t argc, - [[maybe_unused]] void *ptr) -{ - 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) -{ - 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")); - - bool ret2 = JSNApi::Execute(vm_, "gcTests.abc", "_GLOBAL::func_main_0"); - 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::ecmascript -- Gitee From e7046b80c454c67388af2492cfe307973dd9abbe Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Tue, 27 Sep 2022 12:05:45 +0300 Subject: [PATCH 3/3] Add mark queue builtins for GC intrusive testing Change-Id: I66fe061d57aeb66858c80be289ef9afec3fc1a1b --- runtime/builtins.cpp | 3 +++ runtime/builtins/builtins_global.cpp | 38 +++++++++++++++++++++++++--- runtime/builtins/builtins_global.h | 3 +++ runtime/ecma_vm.cpp | 2 ++ runtime/runtime_call_id.h | 3 +++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index cdfd71dbd..99131ff35 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -356,6 +356,9 @@ void Builtins::InitializeGlobalObject(const JSHandle &env, const JSHa SetFunction(env, globalObject, "print", Global::PrintEntrypoint, 0); SetFunction(env, globalObject, "gc", Global::GCEntrypoint, 0); SetFunction(env, globalObject, "startGC", Global::SpecifiedGCEntrypoint, 1); + SetFunction(env, globalObject, "markObject", Global::MarkObject, 1); + SetFunction(env, globalObject, "getMarkQueue", Global::GetMarkQueue, 0); + SetFunction(env, globalObject, "clearMarkQueue", Global::ClearMarkQueue, 0); #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 b6db4b38c..f0a745e19 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -22,6 +22,7 @@ #include "plugins/ecmascript/runtime/base/string_helper.h" #include "plugins/ecmascript/runtime/ecma_macros.h" #include "plugins/ecmascript/runtime/internal_call_params.h" +#include "plugins/ecmascript/runtime/interpreter/fast_runtime_stub-inl.h" #include "plugins/ecmascript/runtime/interpreter/slow_runtime_helper.h" #include "plugins/ecmascript/runtime/js_invoker.h" #include "plugins/ecmascript/runtime/tagged_array-inl.h" @@ -655,9 +656,6 @@ JSTaggedValue BuiltinsGlobal::PrintEntrypoint(EcmaRuntimeCallInfo *msg) JSTaggedValue BuiltinsGlobal::GCEntrypoint(EcmaRuntimeCallInfo *msg) { - if (msg == nullptr) { - return JSTaggedValue::Undefined(); - } JSThread *thread = msg->GetThread(); ASSERT(thread != nullptr); [[maybe_unused]] EcmaHandleScope handleScope(thread); @@ -707,7 +705,41 @@ JSTaggedValue BuiltinsGlobal::SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg) auto task = MakePandaUnique(reason, thread); thread->GetEcmaVM()->GetGC()->Trigger(std::move(task)); + return JSTaggedValue::Undefined(); +} +JSTaggedValue BuiltinsGlobal::MarkObject(EcmaRuntimeCallInfo *msg) +{ + JSThread *thread = msg->GetThread(); + BUILTINS_API_TRACE(thread, Global, MarkObject); + EcmaVM *vm = thread->GetEcmaVM(); + JSHandle arg = GetCallArg(msg, 0); + if (arg->IsHeapObject()) { + vm->MarkObject(arg->GetHeapObject()); + } + return JSTaggedValue::Undefined(); +} + +JSTaggedValue BuiltinsGlobal::GetMarkQueue(EcmaRuntimeCallInfo *msg) +{ + JSThread *thread = msg->GetThread(); + BUILTINS_API_TRACE(thread, Global, GetMarkQueue); + EcmaVM *vm = thread->GetEcmaVM(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + JSHandle array = vm->GetFactory()->NewJSArray(); + RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); + size_t index = 0; + vm->IterateOverMarkQueue([&index, &array, thread](ObjectHeader *obj) { + FastRuntimeStub::SetPropertyByIndex(thread, array.GetTaggedValue(), index++, JSTaggedValue(obj)); + }); + return array.GetTaggedValue(); +} + +JSTaggedValue BuiltinsGlobal::ClearMarkQueue(EcmaRuntimeCallInfo *msg) +{ + JSThread *thread = msg->GetThread(); + BUILTINS_API_TRACE(thread, Global, ClearMarkQueue); + thread->GetEcmaVM()->ClearMarkQueue(); return JSTaggedValue::Undefined(); } diff --git a/runtime/builtins/builtins_global.h b/runtime/builtins/builtins_global.h index 3de105f8d..c1db81869 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -47,6 +47,9 @@ public: static JSTaggedValue PrintEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue GCEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg); + static JSTaggedValue MarkObject(EcmaRuntimeCallInfo *msg); + static JSTaggedValue GetMarkQueue(EcmaRuntimeCallInfo *msg); + static JSTaggedValue ClearMarkQueue(EcmaRuntimeCallInfo *msg); static JSTaggedValue CallJsBoundFunction(EcmaRuntimeCallInfo *msg); static JSTaggedValue CallJsProxy(EcmaRuntimeCallInfo *msg); #if ECMASCRIPT_ENABLE_RUNTIME_STAT diff --git a/runtime/ecma_vm.cpp b/runtime/ecma_vm.cpp index 8d20bfa02..f01b14954 100644 --- a/runtime/ecma_vm.cpp +++ b/runtime/ecma_vm.cpp @@ -986,6 +986,7 @@ void EcmaVM::HandleReturnFrame() void EcmaVM::VisitVmRoots(const GCRootVisitor &visitor) { + PandaVM::VisitVmRoots(visitor); ObjectXRay rootManager(this); auto common_visitor = [&visitor](Root type, ObjectSlot slot) { mem::RootType root; @@ -1015,6 +1016,7 @@ void EcmaVM::VisitVmRoots(const GCRootVisitor &visitor) void EcmaVM::UpdateVmRefs() { + PandaVM::UpdateVmRefs(); ObjectXRay rootManager(this); auto single_visitor = []([[maybe_unused]] Root type, ObjectSlot slot) { JSTaggedValue value(slot.GetTaggedType()); diff --git a/runtime/runtime_call_id.h b/runtime/runtime_call_id.h index 0979daaf2..4f6075194 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -328,6 +328,9 @@ namespace panda::ecmascript { V(Global, DecodeURI) \ V(Global, EncodeURI) \ V(Global, DecodeURIComponent) \ + V(Global, MarkObject) \ + V(Global, GetMarkQueue) \ + V(Global, ClearMarkQueue) \ V(Global, EncodeURIComponent) \ V(Iterator, Constructor) \ V(Iterator, Next) \ -- Gitee