From 36b9c58ed9f1e2cf0c43ce05f1fb79381768b4fc Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Wed, 23 Nov 2022 12:26:28 +0300 Subject: [PATCH] Add possibility to run JS code during concurrent mark * startGc with callback runs the task in-place Change-Id: I763380ac5b50057062825ad2d2d77f00bed39cc8 Signed-off-by: Artem Udovichenko --- runtime/builtins.cpp | 2 +- runtime/builtins/builtins_global.cpp | 98 +++++++++++++++++++++++----- runtime/builtins/builtins_global.h | 18 +++-- tests/runtime/common/CMakeLists.txt | 1 + tests/runtime/common/concurrent.js | 36 ++++++++++ tests/runtime/common/startGc.js | 9 ++- 6 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 tests/runtime/common/concurrent.js diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index e558859d0..db227f468 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -343,7 +343,7 @@ 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, "startGC", Global::SpecifiedGCEntrypoint, 1); + SetFunction(env, globalObject, "startGC", Global::SpecifiedGCEntrypoint, 2); SetFunction(env, globalObject, "waitForFinishGC", Global::WaitForFinishGC, 1); SetFunction(env, globalObject, "scheduleGcAfterNthAlloc", Global::ScheduleGCAfterNthAlloc, 2); SetFunction(env, globalObject, "isScheduledGcTriggered", Global::IsScheduledGCTriggered, 0); diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index 295e30eb1..9eaf693cf 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -28,6 +28,7 @@ #include "plugins/ecmascript/runtime/js_eval.h" #include "plugins/ecmascript/runtime/js_array.h" #include "plugins/ecmascript/runtime/tagged_array-inl.h" +#include "plugins/ecmascript/runtime/ecma_global_storage-inl.h" #include "include/thread_scopes.h" namespace panda::ecmascript::builtins { @@ -63,9 +64,28 @@ bool BuiltinsGlobal::GCTaskTracker::HasId(uint64_t id) return std::find(task_ids_.begin(), task_ids_.end(), id) != task_ids_.end(); } -void BuiltinsGlobal::GCTaskTracker::TaskCanceled(const GCTask &task) +void BuiltinsGlobal::GCTaskTracker::SetCallbackForTask(uint32_t task_id, uintptr_t callback_handle) { - RemoveId(task.GetId()); + callback_task_id_ = task_id; + callback_handle_ = callback_handle; +} + +void BuiltinsGlobal::GCTaskTracker::GCPhaseStarted(mem::GCPhase phase) +{ + if (phase == mem::GCPhase::GC_PHASE_MARK && callback_handle_ != 0 && current_task_id_ == callback_task_id_) { + JSThread *thread = JSThread::GetCurrent(); + JSHandle callback(thread, *reinterpret_cast(callback_handle_)); + JSHandle fn = JSHandle::Cast(callback); + JSHandle global(thread, thread->GetGlobalObject()); + JSHandle new_target(thread, JSTaggedValue::Undefined()); + auto info = NewRuntimeCallInfo(thread, fn, global, JSTaggedValue::Undefined(), 0); + JSFunction::Call(info.get()); + } +} + +void BuiltinsGlobal::GCTaskTracker::GCStarted(const GCTask &task, [[maybe_unused]] size_t heap_size) +{ + current_task_id_ = task.GetId(); } void BuiltinsGlobal::GCTaskTracker::GCFinished(const GCTask &task, [[maybe_unused]] size_t heap_size_before_gc, @@ -76,14 +96,18 @@ void BuiltinsGlobal::GCTaskTracker::GCFinished(const GCTask &task, [[maybe_unuse void BuiltinsGlobal::GCTaskTracker::RemoveId(uint64_t id) { - if (id == 0) { - return; + current_task_id_ = 0; + if (id == callback_task_id_ && callback_handle_ != 0) { + JSThread::GetCurrent()->GetEcmaGlobalStorage()->DisposeGlobalHandle(callback_handle_); + callback_handle_ = 0; } - 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); + if (id != 0) { + 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); + } } } @@ -745,44 +769,84 @@ static GCTaskCause GCCauseFromString(JSThread *thread, const JSHandleGetThread(); ASSERT(thread != nullptr); [[maybe_unused]] EcmaHandleScope handleScope(thread); BUILTINS_API_TRACE(thread, Global, SpecifiedGCEntrypoint); + bool run_gc_in_place = Runtime::GetOptions().IsRunGcInPlace("ecmascript"); GCTaskCause reason = GCCauseFromString(thread, GetCallArg(msg, 0)); + JSHandle callback_fn = 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()); } mem::GC *gc = thread->GetEcmaVM()->GetGC(); + g_gctask_tracker.InitIfNeeded(gc); auto task = MakePandaUnique(reason); + if (!callback_fn->IsUndefined()) { + if (!callback_fn->IsJSFunction()) { + JSHandle err = + thread->GetEcmaVM()->GetFactory()->GetJSError(ErrorType::TYPE_ERROR, "Invalid GC callback"); + THROW_NEW_ERROR_AND_RETURN_VALUE(thread, err.GetTaggedValue(), JSTaggedValue::Exception()); + } + run_gc_in_place = true; + uintptr_t global_handle = + thread->GetEcmaGlobalStorage()->NewGlobalHandle(callback_fn.GetTaggedValue().GetRawData()); + g_gctask_tracker.SetCallbackForTask(task->GetId(), global_handle); + } if (reason == GCTaskCause::YOUNG_GC_CAUSE) { - gc->WaitForGCInManaged(*task); - return JSTaggedValue(0); + run_gc_in_place = true; } - g_gctask_tracker.InitIfNeeded(gc); - uint64_t id = gc->Trigger(std::move(task)); - g_gctask_tracker.AddTaskId(id); + bool task_executed = false; + uint32_t id = task->GetId(); + if (run_gc_in_place) { + task_executed = gc->WaitForGCInManaged(*task); + } else { + g_gctask_tracker.AddTaskId(id); + task_executed = gc->Trigger(std::move(task)); + } + if (run_gc_in_place) { + return task_executed ? JSTaggedValue(0) : JSTaggedValue(-1); + } + if (!task_executed) { + g_gctask_tracker.RemoveId(id); + return JSTaggedValue(-1); + } return JSTaggedValue(static_cast(id)); } +/** + * The function returns when the specified GC gets finished. + * @param gc_id - id of the GC which is returned by startGc. + * If gc_id is 0 or -1 the function returns immediately. + */ 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()) { + if (!arg->IsNumber()) { 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) { + auto id = static_cast(arg->GetNumber()); + if (id <= 0) { return JSTaggedValue::Undefined(); } ASSERT(g_gctask_tracker.IsInitialized()); diff --git a/runtime/builtins/builtins_global.h b/runtime/builtins/builtins_global.h index c3ab4d071..8375805c2 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -124,21 +124,29 @@ private: static JSHandle GCSpecialisedObjectSpaceType(JSThread *thread, const JSHandle &obj_handle); + /** + * Class tracks GC tasks already processed by GC. + * Also the class tracks concurrent mark GC phase and calls + * the callback if it specified. + */ 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 SetCallbackForTask(uint32_t task_id, uintptr_t callback_handle); + void GCStarted(const GCTask &task, size_t heap_size) override; + void GCFinished(const GCTask &task, size_t heap_size_before_gc, size_t heap_size) override; + void GCPhaseStarted(mem::GCPhase phase) override; void RemoveId(uint64_t id); + private: bool initialized_ = false; std::vector task_ids_ GUARDED_BY(lock_); + uint32_t current_task_id_ = 0; + uint32_t callback_task_id_ = 0; + uintptr_t callback_handle_ = 0; os::memory::Mutex lock_; }; diff --git a/tests/runtime/common/CMakeLists.txt b/tests/runtime/common/CMakeLists.txt index fa8f47a59..5128b547a 100644 --- a/tests/runtime/common/CMakeLists.txt +++ b/tests/runtime/common/CMakeLists.txt @@ -50,6 +50,7 @@ endfunction() 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") panda_add_ecma_test(FILE spaceTypeTest.js OPTIONS "--gc-type=g1-gc" "--run-gc-in-place=true") +panda_add_ecma_test(FILE concurrent.js OPTIONS "--gc-type=g1-gc" "--gc-trigger-type=debug-never") add_subdirectory(helloworld) add_subdirectory(newobjdynrange) diff --git a/tests/runtime/common/concurrent.js b/tests/runtime/common/concurrent.js new file mode 100644 index 000000000..b6273ac31 --- /dev/null +++ b/tests/runtime/common/concurrent.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 alloc() { + for (let i = 0; i < 100; ++i) { + new Object(); + } +} + +let flag = false; +alloc(); +let gc = startGC("threshold", function() { + flag = true; +}); +assert(gc === 0, "Threshold GC with callback must be run in place"); +assert(flag === true, "Callback hasn't been called"); diff --git a/tests/runtime/common/startGc.js b/tests/runtime/common/startGc.js index 04b6d2509..86fe9c865 100644 --- a/tests/runtime/common/startGc.js +++ b/tests/runtime/common/startGc.js @@ -64,12 +64,17 @@ function newWeakRefInCollectibleTenured() { // test young GC let ref = newWeakRefInYoung(); -startGC("young"); +let gc = startGC("young"); assert(ref.deref() === undefined, "Young GC didn't collect weak ref"); +assert(gc === 0, "startGc for young should return 0"); +// Check waitForFinishGC works with special values +waitForFinishGC(0); +let task_discarded_id = -1; +waitForFinishGC(task_discarded_id); // test mixed GC ref = newWeakRefInCollectibleTenured(); -let gc = startGC("mixed"); +gc = startGC("mixed"); waitForFinishGC(gc); assert(ref.deref() === undefined, "Mixed GC didn't collect weak ref"); -- Gitee