diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index fafd1278a5c1042ec9fb30525b097e194aa44f14..99131ff358d6031c77b879ad4e7f34119a5954fc 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -354,7 +354,11 @@ 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); + 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 d0a0e903806c75e976c29b6b288341e6b0010468..f0a745e19869d9efabe21aea00d4e5a6b3ee8c58 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -22,9 +22,11 @@ #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" +#include "include/thread_scopes.h" namespace panda::ecmascript::builtins { using NumberHelper = ecmascript::base::NumberHelper; @@ -652,15 +654,12 @@ JSTaggedValue BuiltinsGlobal::PrintEntrypoint(EcmaRuntimeCallInfo *msg) return JSTaggedValue::Undefined(); } -JSTaggedValue BuiltinsGlobal::GcEntrypoint(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); - 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 +667,82 @@ 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) +{ + 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); + + 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(); +} + 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..c1db81869b0dd93c0138ec53329ba47a2b76c8aa 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -45,7 +45,11 @@ 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 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 8d20bfa02fa991432251eab22932e55a5dd22470..f01b14954fd703f407aaef0c2b5a0662fc1ff110 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/global_env_constants.cpp b/runtime/global_env_constants.cpp index 5bdfbd97ae36d7f009af07ba8710c4854c15dd1b..eea1fcc3c332a77c1e04f1413a49972db996d88a 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 a775df693a1631edaa32ff72bbba49e087e3d15c..7f153ccb140d2b2934d29249317ad8454a733a3e 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 b9041b586eafbed65fd6300ba72377cc9a77b98c..4f6075194f715fb5263043bf5115e028d6278ec8 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -320,13 +320,17 @@ 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) \ 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) \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6e80c901870063343ad70a4cdfeec98048021e71..106def2f82f8006f45959c9d773e9327df69dc54 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 7f09a016de16cdcdc222f6dbc189926281830808..73ab5a00d605ad274f455daec90e8cd09adf4400 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 d6221d3ac4b5f891d7e53fb8295ec02fcecba9b9..d2a0ad25f696ccfdd0353b8beb64f69bb1b91db4 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 0000000000000000000000000000000000000000..f233b49668909ebf7bf9fff8ef24ff70b694dfde --- /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/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