From bec61ae96d93d074987d33a9d646e30123418f15 Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Fri, 7 Oct 2022 18:00:17 +0300 Subject: [PATCH 1/2] Remove caller_thread_ from GCTask Change-Id: Ibae4bfbd3f5e757d11073a7a6f67ecd877179103 --- runtime/builtins/builtins_global.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index f0a745e19..d3a9d868b 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -661,7 +661,7 @@ JSTaggedValue BuiltinsGlobal::GCEntrypoint(EcmaRuntimeCallInfo *msg) [[maybe_unused]] EcmaHandleScope handleScope(thread); BUILTINS_API_TRACE(thread, Global, GCEntrypoint); - thread->GetEcmaVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE, thread)); + thread->GetEcmaVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE)); thread->SafepointPoll(); return JSTaggedValue::Undefined(); @@ -702,7 +702,7 @@ JSTaggedValue BuiltinsGlobal::SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg) 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); + auto task = MakePandaUnique(reason); thread->GetEcmaVM()->GetGC()->Trigger(std::move(task)); return JSTaggedValue::Undefined(); -- Gitee From 75153bf3337ea1b9fdfceaf1f2ec9f9a82c2c813 Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Wed, 5 Oct 2022 16:04:33 +0300 Subject: [PATCH 2/2] Implement WaitForFinishGC Change-Id: I06e97ab74bdde34756c4f47bced4989caf9fadc4 --- runtime/builtins.cpp | 1 + runtime/builtins/builtins_global.cpp | 85 +++++++++++++++++++++++++++- runtime/builtins/builtins_global.h | 23 ++++++++ runtime/runtime_call_id.h | 7 ++- tests/runtime/common/CMakeLists.txt | 4 +- tests/runtime/common/startGc.js | 51 +++++++++-------- 6 files changed, 141 insertions(+), 30 deletions(-) diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index 8c984e21f..aa368a635 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -356,6 +356,7 @@ 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, "waitForFinishGC", Global::WaitForFinishGC, 1); 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 d3a9d868b..01487e3fa 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -32,6 +32,59 @@ namespace panda::ecmascript::builtins { using NumberHelper = ecmascript::base::NumberHelper; using StringHelper = ecmascript::base::StringHelper; +// NOLINTNEXTLINE(fuchsia-statically-constructed-objects) +BuiltinsGlobal::GCTaskTracker BuiltinsGlobal::g_gctask_tracker; + +void BuiltinsGlobal::GCTaskTracker::InitIfNeeded(mem::GC *gc) +{ + if (initialized_) { + return; + } + gc->AddListener(this); + initialized_ = true; +} + +bool BuiltinsGlobal::GCTaskTracker::IsInitialized() +{ + return initialized_; +} + +void BuiltinsGlobal::GCTaskTracker::AddTaskId(uint64_t id) +{ + os::memory::LockHolder lock(lock_); + task_ids_.push_back(id); +} + +bool BuiltinsGlobal::GCTaskTracker::HasId(uint64_t id) +{ + os::memory::LockHolder lock(lock_); + return std::find(task_ids_.begin(), task_ids_.end(), id) != task_ids_.end(); +} + +void BuiltinsGlobal::GCTaskTracker::TaskCanceled(const GCTask &task) +{ + RemoveId(task.GetId()); +} + +void BuiltinsGlobal::GCTaskTracker::GCFinished(const GCTask &task, [[maybe_unused]] size_t heap_size_before_gc, + [[maybe_unused]] size_t heap_size) +{ + RemoveId(task.GetId()); +} + +void BuiltinsGlobal::GCTaskTracker::RemoveId(uint64_t id) +{ + if (id == 0) { + return; + } + os::memory::LockHolder lock(lock_); + auto it = std::find(task_ids_.begin(), task_ids_.end(), id); + // There may be no such id if the corresponding GC has been triggered not by startGC + if (it != task_ids_.end()) { + task_ids_.erase(it); + } +} + // 18.2.1 JSTaggedValue BuiltinsGlobal::NotSupportEval(EcmaRuntimeCallInfo *msg) { @@ -702,9 +755,39 @@ JSTaggedValue BuiltinsGlobal::SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg) thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC cause"); THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); } + mem::GC *gc = thread->GetEcmaVM()->GetGC(); auto task = MakePandaUnique(reason); + if (reason == GCTaskCause::YOUNG_GC_CAUSE) { + gc->WaitForGCInManaged(*task); + return JSTaggedValue(0); + } + + g_gctask_tracker.InitIfNeeded(gc); + uint64_t id = gc->Trigger(std::move(task)); + g_gctask_tracker.AddTaskId(id); + return JSTaggedValue(static_cast(id)); +} - thread->GetEcmaVM()->GetGC()->Trigger(std::move(task)); +JSTaggedValue BuiltinsGlobal::WaitForFinishGC(EcmaRuntimeCallInfo *msg) +{ + JSThread *thread = msg->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + BUILTINS_API_TRACE(thread, Global, WaitForFinishGC); + JSHandle arg = GetCallArg(msg, 0); + if (!arg->IsDouble()) { + JSHandle err = thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC id"); + THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); + } + auto id = static_cast(arg->GetDouble()); + if (id == 0) { + return JSTaggedValue::Undefined(); + } + ASSERT(g_gctask_tracker.IsInitialized()); + ScopedNativeCodeThread s(thread); + while (g_gctask_tracker.HasId(id)) { + constexpr uint64_t WAIT_TIME_MS = 10; + os::thread::NativeSleep(WAIT_TIME_MS); + } return JSTaggedValue::Undefined(); } diff --git a/runtime/builtins/builtins_global.h b/runtime/builtins/builtins_global.h index c1db81869..134ee76aa 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -16,6 +16,8 @@ #ifndef ECMASCRIPT_BUILTINS_BUILTINS_GLOBAL_H #define ECMASCRIPT_BUILTINS_BUILTINS_GLOBAL_H +#include "libpandabase/os/mutex.h" +#include "runtime/mem/gc/gc.h" #include "plugins/ecmascript/runtime/base/builtins_base.h" #include "plugins/ecmascript/runtime/js_thread.h" @@ -47,6 +49,7 @@ public: static JSTaggedValue PrintEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue GCEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue SpecifiedGCEntrypoint(EcmaRuntimeCallInfo *msg); + static JSTaggedValue WaitForFinishGC(EcmaRuntimeCallInfo *msg); static JSTaggedValue MarkObject(EcmaRuntimeCallInfo *msg); static JSTaggedValue GetMarkQueue(EcmaRuntimeCallInfo *msg); static JSTaggedValue ClearMarkQueue(EcmaRuntimeCallInfo *msg); @@ -114,6 +117,26 @@ private: return -1; } static uint8_t GetValueFromTwoHex(uint16_t front, uint16_t behind); + + class GCTaskTracker : public mem::GCListener { + public: + void InitIfNeeded(mem::GC *gc); + bool IsInitialized(); + void AddTaskId(uint64_t id); + bool HasId(uint64_t id); + void TaskCanceled(const GCTask &task) override; + void GCFinished(const GCTask &task, [[maybe_unused]] size_t heap_size_before_gc, + [[maybe_unused]] size_t heap_size) override; + + private: + void RemoveId(uint64_t id); + + bool initialized_ = false; + std::vector task_ids_ GUARDED_BY(lock_); + os::memory::Mutex lock_; + }; + + static GCTaskTracker g_gctask_tracker; }; } // namespace panda::ecmascript::builtins diff --git a/runtime/runtime_call_id.h b/runtime/runtime_call_id.h index 4f6075194..80ca59ba6 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -322,15 +322,16 @@ namespace panda::ecmascript { V(Global, PrintEntryPoint) \ V(Global, GCEntrypoint) \ V(Global, SpecifiedGCEntrypoint) \ + V(Global, WaitForFinishGC) \ + V(Global, MarkObject) \ + V(Global, GetMarkQueue) \ + V(Global, ClearMarkQueue) \ 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/runtime/common/CMakeLists.txt b/tests/runtime/common/CMakeLists.txt index 934e7be6b..4531ca029 100644 --- a/tests/runtime/common/CMakeLists.txt +++ b/tests/runtime/common/CMakeLists.txt @@ -45,7 +45,9 @@ function(panda_add_ecma_test) add_dependencies(ecmascript_common_tests ${TEST_NAME}) endfunction() -panda_add_ecma_test(FILE startGc.js OPTIONS "--run-gc-in-place") +# 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") add_subdirectory(helloworld) add_subdirectory(newobjdynrange) diff --git a/tests/runtime/common/startGc.js b/tests/runtime/common/startGc.js index f233b4966..8056796dd 100644 --- a/tests/runtime/common/startGc.js +++ b/tests/runtime/common/startGc.js @@ -13,21 +13,34 @@ * 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()); } -// allocatea an object of size in bytes at least 'sizeInBytes' +// allocate 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) { + for (let i = 0; i < 60; ++i) { let a = new Array(); a.length = 1024; - big[i] = a; + if (i % 2 == 0) { + big[i] = a; + } + if (i % 10 == 0) { + startGC("young"); + } } return big; } @@ -39,14 +52,12 @@ function makeTenuredCollectible() // 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(); + let tmp = allocLargeObject(); startGC("young"); } function newWeakRefInCollectibleTenured() { - let obj = allocLargeObject(); + let obj = new Object(); startGC("young"); // promote obj from young to tenured makeTenuredCollectible(); return new WeakRef(obj); @@ -55,28 +66,18 @@ function newWeakRefInCollectibleTenured() { // test young GC let ref = newWeakRefInYoung(); startGC("young"); -if (ref.deref() !== undefined) { - print("Young GC didn't collect weak ref"); - throw Error(); -} +assert(ref.deref() === undefined, "Young GC didn't collect weak ref"); // test mixed GC ref = newWeakRefInCollectibleTenured(); -startGC("mixed"); -if (ref.deref() !== undefined) { - print("Mixed GC didn't collect weak ref"); - throw Error(); -} +let gc = startGC("mixed"); +waitForFinishGC(gc); +assert(ref.deref() === undefined, "Mixed GC didn't collect weak ref"); // 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(); -} +gc = startGC("full"); +waitForFinishGC(gc); +assert(ref.deref() === undefined, "Full GC didn't collect weak ref in tenured"); +assert(ref2.deref() === undefined, "Full GC didn't collect weak ref in young"); -- Gitee