From b348f3849b12706fe161494bb3abb94397053277 Mon Sep 17 00:00:00 2001 From: Petrov Igor Date: Wed, 15 Mar 2023 16:39:33 +0300 Subject: [PATCH] [MM] Support reference processor with WeakRef in STS Signed-off-by: Petrov Igor --- libpandabase/mem/object_pointer.h | 2 +- plugins/ets/BUILD.gn | 3 +- plugins/ets/runtime/CMakeLists.txt | 1 + .../runtime/ets_class_linker_extension.cpp | 24 +++- .../ets/runtime/ets_class_linker_extension.h | 3 + plugins/ets/runtime/ets_vm.cpp | 8 +- .../runtime/mem/ets_reference_processor.cpp | 110 ++++++++++++++++++ .../ets/runtime/mem/ets_reference_processor.h | 67 +++++++++++ .../ets/runtime/types/ets_weak_reference.h | 49 ++++++++ plugins/ets/stdlib/std/core/WeakRef.ets | 42 +++++++ plugins/ets/subproject_sources.gn | 1 + .../tests/ets_test_suite/gc/CMakeLists.txt | 1 + .../tests/ets_test_suite/gc/weak_ref_test.ets | 35 ++++++ 13 files changed, 335 insertions(+), 11 deletions(-) create mode 100644 plugins/ets/runtime/mem/ets_reference_processor.cpp create mode 100644 plugins/ets/runtime/mem/ets_reference_processor.h create mode 100644 plugins/ets/runtime/types/ets_weak_reference.h create mode 100644 plugins/ets/stdlib/std/core/WeakRef.ets create mode 100644 plugins/ets/tests/ets_test_suite/gc/weak_ref_test.ets diff --git a/libpandabase/mem/object_pointer.h b/libpandabase/mem/object_pointer.h index f009b818a..8468d382f 100644 --- a/libpandabase/mem/object_pointer.h +++ b/libpandabase/mem/object_pointer.h @@ -67,7 +67,7 @@ public: } // NOLINTNEXTLINE(google-explicit-constructor) - ALWAYS_INLINE operator Object *() + ALWAYS_INLINE operator Object *() const { return ToObjectPtr(object_); } diff --git a/plugins/ets/BUILD.gn b/plugins/ets/BUILD.gn index cf78615c7..568d26a83 100644 --- a/plugins/ets/BUILD.gn +++ b/plugins/ets/BUILD.gn @@ -93,9 +93,9 @@ if (with_stdlib) { "stdlib/std/core/Console.ets", "stdlib/std/core/Double.ets", "stdlib/std/core/Exception.ets", - "stdlib/std/core/GC.ets", "stdlib/std/core/Float.ets", "stdlib/std/core/Floating.ets", + "stdlib/std/core/GC.ets", "stdlib/std/core/Int.ets", "stdlib/std/core/Integral.ets", "stdlib/std/core/Long.ets", @@ -110,6 +110,7 @@ if (with_stdlib) { "stdlib/std/core/StringBuilder.ets", "stdlib/std/core/System.ets", "stdlib/std/core/Types.ets", + "stdlib/std/core/WeakRef.ets", "stdlib/std/math/consts/consts.ets", "stdlib/std/math/math.ets", "stdlib/std/time/Date.ets", diff --git a/plugins/ets/runtime/CMakeLists.txt b/plugins/ets/runtime/CMakeLists.txt index d732714bd..f3be2387d 100644 --- a/plugins/ets/runtime/CMakeLists.txt +++ b/plugins/ets/runtime/CMakeLists.txt @@ -35,6 +35,7 @@ set(ETS_RUNTIME_SOURCES ${ETS_EXT_SOURCES}/intrinsics/std_core_Promise.cpp ${ETS_EXT_SOURCES}/intrinsics/std_math.cpp ${ETS_EXT_SOURCES}/intrinsics/helpers/ets_intrinsics_helpers.cpp + ${ETS_EXT_SOURCES}/mem/ets_reference_processor.cpp ${ETS_EXT_SOURCES}/napi/ets_napi_helpers.cpp ${ETS_EXT_SOURCES}/napi/ets_napi_invoke_interface.cpp ${ETS_EXT_SOURCES}/napi/ets_napi_native_interface.cpp diff --git a/plugins/ets/runtime/ets_class_linker_extension.cpp b/plugins/ets/runtime/ets_class_linker_extension.cpp index 9b181bbe9..d07752fe7 100644 --- a/plugins/ets/runtime/ets_class_linker_extension.cpp +++ b/plugins/ets/runtime/ets_class_linker_extension.cpp @@ -91,6 +91,17 @@ void EtsClassLinkerExtension::ErrorHandler::OnError(ClassLinker::Error error, co } } +bool EtsClassLinkerExtension::CacheClass(Class **class_for_cache, const char *descriptor) +{ + *class_for_cache = + GetClassLinker()->GetClass(reinterpret_cast(descriptor), false, GetBootContext()); + if (*class_for_cache == nullptr || !InitializeClass(*class_for_cache)) { + LOG(ERROR, CLASS_LINKER) << "Cannot create class " << descriptor; + return false; + } + return true; +} + bool EtsClassLinkerExtension::InitializeImpl(bool compressed_string_enabled) { auto ctx = ets::utils::GetLanguageContext(); @@ -156,12 +167,17 @@ bool EtsClassLinkerExtension::InitializeImpl(bool compressed_string_enabled) InitializeArrayClassRoot(ClassRoot::ARRAY_STRING, ClassRoot::STRING, utf::Mutf8AsCString(ctx.GetStringArrayClassDescriptor())); - promise_class_ = - GetClassLinker()->GetClass(reinterpret_cast("Lstd/core/Promise;"), false, GetBootContext()); - if (promise_class_ == nullptr || !InitializeClass(promise_class_)) { - LOG(ERROR, CLASS_LINKER) << "Cannot create class Lstd/core/Promise;"; + if (!CacheClass(&promise_class_, "Lstd/core/Promise;")) { + return false; + } + Class *weak_ref_class; + // Cache into local variable, no need to cache to this class + if (!CacheClass(&weak_ref_class, "Lstd/core/WeakRef;")) { return false; } + // Mark std.core.WeakRef class as weak reference (flag) + auto *managed_weak_ref_ets_class = reinterpret_cast(weak_ref_class->GetManagedObject()); + managed_weak_ref_ets_class->SetWeakReference(); return true; } diff --git a/plugins/ets/runtime/ets_class_linker_extension.h b/plugins/ets/runtime/ets_class_linker_extension.h index fe6a25881..324cdeb40 100644 --- a/plugins/ets/runtime/ets_class_linker_extension.h +++ b/plugins/ets/runtime/ets_class_linker_extension.h @@ -115,12 +115,15 @@ private: Class *CreateClassRoot(const uint8_t *descriptor, ClassRoot root); + bool CacheClass(Class **class_for_cache, const char *descriptor); + class ErrorHandler : public ClassLinkerErrorHandler { public: void OnError(ClassLinker::Error error, const PandaString &message) override; }; ErrorHandler error_handler_; + // Cached classes Class *promise_class_ = nullptr; }; diff --git a/plugins/ets/runtime/ets_vm.cpp b/plugins/ets/runtime/ets_vm.cpp index 426ca0789..0b09d155e 100644 --- a/plugins/ets/runtime/ets_vm.cpp +++ b/plugins/ets/runtime/ets_vm.cpp @@ -42,7 +42,6 @@ #include "runtime/mem/gc/gc_trigger.h" #include "runtime/mem/gc/gc_types.h" #include "runtime/mem/gc/gc.h" -#include "runtime/mem/gc/reference-processor/empty_reference_processor.h" #include "runtime/mem/memory_manager.h" #include "runtime/mem/object_helpers.h" #include "runtime/mem/refstorage/reference.h" @@ -51,6 +50,7 @@ #include "runtime/monitor_pool.h" #include "runtime/string_table.h" #include "plugins/ets/runtime/ets_coroutine_manager.h" +#include "plugins/ets/runtime/mem/ets_reference_processor.h" #include "plugins/ets/runtime/types/ets_method_signature.h" #include "plugins/ets/runtime/napi/ets_mangle.h" @@ -145,7 +145,7 @@ PandaEtsVM::PandaEtsVM(Runtime *runtime, const RuntimeOptions &options, mem::Mem heap_manager->GetMemStats(), runtime_iface_); string_table_ = allocator->New(); monitor_pool_ = allocator->New(allocator); - reference_processor_ = allocator->New(); + reference_processor_ = allocator->New(mm_->GetGC()); coroutine_manager_ = allocator->New(); rendezvous_ = allocator->New(this); } @@ -272,17 +272,15 @@ void PandaEtsVM::HandleReferences(const GCTask &task, const mem::GC::ReferenceCl LOG(DEBUG, REF_PROC) << "Start processing cleared references"; gc->ProcessReferences(gc->GetGCPhase(), task, pred); + LOG(DEBUG, REF_PROC) << "Finish processing cleared references"; } -// TODO(alovkov): call ReferenceQueue.add method with cleared references void PandaEtsVM::HandleEnqueueReferences() { ASSERT(mm_ != nullptr); - LOG(DEBUG, REF_PROC) << "Start HandleEnqueueReferences"; ASSERT(mm_->GetGC() != nullptr); mm_->GetGC()->EnqueueReferences(); - LOG(DEBUG, REF_PROC) << "Finish HandleEnqueueReferences"; } void PandaEtsVM::HandleGCFinished() {} diff --git a/plugins/ets/runtime/mem/ets_reference_processor.cpp b/plugins/ets/runtime/mem/ets_reference_processor.cpp new file mode 100644 index 000000000..d01995e80 --- /dev/null +++ b/plugins/ets/runtime/mem/ets_reference_processor.cpp @@ -0,0 +1,110 @@ +/** + * Copyright (c) 2023 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 "libpandabase/os/mutex.h" +#include "runtime/include/object_header.h" +#include "runtime/mem/gc/gc_phase.h" +#include "runtime/mem/object_helpers.h" +#include "plugins/ets/runtime/mem/ets_reference_processor.h" +#include "plugins/ets/runtime/types/ets_class.h" +#include "plugins/ets/runtime/types/ets_weak_reference.h" + +namespace panda::mem::ets { + +EtsReferenceProcessor::EtsReferenceProcessor(GC *gc) : gc_(gc) {} + +bool EtsReferenceProcessor::IsReference(const BaseClass *base_cls, const ObjectHeader *ref, + const ReferenceCheckPredicateT &pred) const +{ + ASSERT(base_cls != nullptr); + ASSERT(ref != nullptr); + ASSERT(base_cls->GetSourceLang() == panda_file::SourceLang::ETS); + + const auto *obj_ets_class = panda::ets::EtsClass::FromRuntimeClass(static_cast(base_cls)); + + if (obj_ets_class->IsWeakReference()) { + const auto *ets_ref = reinterpret_cast(ref); + + auto *referent = ets_ref->GetReferent(); + if (referent == nullptr) { + LOG(DEBUG, REF_PROC) << "Treat " << GetDebugInfoAboutObject(ref) + << " as normal object, because referent is null"; + return false; + } + + ASSERT(IsMarking(gc_->GetGCPhase())); + bool referent_is_marked = false; + if (pred(referent->GetCoreType())) { + referent_is_marked = gc_->IsMarked(referent->GetCoreType()); + } else { + LOG(DEBUG, REF_PROC) << "Treat " << GetDebugInfoAboutObject(ref) << " as normal object, because referent " + << std::hex << GetDebugInfoAboutObject(referent->GetCoreType()) + << " doesn't suit predicate"; + referent_is_marked = true; + } + return !referent_is_marked; + } + return false; +} + +void EtsReferenceProcessor::HandleReference([[maybe_unused]] GC *gc, [[maybe_unused]] GCMarkingStackType *objects_stack, + [[maybe_unused]] const BaseClass *cls, const ObjectHeader *object, + [[maybe_unused]] const ReferenceProcessPredicateT &pred) +{ + os::memory::LockHolder lock(weak_ref_lock_); + LOG(DEBUG, REF_PROC) << GetDebugInfoAboutObject(object) << " is added to weak references set for processing"; + weak_references_.insert(const_cast(object)); +} + +void EtsReferenceProcessor::ProcessReferences([[maybe_unused]] bool concurrent, + [[maybe_unused]] bool clear_soft_references, + [[maybe_unused]] GCPhase gc_phase, + const mem::GC::ReferenceClearPredicateT &pred) +{ + os::memory::LockHolder lock(weak_ref_lock_); + while (!weak_references_.empty()) { + auto *weak_ref_obj = weak_references_.extract(weak_references_.begin()).value(); + ASSERT(panda::ets::EtsClass::FromRuntimeClass(weak_ref_obj->ClassAddr())->IsWeakReference()); + auto *weak_ref = static_cast(panda::ets::EtsObject::FromCoreType(weak_ref_obj)); + auto *referent = weak_ref->GetReferent(); + if (referent == nullptr) { + LOG(DEBUG, REF_PROC) << "Don't process reference " << GetDebugInfoAboutObject(weak_ref_obj) + << " because referent is null"; + continue; + } + auto *referent_obj = referent->GetCoreType(); + if (!pred(referent_obj)) { + LOG(DEBUG, REF_PROC) << "Don't process reference " << GetDebugInfoAboutObject(weak_ref_obj) + << " because referent " << GetDebugInfoAboutObject(referent_obj) + << " failed predicate"; + continue; + } + if (gc_->IsMarked(referent_obj)) { + LOG(DEBUG, REF_PROC) << "Don't process reference " << GetDebugInfoAboutObject(weak_ref_obj) + << " because referent " << GetDebugInfoAboutObject(referent_obj) << " is marked"; + continue; + } + LOG(DEBUG, REF_PROC) << "In " << GetDebugInfoAboutObject(weak_ref_obj) << " clear referent"; + weak_ref->ClearReferent(); + } +} + +size_t EtsReferenceProcessor::GetReferenceQueueSize() const +{ + os::memory::LockHolder lock(weak_ref_lock_); + return weak_references_.size(); +} + +} // namespace panda::mem::ets diff --git a/plugins/ets/runtime/mem/ets_reference_processor.h b/plugins/ets/runtime/mem/ets_reference_processor.h new file mode 100644 index 000000000..f44539c65 --- /dev/null +++ b/plugins/ets/runtime/mem/ets_reference_processor.h @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2023 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 PANDA_PLUGINS_ETS_RUNTIME_MEM_ETS_REFERENCE_PROCESSOR_H +#define PANDA_PLUGINS_ETS_RUNTIME_MEM_ETS_REFERENCE_PROCESSOR_H + +#include "runtime/mem/gc/reference-processor/reference_processor.h" + +namespace panda::mem::ets { + +class EtsReferenceProcessor final : public ReferenceProcessor { +public: + explicit EtsReferenceProcessor(GC *gc); + NO_COPY_SEMANTIC(EtsReferenceProcessor); + NO_MOVE_SEMANTIC(EtsReferenceProcessor); + ~EtsReferenceProcessor() final = default; + + bool IsReference(const BaseClass *base_cls, const ObjectHeader *ref, + const ReferenceCheckPredicateT &pred) const final; + + void HandleReference(GC *gc, GCMarkingStackType *objects_stack, const BaseClass *cls, const ObjectHeader *object, + const ReferenceProcessPredicateT &pred) final; + + void ProcessReferences(bool concurrent, bool clear_soft_references, GCPhase gc_phase, + const mem::GC::ReferenceClearPredicateT &pred) final; + + panda::mem::Reference *CollectClearedReferences() final + { + return nullptr; + } + + void ScheduleForEnqueue([[maybe_unused]] Reference *cleared_references) final + { + UNREACHABLE(); + } + + void Enqueue([[maybe_unused]] panda::mem::Reference *cleared_references) final + { + UNREACHABLE(); + } + + /** + * @return size of the queue of weak references + */ + size_t GetReferenceQueueSize() const final; + +private: + mutable os::memory::Mutex weak_ref_lock_; + PandaUnorderedSet weak_references_ GUARDED_BY(weak_ref_lock_); + GC *gc_; +}; + +} // namespace panda::mem::ets + +#endif // PANDA_PLUGINS_ETS_RUNTIME_MEM_ETS_REFERENCE_PROCESSOR_H diff --git a/plugins/ets/runtime/types/ets_weak_reference.h b/plugins/ets/runtime/types/ets_weak_reference.h new file mode 100644 index 000000000..861cc1c20 --- /dev/null +++ b/plugins/ets/runtime/types/ets_weak_reference.h @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2023 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 PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_WEAK_REFERENCE_H +#define PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_WEAK_REFERENCE_H + +#include "plugins/ets/runtime/types/ets_object.h" + +namespace panda::ets { + +/** + * @class EtsWeakReference represent std.core.WeakRef class + */ +class EtsWeakReference : public EtsObject { +public: + EtsWeakReference() = delete; + NO_COPY_SEMANTIC(EtsWeakReference); + NO_MOVE_SEMANTIC(EtsWeakReference); + ~EtsWeakReference() = delete; + + EtsObject *GetReferent() const + { + return referent_; + } + + void ClearReferent() + { + referent_ = nullptr; + } + +private: + // Such field has the same layout as referent in std.core.WeakRef class + ObjectPointer referent_; +}; + +} // namespace panda::ets + +#endif // PANDA_PLUGINS_ETS_RUNTIME_TYPES_ETS_WEAK_REFERENCE_H diff --git a/plugins/ets/stdlib/std/core/WeakRef.ets b/plugins/ets/stdlib/std/core/WeakRef.ets new file mode 100644 index 000000000..f52b86b68 --- /dev/null +++ b/plugins/ets/stdlib/std/core/WeakRef.ets @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 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. + */ + +package std.core; + +/** + * @class WeakRef - weak reference to object. + * A weak reference to an object is a reference that does not prevent the object + * from being reclaimed by the GC. + * @remarks It's important to avoid relying on any specific GC behaviors. + */ +export class WeakRef { + // Reference to target object + private referent: Object; + + /** + * Constructor creates weak reference object referring to a given target object + * @param target - target object for weak reference + */ + public constructor(target: Object) { + this.referent = target; + } + + /** + * @return instance's target object, or null if the target object has been collected + */ + public deref(): Object { + return this.referent; + } +} diff --git a/plugins/ets/subproject_sources.gn b/plugins/ets/subproject_sources.gn index 6a543e3b9..7cf18ce0b 100644 --- a/plugins/ets/subproject_sources.gn +++ b/plugins/ets/subproject_sources.gn @@ -56,6 +56,7 @@ srcs_runtime = [ "runtime/intrinsics/std_math.cpp", "runtime/intrinsics/std_time_Date.cpp", "runtime/intrinsics/helpers/ets_intrinsics_helpers.cpp", + "runtime/mem/ets_reference_processor.cpp", "runtime/napi/ets_napi_helpers.cpp", "runtime/napi/ets_napi_invoke_interface.cpp", "runtime/napi/ets_napi_native_interface.cpp", diff --git a/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt b/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt index 857ab5553..de0f1acee 100644 --- a/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt +++ b/plugins/ets/tests/ets_test_suite/gc/CMakeLists.txt @@ -51,3 +51,4 @@ endfunction() add_ets_gc_test(FILE pin_object.ets OPTIONS "--gc-type=g1-gc" "--gc-trigger-type=debug-never" MODE "INT") add_ets_gc_test(FILE space_type_test.ets OPTIONS "--gc-type=g1-gc" "--gc-trigger-type=debug-never" MODE "INT") +add_ets_gc_test(FILE weak_ref_test.ets OPTIONS "--gc-type=g1-gc" "--gc-trigger-type=debug-never" MODE "INT") diff --git a/plugins/ets/tests/ets_test_suite/gc/weak_ref_test.ets b/plugins/ets/tests/ets_test_suite/gc/weak_ref_test.ets new file mode 100644 index 000000000..46fdee214 --- /dev/null +++ b/plugins/ets/tests/ets_test_suite/gc/weak_ref_test.ets @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023 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 CreateWeakRef(): WeakRef +{ + let obj = new Object(); + assert obj != null : "New object must be correctly allocated"; + return new WeakRef(obj); +} + +function main(): int { + let wr = CreateWeakRef(); + let reachable_obj = new Object(); + let wr_with_reachable = new WeakRef(reachable_obj); + // Run Full GC for referent collection + let gc_id = GC.startGC(GC.FULL_CAUSE); + GC.waitForFinishGC(gc_id); + assert wr.deref() == null : "Referent must be collected after Full GC"; + assert reachable_obj != null : "Local object must not be collected" + assert wr_with_reachable.deref() != null : "deref for weak reference with reachable object " + + "must return non-null value"; + return 0; +} -- Gitee