From eaa40b7245bf52786734b7be8726ad68288ee977 Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Mon, 24 Oct 2022 17:04:06 +0300 Subject: [PATCH] Implement ScheduleGcAfterNthAlloc global function * The function runs GC on n-th allocation. Change-Id: I07ab3a51562a1da8bcac47f6470b5f69b5538c0d --- runtime/builtins.cpp | 2 ++ runtime/builtins/builtins_global.cpp | 53 ++++++++++++++++++++++++++++ runtime/builtins/builtins_global.h | 2 ++ runtime/runtime_call_id.h | 2 ++ tests/runtime/common/CMakeLists.txt | 1 + tests/runtime/common/scheduleGc.js | 36 +++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 tests/runtime/common/scheduleGc.js diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index 5294de92e..5bd4ee9c2 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -346,6 +346,8 @@ void Builtins::InitializeGlobalObject(const JSHandle &env, const JSHa SetFunction(env, globalObject, "gc", Global::GCEntrypoint, 0); SetFunction(env, globalObject, "startGC", Global::SpecifiedGCEntrypoint, 1); SetFunction(env, globalObject, "waitForFinishGC", Global::WaitForFinishGC, 1); + SetFunction(env, globalObject, "scheduleGcAfterNthAlloc", Global::ScheduleGCAfterNthAlloc, 2); + SetFunction(env, globalObject, "isScheduledGcTriggered", Global::IsScheduledGCTriggered, 0); SetFunction(env, globalObject, "markObject", Global::MarkObject, 1); SetFunction(env, globalObject, "getMarkQueue", Global::GetMarkQueue, 0); SetFunction(env, globalObject, "clearMarkQueue", Global::ClearMarkQueue, 0); diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index 8e7d235f5..400d9a72b 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -793,6 +793,59 @@ JSTaggedValue BuiltinsGlobal::WaitForFinishGC(EcmaRuntimeCallInfo *msg) return JSTaggedValue::Undefined(); } +// Function schedules GC before n-th allocation by setting counter to the specific GC trigger. +// Another call may reset the counter. In this case the last counter will be used to trigger the GC. +JSTaggedValue BuiltinsGlobal::ScheduleGCAfterNthAlloc(EcmaRuntimeCallInfo *msg) +{ + JSThread *thread = msg->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + BUILTINS_API_TRACE(thread, Global, ScheduleGCAfterNthAlloc); + JSHandle arg = GetCallArg(msg, 0); + if (!arg->IsInt()) { + JSHandle err = + thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid argument"); + THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); + } + auto counter = static_cast(arg->GetInt()); + if (counter < 0) { + JSHandle err = + thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid argument"); + THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); + } + GCTaskCause reason = GCCauseFromString(thread, GetCallArg(msg, 1)); + 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()); + } + + EcmaVM *vm = thread->GetEcmaVM(); + mem::GCTrigger *trigger = vm->GetGCTrigger(); + if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) { + JSHandle err = thread->GetEcmaVM()->GetFactory()->GetJSError( + ErrorType::TYPE_ERROR, "VM is running with unsupported GC trigger"); + THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); + } + auto sched_trigger = reinterpret_cast(vm->GetGCTrigger()); + sched_trigger->ScheduleGc(reason, counter); + + return JSTaggedValue::Undefined(); +} + +JSTaggedValue BuiltinsGlobal::IsScheduledGCTriggered(EcmaRuntimeCallInfo *msg) +{ + JSThread *thread = msg->GetThread(); + BUILTINS_API_TRACE(thread, Global, IsScheduledGCTriggered); + + EcmaVM *vm = thread->GetEcmaVM(); + mem::GCTrigger *trigger = vm->GetGCTrigger(); + if (trigger->GetType() != mem::GCTriggerType::ON_NTH_ALLOC) { + return JSTaggedValue::Undefined(); + } + auto sched_trigger = reinterpret_cast(vm->GetGCTrigger()); + return JSTaggedValue(sched_trigger->IsTriggered()); +} + JSTaggedValue BuiltinsGlobal::MarkObject(EcmaRuntimeCallInfo *msg) { JSThread *thread = msg->GetThread(); diff --git a/runtime/builtins/builtins_global.h b/runtime/builtins/builtins_global.h index 534ea064a..fdf7caebd 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -50,6 +50,8 @@ public: static JSTaggedValue GCEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue WaitForFinishGC(EcmaRuntimeCallInfo *msg); + static JSTaggedValue ScheduleGCAfterNthAlloc(EcmaRuntimeCallInfo *msg); + static JSTaggedValue IsScheduledGCTriggered(EcmaRuntimeCallInfo *msg); static JSTaggedValue MarkObject(EcmaRuntimeCallInfo *msg); static JSTaggedValue GetMarkQueue(EcmaRuntimeCallInfo *msg); static JSTaggedValue ClearMarkQueue(EcmaRuntimeCallInfo *msg); diff --git a/runtime/runtime_call_id.h b/runtime/runtime_call_id.h index 454fabd6f..59b81bed8 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -328,6 +328,8 @@ namespace panda::ecmascript { V(Global, GCEntrypoint) \ V(Global, SpecifiedGCEntrypoint) \ V(Global, WaitForFinishGC) \ + V(Global, ScheduleGCAfterNthAlloc) \ + V(Global, IsScheduledGCTriggered) \ V(Global, MarkObject) \ V(Global, GetMarkQueue) \ V(Global, ClearMarkQueue) \ diff --git a/tests/runtime/common/CMakeLists.txt b/tests/runtime/common/CMakeLists.txt index 7fab0061c..24f5ce084 100644 --- a/tests/runtime/common/CMakeLists.txt +++ b/tests/runtime/common/CMakeLists.txt @@ -48,6 +48,7 @@ endfunction() # Use --gc-trigger-type=debug-never to control GC in the test. # In the test we don't need the runtime triggers GC during allocation. panda_add_ecma_test(FILE startGc.js OPTIONS "--gc-trigger-type=debug-never") +panda_add_ecma_test(FILE scheduleGc.js OPTIONS "--gc-trigger-type=debug-never" "--gc-use-nth-alloc-trigger=true") add_subdirectory(helloworld) add_subdirectory(newobjdynrange) diff --git a/tests/runtime/common/scheduleGc.js b/tests/runtime/common/scheduleGc.js new file mode 100644 index 000000000..bc2b56bd7 --- /dev/null +++ b/tests/runtime/common/scheduleGc.js @@ -0,0 +1,36 @@ +/* + * 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 assert(value, message) { + if (value) { + return; + } + print("Assertion failed: " + message); + throw Error(); +} + +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()); +} + +let ref = newWeakRefInYoung(); +scheduleGcAfterNthAlloc(4, "young"); +new Object(); // 2 allocations +assert(isScheduledGcTriggered() == false, "Expected scheduled GC is not triggered"); +new Object(); // 2 allocations +assert(isScheduledGcTriggered() == true, "Expected scheduled GC is triggered"); +assert(ref.deref() == undefined, "Expected WeakRef is undefined") -- Gitee