From 2b5f5bdb61b1cf97b98f95baac5a6233457f55f1 Mon Sep 17 00:00:00 2001 From: Artem Udovichenko Date: Wed, 6 Jul 2022 16:58:37 +0300 Subject: [PATCH] Implement WeakRef and FinalizationRegistry Change-Id: I65486c929f34622c106274dfd6944b08cf73530b Signed-off-by: Artem Udovichenko --- runtime/CMakeLists.txt | 3 + runtime/builtins.cpp | 70 +++++++++ runtime/builtins.h | 5 + .../builtins_finalization_registry.cpp | 131 ++++++++++++++++ .../builtins/builtins_finalization_registry.h | 33 +++++ runtime/builtins/builtins_global.cpp | 16 ++ runtime/builtins/builtins_global.h | 1 + runtime/builtins/builtins_weak_ref.cpp | 70 +++++++++ runtime/builtins/builtins_weak_ref.h | 32 ++++ runtime/dump.cpp | 9 ++ runtime/ecma_vm.cpp | 32 ++++ runtime/ecma_vm.h | 10 +- runtime/global_env_constants.cpp | 5 + runtime/global_env_constants.h | 4 +- runtime/js_finalization_registry.cpp | 140 ++++++++++++++++++ runtime/js_finalization_registry.h | 53 +++++++ runtime/js_hclass.h | 12 ++ runtime/js_tagged_value-inl.h | 10 ++ runtime/js_tagged_value.h | 2 + runtime/js_weak_container.h | 15 ++ runtime/mem/ecma_reference_processor.cpp | 13 ++ runtime/object_factory.cpp | 19 +++ runtime/object_factory.h | 4 + runtime/runtime_call_id.h | 8 +- runtime/runtime_sources.gn | 3 + runtime/snapshot/mem/snapshot_serialize.cpp | 1 + 26 files changed, 695 insertions(+), 6 deletions(-) create mode 100644 runtime/builtins/builtins_finalization_registry.cpp create mode 100644 runtime/builtins/builtins_finalization_registry.h create mode 100644 runtime/builtins/builtins_weak_ref.cpp create mode 100644 runtime/builtins/builtins_weak_ref.h create mode 100644 runtime/js_finalization_registry.cpp create mode 100644 runtime/js_finalization_registry.h diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 507ee2d08..02de1eec5 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -85,8 +85,10 @@ set(ECMASCRIPT_SOURCES ${ECMA_SRC_DIR}/builtins/builtins_string_iterator.cpp ${ECMA_SRC_DIR}/builtins/builtins_symbol.cpp ${ECMA_SRC_DIR}/builtins/builtins_typedarray.cpp + ${ECMA_SRC_DIR}/builtins/builtins_weak_ref.cpp ${ECMA_SRC_DIR}/builtins/builtins_weak_map.cpp ${ECMA_SRC_DIR}/builtins/builtins_weak_set.cpp + ${ECMA_SRC_DIR}/builtins/builtins_finalization_registry.cpp ${ECMA_SRC_DIR}/class_linker/panda_file_translator.cpp ${ECMA_SRC_DIR}/containers/containers_arraylist.cpp ${ECMA_SRC_DIR}/containers/containers_private.cpp @@ -151,6 +153,7 @@ set(ECMASCRIPT_SOURCES ${ECMA_SRC_DIR}/js_thread.cpp ${ECMA_SRC_DIR}/js_typed_array.cpp ${ECMA_SRC_DIR}/js_weak_container.cpp + ${ECMA_SRC_DIR}/js_finalization_registry.cpp ${ECMA_SRC_DIR}/linked_hash_table.cpp ${ECMA_SRC_DIR}/literal_data_extractor.cpp ${ECMA_SRC_DIR}/message_string.cpp diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index 1fb9de530..160b0c85d 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -56,8 +56,10 @@ #include "plugins/ecmascript/runtime/builtins/builtins_string_iterator.h" #include "plugins/ecmascript/runtime/builtins/builtins_symbol.h" #include "plugins/ecmascript/runtime/builtins/builtins_typedarray.h" +#include "plugins/ecmascript/runtime/builtins/builtins_weak_ref.h" #include "plugins/ecmascript/runtime/builtins/builtins_weak_map.h" #include "plugins/ecmascript/runtime/builtins/builtins_weak_set.h" +#include "plugins/ecmascript/runtime/builtins/builtins_finalization_registry.h" #include "plugins/ecmascript/runtime/containers/containers_private.h" #include "plugins/ecmascript/runtime/ecma_runtime_call_info.h" #include "plugins/ecmascript/runtime/js_array.h" @@ -90,6 +92,7 @@ #include "plugins/ecmascript/runtime/js_tagged_value.h" #include "plugins/ecmascript/runtime/js_typed_array.h" #include "plugins/ecmascript/runtime/js_weak_container.h" +#include "plugins/ecmascript/runtime/js_finalization_registry.h" #include "plugins/ecmascript/runtime/mem/mem.h" #include "plugins/ecmascript/runtime/object_factory.h" #include "ohos/init_data.h" @@ -102,6 +105,7 @@ using Symbol = builtins::BuiltinsSymbol; using Boolean = builtins::BuiltinsBoolean; using BuiltinsMap = builtins::BuiltinsMap; using BuiltinsSet = builtins::BuiltinsSet; +using BuiltinsWeakRef = builtins::BuiltinsWeakRef; using BuiltinsWeakMap = builtins::BuiltinsWeakMap; using BuiltinsWeakSet = builtins::BuiltinsWeakSet; using BuiltinsArray = builtins::BuiltinsArray; @@ -144,6 +148,7 @@ using NumberFormat = builtins::BuiltinsNumberFormat; using Collator = builtins::BuiltinsCollator; using PluralRules = builtins::BuiltinsPluralRules; using ContainersPrivate = containers::ContainersPrivate; +using BuiltinsFinalizationRegistry = builtins::BuiltinsFinalizationRegistry; void Builtins::Initialize(const JSHandle &env, JSThread *thread) { @@ -279,6 +284,7 @@ void Builtins::Initialize(const JSHandle &env, JSThread *thread) InitializeRegExp(env); InitializeSet(env, objFuncDynclass); InitializeMap(env, objFuncDynclass); + InitializeWeakRef(env, objFuncDynclass); InitializeWeakMap(env, objFuncDynclass); InitializeWeakSet(env, objFuncDynclass); InitializeArray(env, objFuncPrototypeVal); @@ -306,6 +312,7 @@ void Builtins::Initialize(const JSHandle &env, JSThread *thread) InitializeAsyncFromSyncIteratorPrototypeObject(env, objFuncDynclass); InitializePromise(env, objFuncDynclass); InitializePromiseJob(env); + InitializeFinalizationRegistry(env, objFuncDynclass); JSRuntimeOptions options = JSRuntimeOptions::Cast(vm_->GetOptions()); std::string icuPath = options.GetIcuDataPath(); @@ -346,6 +353,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); #if ECMASCRIPT_ENABLE_RUNTIME_STAT SetFunction(env, globalObject, "startRuntimeStat", Global::StartRuntimeStat, 0); SetFunction(env, globalObject, "stopRuntimeStat", Global::StopRuntimeStat, 0); @@ -1255,6 +1263,33 @@ void Builtins::InitializeMap(const JSHandle &env, const JSHandleSetMapPrototype(thread_, mapFuncPrototype); } +void Builtins::InitializeWeakRef(const JSHandle &env, const JSHandle &objFuncDynclass) const +{ + [[maybe_unused]] EcmaHandleScope scope(thread_); + const GlobalEnvConstants *globalConst = thread_->GlobalConstants(); + // WeakRef.prototype + JSHandle weakRefFuncPrototype = factory_->NewJSObject(objFuncDynclass); + JSHandle weakRefFuncPrototypeValue(weakRefFuncPrototype); + // WeakRef.prototype_or_dynclass + JSHandle weakRefFuncInstanceDynclass = + factory_->NewEcmaDynClass(JSWeakRef::SIZE, JSType::JS_WEAK_REF, weakRefFuncPrototypeValue); + weakRefFuncInstanceDynclass->SetWeakContainer(true); + // WeakRef() = new Function() + JSHandle weakRefFunction( + NewBuiltinConstructor(env, weakRefFuncPrototype, BuiltinsWeakRef::Constructor, "WeakRef", FunctionLength::ONE)); + // WeakRef().prototype = WeakRef.Prototype & WeakRef.prototype.constructor = WeakRef() + JSFunction::Cast(weakRefFunction->GetTaggedObject()) + ->SetProtoOrDynClass(thread_, weakRefFuncInstanceDynclass.GetTaggedValue()); + + // "constructor" property on the prototype + JSHandle constructorKey = globalConst->GetHandledConstructorString(); + JSObject::SetProperty(thread_, JSHandle(weakRefFuncPrototype), constructorKey, weakRefFunction); + // weakref.prototype.deref() + SetFunction(env, weakRefFuncPrototype, "deref", BuiltinsWeakRef::Deref, FunctionLength::ZERO); + // @@ToStringTag + SetStringTagSymbol(env, weakRefFuncPrototype, "WeakRef"); +} + void Builtins::InitializeWeakMap(const JSHandle &env, const JSHandle &objFuncDynclass) const { [[maybe_unused]] EcmaHandleScope scope(thread_); @@ -2229,6 +2264,41 @@ void Builtins::InitializePromiseJob(const JSHandle &env) env->SetPromiseResolveThenableJob(thread_, func); } +void Builtins::InitializeFinalizationRegistry(const JSHandle &env, + const JSHandle &objFuncDynclass) const +{ + [[maybe_unused]] EcmaHandleScope scope(thread_); + const GlobalEnvConstants *globalConst = thread_->GlobalConstants(); + // FinalizationRegistry.prototype + JSHandle registryFuncPrototype = factory_->NewJSObject(objFuncDynclass); + JSHandle registryFuncPrototypeValue(registryFuncPrototype); + // FinalizationRegistry.prototype_or_dynclass + JSHandle registryFuncInstanceDynclass = factory_->NewEcmaDynClass( + JSFinalizationRegistry::SIZE, JSType::FINALIZATION_REGISTRY, registryFuncPrototypeValue); + // FinalizationRegistry() = new Function() + JSHandle registryFunction(NewBuiltinConstructor(env, registryFuncPrototype, + BuiltinsFinalizationRegistry::Constructor, + "FinalizationRegistry", FunctionLength::ONE)); + // FinalizationRegistry().prototype = FinalizationRegistry.Prototype & + // FinalizationRegistry.prototype.constructor = Finalizationregistry() + JSFunction::Cast(registryFunction->GetTaggedObject()) + ->SetProtoOrDynClass(thread_, registryFuncInstanceDynclass.GetTaggedValue()); + + // "constructor" property on the prototype + JSHandle constructorKey = globalConst->GetHandledConstructorString(); + JSObject::SetProperty(thread_, JSHandle(registryFuncPrototype), constructorKey, registryFunction); + // FinalizationRegistry.prototype.register() + SetFunction(env, registryFuncPrototype, "register", BuiltinsFinalizationRegistry::Register, FunctionLength::TWO); + // FinalizationRegistry.prototype.unregister() + SetFunction(env, registryFuncPrototype, "unregister", BuiltinsFinalizationRegistry::Unregister, + FunctionLength::ONE); + // @@ToStringTag + SetStringTagSymbol(env, registryFuncPrototype, "FinalizationRegistry"); + + const_cast(globalConst) + ->SetConstant(ConstantIndex::FINALIZATION_REGISTRY_CLASS_INDEX, registryFuncInstanceDynclass.GetTaggedValue()); +} + void Builtins::InitializeDataView(const JSHandle &env, const JSHandle &objFuncDynclass) const { [[maybe_unused]] EcmaHandleScope scope(thread_); diff --git a/runtime/builtins.h b/runtime/builtins.h index d1431c42c..399ada65a 100644 --- a/runtime/builtins.h +++ b/runtime/builtins.h @@ -128,6 +128,8 @@ private: void InitializeMap(const JSHandle &env, const JSHandle &objFuncDynclass) const; + void InitializeWeakRef(const JSHandle &env, const JSHandle &objFuncDynclass) const; + void InitializeWeakMap(const JSHandle &env, const JSHandle &objFuncDynclass) const; void InitializeWeakSet(const JSHandle &env, const JSHandle &objFuncDynclass) const; @@ -181,6 +183,9 @@ private: void InitializePromiseJob(const JSHandle &env); + void InitializeFinalizationRegistry(const JSHandle &env, + const JSHandle &objFuncDynclass) const; + void SetFunction(const JSHandle &env, const JSHandle &obj, const char *key, EcmaEntrypoint func, int length) const; diff --git a/runtime/builtins/builtins_finalization_registry.cpp b/runtime/builtins/builtins_finalization_registry.cpp new file mode 100644 index 000000000..a82cba231 --- /dev/null +++ b/runtime/builtins/builtins_finalization_registry.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2021-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. + */ + +#include "plugins/ecmascript/runtime/builtins/builtins_finalization_registry.h" +#include "plugins/ecmascript/runtime/ecma_vm.h" +#include "plugins/ecmascript/runtime/global_env.h" +#include "plugins/ecmascript/runtime/internal_call_params.h" +#include "plugins/ecmascript/runtime/js_finalization_registry.h" +#include "plugins/ecmascript/runtime/object_factory.h" + +namespace panda::ecmascript::builtins { +JSTaggedValue BuiltinsFinalizationRegistry::Constructor(EcmaRuntimeCallInfo *argv) +{ + ASSERT(argv); + BUILTINS_API_TRACE(argv->GetThread(), FinalizationRegistry, Constructor); + JSThread *thread = argv->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); + // 1.If NewTarget is undefined, throw a TypeError exception + JSHandle newTarget = GetNewTarget(argv); + if (newTarget->IsUndefined()) { + // throw type error + THROW_TYPE_ERROR_AND_RETURN(thread, "new target can't be undefined", JSTaggedValue::Exception()); + } + // 2. If IsCallable(cleanupCallback) is false, throw a TypeError exception. + if (!GetCallArg(argv, 0)->IsCallable()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "cleanupCallback is not callable", JSTaggedValue::Exception()); + } + // 3.Let FinalizationRegistry be OrdinaryCreateFromConstructor( + // NewTarget, "%FinalizationRegistry.prototype%", «[[Realm]], [[CleanupCallback]], [[Cells]]») + JSHandle constructor = GetConstructor(argv); + JSHandle obj = factory->NewJSObjectByConstructor(JSHandle(constructor), newTarget); + RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); + JSHandle registry = JSHandle::Cast(obj); + // 4. Let fn be the active function object. + JSHandle cleanupCallback = JSHandle::Cast(constructor); + // 5. Set finalizationRegistry.[[Realm]] to fn.[[Realm]]. + // Skip. Don't know what is it + // 6. Set finalizationRegistry.[[CleanupCallback]] to HostMakeJobCallback(cleanupCallback). + registry->SetCleanupCallback(cleanupCallback.GetTaggedValue()); + // 7. Set finalizationRegistry.[[Cells]] to a new empty List. + JSHandle cells = factory->EmptyArray(); + registry->SetCells(cells.GetTaggedValue()); + + thread->GetEcmaVM()->RegisterFinalizationRegistry(registry); + + return registry.GetTaggedValue(); +} + +JSTaggedValue BuiltinsFinalizationRegistry::Register(EcmaRuntimeCallInfo *argv) +{ + ASSERT(argv); + BUILTINS_API_TRACE(argv->GetThread(), FinalizationRegistry, Register); + JSThread *thread = argv->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + JSHandle self(GetThis(argv)); + // 1. Let finalizationRegistry be the this value. + if (!self->IsJSFinalizationRegistry()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "'this' is not FinalizationRegistry.", JSTaggedValue::Exception()); + } + JSHandle registry = JSHandle::Cast(self); + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + // 3. If Type(target) is not Object, throw a TypeError exception. + JSHandle target = GetCallArg(argv, 0U); + if (!target->IsECMAObject()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "target is not an object.", JSTaggedValue::Exception()); + } + // 4. If SameValue(target, heldValue) is true, throw a TypeError exception. + JSMutableHandle heldValue(thread, JSTaggedValue::Hole()); + if (argv->GetArgsNumber() >= 2U) { + heldValue.Update(GetCallArg(argv, 1U)); + if (JSTaggedValue::SameValue(target.GetTaggedValue(), heldValue.GetTaggedValue())) { + THROW_TYPE_ERROR_AND_RETURN(thread, "target and heldValue are the same.", JSTaggedValue::Exception()); + } + } + // 5. If Type(unregisterToken) is not Object, then + // a. If unregisterToken is not undefined, throw a TypeError exception. + // b. Set unregisterToken to empty. + JSMutableHandle unregisterToken(thread, GetCallArg(argv, 2U)); + if (!unregisterToken->IsECMAObject()) { + if (!unregisterToken->IsUndefined()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "unregisterToken must be an object.", JSTaggedValue::Exception()); + } + unregisterToken.Update(JSTaggedValue::Hole()); + } + // 6. Let cell be the Record { [[WeakRefTarget]]: target, [[HeldValue]]: + // heldValue, [[UnregisterToken]]: unregisterToken }. + // 7. Append cell to finalizationRegistry.[[Cells]]. + JSFinalizationRegistry::Register(thread, registry, target, heldValue, unregisterToken); + // 8. Return undefined. + return JSTaggedValue::Undefined(); +} + +JSTaggedValue BuiltinsFinalizationRegistry::Unregister(EcmaRuntimeCallInfo *argv) +{ + ASSERT(argv); + BUILTINS_API_TRACE(argv->GetThread(), FinalizationRegistry, Unregister); + JSThread *thread = argv->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + JSHandle self(GetThis(argv)); + // 1. Let finalizationRegistry be the this value. + if (!self->IsJSFinalizationRegistry()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "'this' is not FinalizationRegistry.", JSTaggedValue::Exception()); + } + JSHandle registry = JSHandle::Cast(self); + // 2. Perform ? RequireInternalSlot(finalizationRegistry, [[Cells]]). + // 3. If Type(unregisterToken) is not Object, throw a TypeError exception. + JSHandle unregisterToken = GetCallArg(argv, 0U); + if (!unregisterToken->IsECMAObject()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "unregisterToken must be an object.", JSTaggedValue::Exception()); + } + // 4. Let removed be false. + // 5. For each Record { [[WeakRefTarget]], [[HeldValue]], [[UnregisterToken]] } cell of + // finalizationRegistry.[[Cells]], do + bool removed = JSFinalizationRegistry::Unregister(thread, registry, unregisterToken); + // 8. Return removed. + return JSTaggedValue(removed); +} +} // namespace panda::ecmascript::builtins diff --git a/runtime/builtins/builtins_finalization_registry.h b/runtime/builtins/builtins_finalization_registry.h new file mode 100644 index 000000000..1a4ee5751 --- /dev/null +++ b/runtime/builtins/builtins_finalization_registry.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021-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. + */ + +#ifndef ECMASCRIPT_BUILTINS_BUILTINS_FINALIZATION_REGISTRY_H +#define ECMASCRIPT_BUILTINS_BUILTINS_FINALIZATION_REGISTRY_H + +#include "plugins/ecmascript/runtime/base/builtins_base.h" +#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h" + +namespace panda::ecmascript::builtins { +class BuiltinsFinalizationRegistry : public ecmascript::base::BuiltinsBase { +public: + // 26.2.3.1 + static JSTaggedValue Constructor(EcmaRuntimeCallInfo *argv); + // 26.2.3.2 + static JSTaggedValue Register(EcmaRuntimeCallInfo *argv); + // 26.2.3.3 + static JSTaggedValue Unregister(EcmaRuntimeCallInfo *argv); +}; +} // namespace panda::ecmascript::builtins +#endif // ECMASCRIPT_BUILTINS_BUILTINS_FINALIZATION_REGISTRY_H diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index 9286d8e5a..7275383cc 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -501,6 +501,22 @@ JSTaggedValue BuiltinsGlobal::PrintEntrypoint(EcmaRuntimeCallInfo *msg) return JSTaggedValue::Undefined(); } +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); + + thread->GetEcmaVM()->GetGC()->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE, thread)); + thread->SafepointPoll(); + + 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 b0f713ca0..61d75fa92 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -42,6 +42,7 @@ public: static JSTaggedValue EncodeURIComponent(EcmaRuntimeCallInfo *msg); static JSTaggedValue PrintEntrypoint(EcmaRuntimeCallInfo *msg); + static JSTaggedValue GcEntrypoint(EcmaRuntimeCallInfo *msg); static JSTaggedValue CallJsBoundFunction(EcmaRuntimeCallInfo *msg); static JSTaggedValue CallJsProxy(EcmaRuntimeCallInfo *msg); #if ECMASCRIPT_ENABLE_RUNTIME_STAT diff --git a/runtime/builtins/builtins_weak_ref.cpp b/runtime/builtins/builtins_weak_ref.cpp new file mode 100644 index 000000000..ba91b9e95 --- /dev/null +++ b/runtime/builtins/builtins_weak_ref.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2021-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. + */ + +#include "plugins/ecmascript/runtime/builtins/builtins_weak_ref.h" +#include "plugins/ecmascript/runtime/ecma_vm.h" +#include "plugins/ecmascript/runtime/global_env.h" +#include "plugins/ecmascript/runtime/internal_call_params.h" +#include "plugins/ecmascript/runtime/js_weak_container.h" +#include "plugins/ecmascript/runtime/object_factory.h" + +namespace panda::ecmascript::builtins { +JSTaggedValue BuiltinsWeakRef::Constructor(EcmaRuntimeCallInfo *argv) +{ + ASSERT(argv); + BUILTINS_API_TRACE(argv->GetThread(), WeakRef, Constructor); + JSThread *thread = argv->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); + // 1.If NewTarget is undefined, throw a TypeError exception + JSHandle newTarget = GetNewTarget(argv); + if (newTarget->IsUndefined()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "new target must not be undefined", JSTaggedValue::Exception()); + } + // 2. If Type(target) is not Object, throw a TypeError exception. + JSHandle target = GetCallArg(argv, 0); + if (!target->IsECMAObject()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "target must not be undefined", JSTaggedValue::Exception()); + } + // 3. Let WeakRef be OrdinaryCreateFromConstructor(NewTarget, "%WeakRefPrototype%", «[[WeakRefTarget]]» ). + JSHandle constructor = GetConstructor(argv); + JSHandle obj = factory->NewJSObjectByConstructor(JSHandle(constructor), newTarget); + RETURN_EXCEPTION_IF_ABRUPT_COMPLETION(thread); + JSHandle weakRef = JSHandle::Cast(obj); + + // 4. Perform AddToKeptObjects(target). + // 5. Set weakRef.[[WeakRefTarget]] to target. + weakRef->SetReferent(thread, target.GetTaggedValue()); + // Return weakRef. + return weakRef.GetTaggedValue(); +} + +JSTaggedValue BuiltinsWeakRef::Deref(EcmaRuntimeCallInfo *argv) +{ + ASSERT(argv); + BUILTINS_API_TRACE(argv->GetThread(), WeakRef, Deref); + JSThread *thread = argv->GetThread(); + [[maybe_unused]] EcmaHandleScope handleScope(thread); + JSHandle self = GetThis(argv); + // 1. Let weakRef be the this value. + // 2. Perform ? RequireInternalSlot(weakRef, [[WeakRefTarget]]). + if (!self->IsJSWeakRef()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "obj is not JSWeakRef.", JSTaggedValue::Exception()); + } + JSHandle weakRef(self); + // 3. Return WeakRefDeref(weakRef). + return weakRef->GetReferent(); +} +} // namespace panda::ecmascript::builtins diff --git a/runtime/builtins/builtins_weak_ref.h b/runtime/builtins/builtins_weak_ref.h new file mode 100644 index 000000000..d60e94700 --- /dev/null +++ b/runtime/builtins/builtins_weak_ref.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021-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. + */ + +#ifndef ECMASCRIPT_BUILTINS_BUILTINS_WEAK_REF_H +#define ECMASCRIPT_BUILTINS_BUILTINS_WEAK_REF_H + +#include "plugins/ecmascript/runtime/base/builtins_base.h" +#include "plugins/ecmascript/runtime/ecma_runtime_call_info.h" + +namespace panda::ecmascript::builtins { +class BuiltinsWeakRef : public ecmascript::base::BuiltinsBase { +public: + // 26.1.1.1 + static JSTaggedValue Constructor(EcmaRuntimeCallInfo *argv); + // 26.1.3.2 + static JSTaggedValue Deref(EcmaRuntimeCallInfo *argv); + // 26.1.3.3 @@toStringTag +}; +} // namespace panda::ecmascript::builtins +#endif // ECMASCRIPT_BUILTINS_BUILTINS_REF_H diff --git a/runtime/dump.cpp b/runtime/dump.cpp index 6b8fb9ad0..36b7f3fdf 100644 --- a/runtime/dump.cpp +++ b/runtime/dump.cpp @@ -439,6 +439,9 @@ static void DumpObject(JSThread *thread, TaggedObject *obj, std::ostream &os) case JSType::JS_MAP: JSMap::Cast(obj)->Dump(thread, os); break; + case JSType::JS_WEAK_REF: + JSWeakRef::Cast(obj)->Dump(thread, os); + break; case JSType::JS_WEAK_SET: JSWeakSet::Cast(obj)->Dump(thread, os); break; @@ -1071,6 +1074,12 @@ void JSSet::Dump(JSThread *thread, std::ostream &os) const set->Dump(thread, os); } +void JSWeakRef::Dump(JSThread *thread, std::ostream &os) const +{ + os << " - referent: " << (GetReferent().IsUndefined() ? "undefined\n" : "\n"); + JSObject::Dump(thread, os); +} + void JSWeakMap::Dump(JSThread *thread, std::ostream &os) const { LinkedHashMap *map = LinkedHashMap::Cast(GetLinkedMap().GetTaggedObject()); diff --git a/runtime/ecma_vm.cpp b/runtime/ecma_vm.cpp index 96fb16d52..d26c0a9b0 100644 --- a/runtime/ecma_vm.cpp +++ b/runtime/ecma_vm.cpp @@ -56,6 +56,7 @@ #include "plugins/ecmascript/compiler/ecmascript_extensions/thread_environment_api.h" #include "plugins/ecmascript/runtime/ecma_class_linker_extension.h" #include "plugins/ecmascript/runtime/mem/ecma_reference_processor.h" +#include "plugins/ecmascript/runtime/js_finalization_registry.h" #include "include/runtime_notification.h" #include "libpandafile/file.h" @@ -547,6 +548,20 @@ bool EcmaVM::Execute(std::unique_ptr pf, std::string_vie return true; } +void EcmaVM::SweepVmRefs(const GCObjectVisitor &gc_object_visitor) +{ + GetEcmaStringTable()->Sweep(gc_object_visitor); + auto it = finalization_registries_.begin(); + while (it != finalization_registries_.end()) { + if (gc_object_visitor(*it) == ObjectStatus::DEAD_OBJECT) { + auto to_remove = it++; + finalization_registries_.erase(to_remove); + } else { + ++it; + } + } +} + JSHandle EcmaVM::GetGlobalEnv() const { return JSHandle(reinterpret_cast(&globalEnv_)); @@ -599,6 +614,11 @@ void EcmaVM::RedirectMethod(const panda_file::File &pf) } } +void EcmaVM::RegisterFinalizationRegistry(JSHandle registry) +{ + finalization_registries_.push_back(*registry); +} + Expected EcmaVM::InvokeEntrypointImpl(Method *entrypoint, const std::vector &args) { // For testcase startup @@ -807,6 +827,14 @@ void EcmaVM::ProcessPrograms(const WeakRootVisitor &v0) } } +void EcmaVM::HandleEnqueueReferences() +{ + for (JSFinalizationRegistry *registry : finalization_registries_) { + JSHandle handle(thread_, registry); + JSFinalizationRegistry::CallCleanupCallback(thread_, handle); + } +} + void EcmaVM::PushToArrayDataList(JSNativePointer *array) { if (std::find(arrayBufferDataList_.begin(), arrayBufferDataList_.end(), array) != arrayBufferDataList_.end()) { @@ -1045,6 +1073,10 @@ void EcmaVM::UpdateVmRefs() for (size_t i = 0; i < arrayBufferDataList_.size(); ++i) { single_visitor(Root::ROOT_VM, ObjectSlot(ToUintPtr(&arrayBufferDataList_[i]))); } + + for (auto &entry : finalization_registries_) { + single_visitor(Root::ROOT_VM, ObjectSlot(ToUintPtr(&entry))); + } } } // namespace panda::ecmascript diff --git a/runtime/ecma_vm.h b/runtime/ecma_vm.h index db2812905..f44fcf5d4 100644 --- a/runtime/ecma_vm.h +++ b/runtime/ecma_vm.h @@ -58,6 +58,7 @@ class HeapTracker; class JSNativePointer; class Program; class JSPromise; +class JSFinalizationRegistry; enum class PromiseRejectionEvent : uint32_t; namespace job { @@ -226,10 +227,7 @@ public: GetEcmaStringTable()->VisitRoots(visitor, flags); } - void SweepVmRefs(const GCObjectVisitor &gc_object_visitor) override - { - GetEcmaStringTable()->Sweep(gc_object_visitor); - } + void SweepVmRefs(const GCObjectVisitor &gc_object_visitor) override; bool UpdateMovedStrings() override { @@ -389,6 +387,7 @@ public: } void ProcessReferences(const WeakRootVisitor &v0); + void HandleEnqueueReferences() override; JSHandle GetModuleByName(JSHandle moduleName); @@ -458,6 +457,8 @@ public: } } + void RegisterFinalizationRegistry(JSHandle registry); + protected: bool CheckEntrypointSignature([[maybe_unused]] Method *entrypoint) override { @@ -580,6 +581,7 @@ private: PromiseRejectCallback promiseRejectCallback_ {nullptr}; HostPromiseRejectionTracker hostPromiseRejectionTracker_ {nullptr}; void *data_ {nullptr}; + PandaList finalization_registries_; friend class SnapShotSerialize; friend class ObjectFactory; diff --git a/runtime/global_env_constants.cpp b/runtime/global_env_constants.cpp index 8c333a714..016a5c4fe 100644 --- a/runtime/global_env_constants.cpp +++ b/runtime/global_env_constants.cpp @@ -51,6 +51,7 @@ #include "plugins/ecmascript/runtime/js_symbol.h" #include "plugins/ecmascript/runtime/js_tagged_value.h" #include "plugins/ecmascript/runtime/js_thread.h" +#include "plugins/ecmascript/runtime/js_weak_container.h" #include "plugins/ecmascript/runtime/object_factory.h" #include "plugins/ecmascript/runtime/object_wrapper.h" @@ -183,6 +184,10 @@ void GlobalEnvConstants::InitRootsClass([[maybe_unused]] JSThread *thread, JSHCl SetConstant(ConstantIndex::LINKED_HASH_MAP_CLASS_INDEX, factory->NewEcmaDynClass(dynClassClass, 0, JSType::LINKED_HASH_MAP, HClass::ARRAY).GetTaggedValue()); + JSHClass *weakRefClass = *factory->NewEcmaDynClass(dynClassClass, JSWeakRef::SIZE, JSType::JS_WEAK_REF, 0, 0); + weakRefClass->SetWeakContainer(true); + SetConstant(ConstantIndex::WEAK_REF_CLASS_INDEX, JSTaggedValue(weakRefClass)); + JSHClass *weakHashSetClass = *factory->NewEcmaDynClass(dynClassClass, 0, JSType::LINKED_HASH_SET, HClass::ARRAY); weakHashSetClass->SetWeakContainer(true); SetConstant(ConstantIndex::WEAK_LINKED_HASH_SET_CLASS_INDEX, JSTaggedValue(weakHashSetClass)); diff --git a/runtime/global_env_constants.h b/runtime/global_env_constants.h index 4440db666..f8eb51e53 100644 --- a/runtime/global_env_constants.h +++ b/runtime/global_env_constants.h @@ -67,13 +67,15 @@ class JSThread; V(JSTaggedValue, JSProxyCallableClass, JS_PROXY_CALLABLE_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, JSProxyConstructClass, JS_PROXY_CONSTRUCT_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, JSRealmClass, JS_REALM_CLASS_INDEX, ecma_roots_class) \ + V(JSTaggedValue, WeakRefClass, WEAK_REF_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, LinkedHashSetClass, LINKED_HASH_SET_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, WeakLinkedHashSetClass, WEAK_LINKED_HASH_SET_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, LinkedHashMapClass, LINKED_HASH_MAP_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, WeakLinkedHashMapClass, WEAK_LINKED_HASH_MAP_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, JSRegExpClass, JS_REGEXP_CLASS_INDEX, ecma_roots_class) \ V(JSTaggedValue, MachineCodeClass, MACHINE_CODE_CLASS_INDEX, ecma_roots_class) \ - V(JSTaggedValue, ClassInfoExtractorHClass, CLASS_INFO_EXTRACTOR_HCLASS_INDEX, ecma_roots_class) + V(JSTaggedValue, ClassInfoExtractorHClass, CLASS_INFO_EXTRACTOR_HCLASS_INDEX, ecma_roots_class) \ + V(JSTaggedValue, FinalizationRegistryClass, FINALIZATION_REGISTRY_CLASS_INDEX, ecma_root_class) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define GLOBAL_ENV_CONSTANT_SPECIAL(V) \ diff --git a/runtime/js_finalization_registry.cpp b/runtime/js_finalization_registry.cpp new file mode 100644 index 000000000..3f6c186a9 --- /dev/null +++ b/runtime/js_finalization_registry.cpp @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2021-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. + */ + +#include "plugins/ecmascript/runtime/js_finalization_registry.h" +#include "plugins/ecmascript/runtime/js_invoker.h" +#include "plugins/ecmascript/runtime/js_tagged_value.h" +#include "plugins/ecmascript/runtime/js_weak_container.h" +#include "plugins/ecmascript/runtime/object_factory.h" +#include "plugins/ecmascript/runtime/internal_call_params.h" + +namespace panda::ecmascript { +uint32_t JSFinalizationRegistry::GetLength() const +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->GetLength() / 3U; +} + +JSTaggedValue JSFinalizationRegistry::GetObject(uint32_t idx) +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->Get(idx * 3U); +} + +void JSFinalizationRegistry::SetObject(const JSThread *thread, uint32_t idx, JSTaggedValue obj) +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->Set(thread, idx * 3U, obj); +} + +JSTaggedValue JSFinalizationRegistry::GetCallbackArg(uint32_t idx) +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->Get(idx * 3U + 1U); +} + +void JSFinalizationRegistry::SetCallbackArg(const JSThread *thread, uint32_t idx, JSTaggedValue arg) +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->Set(thread, idx * 3U + 1U, arg); +} + +JSTaggedValue JSFinalizationRegistry::GetToken(uint32_t idx) +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->Get(idx * 3U + 2U); +} + +void JSFinalizationRegistry::SetToken(const JSThread *thread, uint32_t idx, JSTaggedValue token) +{ + return TaggedArray::Cast(GetCells().GetRawHeapObject())->Set(thread, idx * 3U + 2U, token); +} + +void JSFinalizationRegistry::Register(const JSThread *thread, const JSHandle ®istry, + const JSHandle &object, + const JSHandle &callback_arg, const JSHandle &token) +{ + uint32_t length = registry->GetLength(); + uint32_t free_cell_idx = length; + // Find a free triplet. The free triplet is a triplet where the object value is JSTaggedValue::Hole(). + bool found = false; + for (uint32_t i = 0; i < length && !found; ++i) { + JSHandle obj(thread, registry->GetObject(i)); + if (obj.GetTaggedValue().IsHole()) { + found = true; + free_cell_idx = i; + } + } + if (!found) { + // There is no free triplet. Expand the array. + JSMutableHandle cells(thread, registry->GetCells()); + cells.Update(TaggedArray::SetCapacity(thread, cells, 3U * (length + 1))); + registry->SetCells(thread, cells); + } + ObjectFactory *factory = thread->GetEcmaVM()->GetFactory(); + JSHandle obj_weak_ref = factory->NewWeakRef(JSHandle::Cast(object)); + registry->SetObject(thread, free_cell_idx, obj_weak_ref.GetTaggedValue()); + registry->SetCallbackArg(thread, free_cell_idx, callback_arg.GetTaggedValue()); + if (token->IsHole()) { + registry->SetToken(thread, free_cell_idx, JSTaggedValue::Hole()); + } else { + JSHandle token_weak_ref = factory->NewWeakRef(JSHandle::Cast(token)); + registry->SetToken(thread, free_cell_idx, token_weak_ref.GetTaggedValue()); + } +} + +bool JSFinalizationRegistry::Unregister(JSThread *thread, const JSHandle ®istry, + const JSHandle &token) +{ + // Find all triplets with the matching token and replace them by JSTaggedValue::Hole + bool found = false; + uint32_t length = registry->GetLength(); + for (uint32_t i = 0; i < length; ++i) { + if (!registry->GetToken(i).IsHole()) { + JSHandle t(thread, JSWeakRef::Cast(registry->GetToken(i).GetRawHeapObject())); + if (JSTaggedValue::SameValue(t->GetReferent(), token.GetTaggedValue())) { + registry->SetObject(thread, i, JSTaggedValue::Hole()); + registry->SetCallbackArg(thread, i, JSTaggedValue::Hole()); + registry->SetToken(thread, i, JSTaggedValue::Hole()); + found = true; + } + } + } + return found; +} + +void JSFinalizationRegistry::CallCleanupCallback(JSThread *thread, JSHandle registry) +{ + JSHandle callback(thread, registry->GetCleanupCallback()); + JSHandle new_target(thread, JSTaggedValue::Undefined()); + JSHandle global(thread, thread->GetGlobalObject()); + + // Find all GC-ed objects and call FinalizationRegistry.callback for them. + uint32_t length = registry->GetLength(); + for (uint32_t i = 0; i < length; ++i) { + if (!registry->GetObject(i).IsHole()) { + JSHandle obj_weak_ref(thread, JSWeakRef::Cast(registry->GetObject(i).GetRawHeapObject())); + if (obj_weak_ref->GetReferent().IsUndefined()) { + JSHandle arg(thread, registry->GetCallbackArg(i)); + // Clear the triplet. + registry->SetObject(thread, i, JSTaggedValue::Hole()); + registry->SetCallbackArg(thread, i, JSTaggedValue::Hole()); + registry->SetToken(thread, i, JSTaggedValue::Hole()); + + if (arg->IsHole()) { + JSFunction::Call(thread, JSHandle(callback), global, 0U, nullptr); + } else { + JSTaggedType raw_arg = arg.GetTaggedValue().GetRawData(); + JSFunction::Call(thread, JSHandle(callback), global, 1U, &raw_arg); + } + } + } + } +} +} // namespace panda::ecmascript diff --git a/runtime/js_finalization_registry.h b/runtime/js_finalization_registry.h new file mode 100644 index 000000000..ed3b23e28 --- /dev/null +++ b/runtime/js_finalization_registry.h @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2021-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. + */ + +#ifndef ECMASCRIPT_JS_FINALIZATION_REGISTRY_H +#define ECMASCRIPT_JS_FINALIZATION_REGISTRY_H + +#include +#include "js_object.h" +#include "js_tagged_value-inl.h" + +namespace panda::ecmascript { +class JSFinalizationRegistry : public JSObject { +public: + CAST_CHECK(JSFinalizationRegistry, IsJSFinalizationRegistry); + + static void Register(const JSThread *thread, const JSHandle ®istry, + const JSHandle &obj, const JSHandle &callback_arg, + const JSHandle &token); + static bool Unregister(JSThread *thread, const JSHandle ®istry, + const JSHandle &token); + static void CallCleanupCallback(JSThread *thread, JSHandle registry); + + static constexpr size_t CLEANUP_CALLBACK_OFFSET = JSObject::SIZE; + ACCESSORS(CleanupCallback, CLEANUP_CALLBACK_OFFSET, CELLS_OFFSET); + // Array of triplets (WeakRef(object), callback_argument, WeakRef(unregistration_token)) + ACCESSORS(Cells, CELLS_OFFSET, SIZE); + + DECL_VISIT_OBJECT_FOR_JS_OBJECT(JSObject, CLEANUP_CALLBACK_OFFSET, SIZE) + +private: + uint32_t GetLength() const; + JSTaggedValue GetObject(uint32_t idx); + void SetObject(const JSThread *thread, uint32_t idx, JSTaggedValue obj); + JSTaggedValue GetCallbackArg(uint32_t idx); + void SetCallbackArg(const JSThread *thread, uint32_t idx, JSTaggedValue arg); + JSTaggedValue GetToken(uint32_t idx); + void SetToken(const JSThread *thread, uint32_t idx, JSTaggedValue token); +}; +} // namespace panda::ecmascript + +#endif // ECMASCRIPT_JS_FINALIZATION_REGISTRY_H diff --git a/runtime/js_hclass.h b/runtime/js_hclass.h index 234e53615..22ee5eaf4 100644 --- a/runtime/js_hclass.h +++ b/runtime/js_hclass.h @@ -92,6 +92,7 @@ class ProtoChangeDetails; JS_REG_EXP, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_SET, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_MAP, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ + JS_WEAK_REF, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_WEAK_MAP, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_WEAK_SET, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_DATE, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ @@ -108,6 +109,7 @@ class ProtoChangeDetails; JS_NUMBER_FORMAT, /* //////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_COLLATOR, /* ///////////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_PLURAL_RULES, /* ///////////////////////////////////////////////////////////////////////////////-PADDING */ \ + FINALIZATION_REGISTRY, /* /////////////////////////////////////////////////////////////////////////-PADDING */ \ \ JS_ARRAY_BUFFER, /* ///////////////////////////////////////////////////////////////////////////////-PADDING */ \ JS_PROMISE, /* ///////////////////////////////////////////////////////////////////////////////-PADDING */ \ @@ -553,6 +555,11 @@ public: return GetObjectType() == JSType::JS_MAP; } + bool IsJSWeakRef() const + { + return GetObjectType() == JSType::JS_WEAK_REF; + } + bool IsJSWeakMap() const { return GetObjectType() == JSType::JS_WEAK_MAP; @@ -733,6 +740,11 @@ public: return GetObjectType() == JSType::CLASS_INFO_EXTRACTOR; } + inline bool IsJSFinalizationRegistry() const + { + return GetObjectType() == JSType::FINALIZATION_REGISTRY; + } + inline bool IsLexicalFunction() const { return GetObjectType() == JSType::LEXICAL_FUNCTION; diff --git a/runtime/js_tagged_value-inl.h b/runtime/js_tagged_value-inl.h index f7eb1ae7c..1f291ff7e 100644 --- a/runtime/js_tagged_value-inl.h +++ b/runtime/js_tagged_value-inl.h @@ -651,6 +651,11 @@ inline bool JSTaggedValue::IsJSMap() const return IsHeapObject() && GetTaggedObject()->GetClass()->IsJSMap(); } +inline bool JSTaggedValue::IsJSWeakRef() const +{ + return IsHeapObject() && GetTaggedObject()->GetClass()->IsJSWeakRef(); +} + inline bool JSTaggedValue::IsJSWeakMap() const { return IsHeapObject() && GetTaggedObject()->GetClass()->IsJSWeakMap(); @@ -890,6 +895,11 @@ inline bool JSTaggedValue::IsClassInfoExtractor() const return IsHeapObject() && GetTaggedObject()->GetClass()->IsClassInfoExtractor(); } +inline bool JSTaggedValue::IsJSFinalizationRegistry() const +{ + return IsHeapObject() && GetTaggedObject()->GetClass()->IsJSFinalizationRegistry(); +} + inline double JSTaggedValue::ExtractNumber() const { ASSERT(IsNumber()); diff --git a/runtime/js_tagged_value.h b/runtime/js_tagged_value.h index 23c2f88c8..337fb1f9e 100644 --- a/runtime/js_tagged_value.h +++ b/runtime/js_tagged_value.h @@ -215,6 +215,7 @@ public: // Type bool IsJSMap() const; bool IsJSSet() const; + bool IsJSWeakRef() const; bool IsJSWeakMap() const; bool IsJSWeakSet() const; bool IsJSRegExp() const; @@ -320,6 +321,7 @@ public: bool IsProtoChangeDetails() const; bool IsMachineCodeObject() const; bool IsClassInfoExtractor() const; + bool IsJSFinalizationRegistry() const; static bool IsSameTypeOrHClass(JSTaggedValue x, JSTaggedValue y); static ComparisonResult Compare(JSThread *thread, const JSHandle &x, diff --git a/runtime/js_weak_container.h b/runtime/js_weak_container.h index d8aac384c..f9536f87e 100644 --- a/runtime/js_weak_container.h +++ b/runtime/js_weak_container.h @@ -21,6 +21,21 @@ #include "plugins/ecmascript/runtime/js_tagged_value-inl.h" namespace panda::ecmascript { +class JSWeakRef : public JSObject { +public: + static JSWeakRef *Cast(ObjectHeader *object) + { + ASSERT(JSTaggedValue(object).IsJSWeakRef()); + return static_cast(object); + } + + static constexpr size_t REFERENT_OFFSET = JSObject::SIZE; + ACCESSORS(Referent, REFERENT_OFFSET, SIZE) + + DECL_VISIT_OBJECT_FOR_JS_OBJECT(JSObject, REFERENT_OFFSET, SIZE) + DECL_DUMP() +}; + class JSWeakMap : public JSObject { public: static JSWeakMap *Cast(ObjectHeader *object) diff --git a/runtime/mem/ecma_reference_processor.cpp b/runtime/mem/ecma_reference_processor.cpp index 44a1d8b2a..3bc1e35fa 100644 --- a/runtime/mem/ecma_reference_processor.cpp +++ b/runtime/mem/ecma_reference_processor.cpp @@ -19,6 +19,7 @@ #include "runtime/mem/gc/gc.h" #include "plugins/ecmascript/runtime/ecma_vm.h" #include "plugins/ecmascript/runtime/js_hclass.h" +#include "plugins/ecmascript/runtime/js_weak_container.h" #include "plugins/ecmascript/runtime/linked_hash_table-inl.h" #include "plugins/ecmascript/runtime/mem/tagged_object.h" @@ -89,6 +90,11 @@ bool EcmaReferenceProcessor::IsReference([[maybe_unused]] const BaseClass *base_ case panda::ecmascript::JSType::TAGGED_ARRAY: return EnumerateArrayElements(static_cast(ref), is_reference_checker); + case panda::ecmascript::JSType::JS_WEAK_REF: { + panda::ecmascript::JSTaggedValue referent = + static_cast(ref)->GetReferent(); + return referent.IsHeapObject() ? is_reference_checker(0, referent.GetRawHeapObject()) : false; + } case panda::ecmascript::JSType::LINKED_HASH_MAP: return EnumerateKeys(static_cast(ref), is_reference_checker); case panda::ecmascript::JSType::LINKED_HASH_SET: @@ -129,6 +135,13 @@ void EcmaReferenceProcessor::ProcessReferences([[maybe_unused]] bool concurrent, return false; }; EnumerateArrayElements(array, handler); + } else if (object_type == panda::ecmascript::JSType::JS_WEAK_REF) { + auto *ref = static_cast(reference); + ASSERT(ref->GetReferent().IsHeapObject()); + if (!gc_->IsMarked(ref->GetReferent().GetRawHeapObject())) { + ObjectAccessor::SetDynValueWithoutBarrier(ref, panda::ecmascript::JSWeakRef::REFERENT_OFFSET, + panda::ecmascript::JSTaggedValue::Undefined().GetRawData()); + } } else if (object_type == panda::ecmascript::JSType::LINKED_HASH_MAP) { auto *map = static_cast(reference); auto map_handler = [this, map, thread](int index, ObjectHeader *key) { diff --git a/runtime/object_factory.cpp b/runtime/object_factory.cpp index 7d0a8eb31..66db846f8 100644 --- a/runtime/object_factory.cpp +++ b/runtime/object_factory.cpp @@ -69,6 +69,7 @@ #include "plugins/ecmascript/runtime/js_thread.h" #include "plugins/ecmascript/runtime/js_typed_array.h" #include "plugins/ecmascript/runtime/js_weak_container.h" +#include "plugins/ecmascript/runtime/js_finalization_registry.h" #include "plugins/ecmascript/runtime/layout_info-inl.h" #include "plugins/ecmascript/runtime/linked_hash_table-inl.h" #include "plugins/ecmascript/runtime/mem/mem_manager.h" @@ -166,6 +167,7 @@ void ObjectFactory::ObtainRootClass([[maybe_unused]] const JSHandle & linkedHashMapClass_ = JSHClass::Cast(globalConst->GetLinkedHashMapClass().GetTaggedObject()); linkedHashSetClass_ = JSHClass::Cast(globalConst->GetLinkedHashSetClass().GetTaggedObject()); + weakRefClass_ = JSHClass::Cast(globalConst->GetWeakRefClass().GetTaggedObject()); weakLinkedHashMapClass_ = JSHClass::Cast(globalConst->GetWeakLinkedHashMapClass().GetTaggedObject()); weakLinkedHashSetClass_ = JSHClass::Cast(globalConst->GetWeakLinkedHashSetClass().GetTaggedObject()); } @@ -752,6 +754,9 @@ JSHandle ObjectFactory::NewJSObjectByConstructor(const JSHandleSetCells(thread_, EmptyArray()); + break; case JSType::JS_ARRAY: { JSArray::Cast(*obj)->SetLength(thread_, JSTaggedValue(0)); auto accessor = thread_->GlobalConstants()->GetArrayLengthAccessor(); @@ -792,6 +797,9 @@ JSHandle ObjectFactory::NewJSObjectByConstructor(const JSHandleSetLinkedMap(thread_, JSTaggedValue::Undefined()); break; + case JSType::JS_WEAK_REF: + JSWeakRef::Cast(*obj)->SetReferent(JSTaggedValue::Undefined()); + break; case JSType::JS_WEAK_MAP: JSWeakMap::Cast(*obj)->SetLinkedMap(thread_, JSTaggedValue::Undefined()); break; @@ -1951,6 +1959,17 @@ JSHandle ObjectFactory::GetEmptyTaggedQueue() const return JSHandle(env->GetEmptyTaggedQueue()); } +JSHandle ObjectFactory::NewWeakRef(const JSHandle &referent) +{ + auto header = heapHelper_.AllocateYoungGenerationOrHugeObject(weakRefClass_); + if (header == nullptr) { + return JSHandle(); + } + JSHandle ref(thread_, header); + ref->SetReferent(referent.GetTaggedValue()); + return ref; +} + JSHandle ObjectFactory::NewJSSetIterator(const JSHandle &set, IterationKind kind) { JSHandle env = vm_->GetGlobalEnv(); diff --git a/runtime/object_factory.h b/runtime/object_factory.h index be2bd732d..070448d31 100644 --- a/runtime/object_factory.h +++ b/runtime/object_factory.h @@ -55,6 +55,7 @@ class TaggedQueue; class JSForInIterator; class JSSet; class JSMap; +class JSWeakRef; class JSRegExp; class JSSetIterator; class JSMapIterator; @@ -285,6 +286,8 @@ public: JSHandle GetEmptyTaggedQueue() const; + JSHandle NewWeakRef(const JSHandle &referent); + JSHandle NewJSSetIterator(const JSHandle &set, IterationKind kind); JSHandle NewJSMapIterator(const JSHandle &map, IterationKind kind); @@ -449,6 +452,7 @@ private: JSHClass *classInfoExtractorHClass_ {nullptr}; JSHClass *linkedHashMapClass_ {nullptr}; JSHClass *linkedHashSetClass_ {nullptr}; + JSHClass *weakRefClass_ {nullptr}; JSHClass *weakLinkedHashMapClass_ {nullptr}; JSHClass *weakLinkedHashSetClass_ {nullptr}; diff --git a/runtime/runtime_call_id.h b/runtime/runtime_call_id.h index 400b1d896..414f9e906 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -318,6 +318,7 @@ namespace panda::ecmascript { V(Global, IsFinite) \ V(Global, IsNaN) \ V(Global, PrintEntryPoint) \ + V(Global, GcEntryPoint) \ V(Global, NewobjDynrange) \ V(Global, CallJsBoundFunction) \ V(Global, CallJsProxy) \ @@ -543,6 +544,8 @@ namespace panda::ecmascript { V(TypedArray, Subarray) \ V(TypedArray, Values) \ V(TypedArray, ToStringTag) \ + V(WeakRef, Constructor) \ + V(WeakRef, Deref) \ V(WeakMap, Constructor) \ V(WeakMap, Delete) \ V(WeakMap, Get) \ @@ -554,7 +557,10 @@ namespace panda::ecmascript { V(WeakSet, Has) \ V(ArrayList, Constructor) \ V(ArrayList, Add) \ - V(ArrayList, Iterator) + V(ArrayList, Iterator) \ + V(FinalizationRegistry, Constructor) \ + V(FinalizationRegistry, Register) \ + V(FinalizationRegistry, Unregister) // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define ABSTRACT_OPERATION_LIST(V) V(JSTaggedValue, ToString) diff --git a/runtime/runtime_sources.gn b/runtime/runtime_sources.gn index 725dd229d..31cb5e112 100644 --- a/runtime/runtime_sources.gn +++ b/runtime/runtime_sources.gn @@ -55,8 +55,10 @@ srcs = [ "builtins/builtins_string_iterator.cpp", "builtins/builtins_symbol.cpp", "builtins/builtins_typedarray.cpp", + "builtins/builtins_weak_ref.cpp", "builtins/builtins_weak_map.cpp", "builtins/builtins_weak_set.cpp", + "builtins/builtins_finalization_registry.cpp", "class_linker/panda_file_translator.cpp", "containers/containers_arraylist.cpp", "containers/containers_private.cpp", @@ -121,6 +123,7 @@ srcs = [ "js_thread.cpp", "js_typed_array.cpp", "js_weak_container.cpp", + "js_finalization_registry.cpp", "linked_hash_table.cpp", "literal_data_extractor.cpp", "message_string.cpp", diff --git a/runtime/snapshot/mem/snapshot_serialize.cpp b/runtime/snapshot/mem/snapshot_serialize.cpp index 30a071b7b..69d83bd37 100644 --- a/runtime/snapshot/mem/snapshot_serialize.cpp +++ b/runtime/snapshot/mem/snapshot_serialize.cpp @@ -433,6 +433,7 @@ static uintptr_t g_nativeTable[] = { reinterpret_cast(DataView::GetByteLength), reinterpret_cast(DataView::GetOffset), reinterpret_cast(Global::PrintEntrypoint), + reinterpret_cast(Global::GcEntrypoint), reinterpret_cast(Global::NotSupportEval), reinterpret_cast(Global::IsFinite), reinterpret_cast(Global::IsNaN), -- Gitee