diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index 5294de92e8c500b16e196f036b8f38d4fa97b9c1..5bd4ee9c2a0762c7f7e72443b53a3fafc2dd9540 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 8e7d235f57c9002183eb692c2091d823469dfcb9..400d9a72b0d22cf2299328bdbbf0bbd0bc28d069 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 534ea064adba921bdccae96c56edb40f05b29f3e..fdf7caebdf5223bfa86f2425f1b97860d07d0eb3 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 454fabd6f0ccbdac7010d2ddbd2cf9615d590408..59b81bed83146cc04fbd2570ff7a60b318f823e5 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 7fab0061cd7b5a9ba30c1a147b27b055f58862df..24f5ce08446b4c98a6ec789cc1133e62e8f5a37b 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 0000000000000000000000000000000000000000..bc2b56bd708e3daf12094afb58b1b92c340d7a82 --- /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")