diff --git a/src/jsvm_reference.cpp b/src/jsvm_reference.cpp new file mode 100644 index 0000000000000000000000000000000000000000..69d5bb8b188999f550915b5b30ff100cea434c22 --- /dev/null +++ b/src/jsvm_reference.cpp @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2024 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 "jsvm_reference.h" + +#include "js_native_api_v8.h" + +namespace v8impl { +namespace { +// In JavaScript, weak references can be created for object types (Object, +// Function, and external Object) and for local symbols that are created with +// the `Symbol` function call. Global symbols created with the `Symbol.for` +// method cannot be weak references because they are never collected. +// Currently, V8 has no API to detect if a symbol is local or global. +// Until we have a V8 API for it, we consider that all symbols can be weak. +inline bool CanBeHeldWeakly(v8::Local value) +{ + return value->IsObject() || value->IsSymbol(); +} +} // namespace + +// RefTracker +inline void RefTracker::Link(RefList* list) +{ + DCHECK(list != nullptr); + prev = list; + next = list->next; + if (next != nullptr) { + next->prev = this; + } + list->next = this; +} + +void RefTracker::FinalizeAll(RefList* list) +{ + while (list->next != nullptr) { + list->next->Finalize(); + } +} + +void RefTracker::Finalize() +{ + UNREACHABLE("Finalize need to be realized"); +} + +// UserReference +UserReference* UserReference::New(JSVM_Env env, v8::Local value, uint32_t initialRefcount) +{ + auto ref = new UserReference(env, value, true, initialRefcount); + + return ref; +} + +UserReference* UserReference::NewData(JSVM_Env env, v8::Local value, uint32_t initialRefcount) +{ + auto ref = new UserReference(env, value, false, initialRefcount); + + return ref; +} + +UserReference::UserReference(JSVM_Env env, v8::Local value, bool isValue, uint32_t initialRefcount) + : persistent(env->isolate, value), isValue(isValue), env(env), refcount(initialRefcount), + canBeWeak(isValue && CanBeHeldWeakly(value.As())) +{ + if (refcount == 0) { + SetWeak(); + } + + Link(&env->userReferenceList); +} + +UserReference::~UserReference() +{ + persistent.Reset(); + Unlink(); +} + +void UserReference::Finalize() +{ + persistent.Reset(); + Unlink(); +} + +v8::Local UserReference::Get() +{ + DCHECK(isValue); + if (persistent.IsEmpty()) { + return v8::Local(); + } else { + return v8::Local::New(env->isolate, persistent).As(); + } +} + +v8::Local UserReference::GetData() +{ + if (persistent.IsEmpty()) { + return v8::Local(); + } else { + return v8::Local::New(env->isolate, persistent); + } +} + +void UserReference::SetWeak() +{ + if (canBeWeak) { + persistent.SetWeak(); + } else { + persistent.Reset(); + } +} + +uint32_t UserReference::Ref() +{ + // If persistent is cleared by GC, return 0 unconditionally. + if (persistent.IsEmpty()) { + return 0; + } + + if (++refcount == 1) { + // If persistent can not be weak, it will be cleared in SetWeak(). + DCHECK(canBeWeak); + persistent.ClearWeak(); + } + + return refcount; +} + +uint32_t UserReference::Unref() +{ + // If persistent is cleared by GC, return 0 unconditionally. + if (persistent.IsEmpty() || refcount == 0) { + return 0; + } + + if (--refcount == 0) { + SetWeak(); + } + + return refcount; +} + +uint32_t UserReference::RefCount() +{ + return refcount; +} + +// FinalizerTracker +FinalizerTracker* FinalizerTracker::New(JSVM_Env env, JSVM_Finalize cb, void* finalizeData, void* finalizeHint) +{ + return new FinalizerTracker(env, cb, finalizeData, finalizeHint); +} + +FinalizerTracker::FinalizerTracker(JSVM_Env env, JSVM_Finalize cb, void* data, void* hint) + : env(env), cb(cb), data(data), hint(hint) +{ + Link(&env->finalizerList); +} + +FinalizerTracker::~FinalizerTracker() +{ + Unlink(); +} + +void FinalizerTracker::ResetFinalizer() +{ + cb = nullptr; + data = nullptr; + hint = nullptr; +} + +void FinalizerTracker::CallFinalizer() +{ + if (!cb) { + return; + } + + JSVM_Finalize cbTemp = cb; + void* dataTemp = data; + void* hintTemp = hint; + ResetFinalizer(); + + if (!env) { + cbTemp(env, dataTemp, hintTemp); + } else { + env->CallIntoModule([&](JSVM_Env env) { cbTemp(env, dataTemp, hintTemp); }); + } +} + +void FinalizerTracker::Finalize() +{ + CallFinalizer(); + delete this; +} + +RuntimeReference::RuntimeReference(JSVM_Env env, v8::Local value, JSVM_Finalize cb, void* data, void* hint) + : FinalizerTracker(env, cb, data, hint), persistent(env->isolate, value) +{ + DCHECK(CanBeHeldWeakly(value)); +} + +RuntimeReference* RuntimeReference::New(JSVM_Env env, v8::Local value, void* data) +{ + auto* ref = new RuntimeReference(env, value, nullptr, data, nullptr); + // Delete self in first pass callback + ref->SetWeak(false); + + return ref; +} + +RuntimeReference* RuntimeReference::New(JSVM_Env env, + v8::Local value, + JSVM_Finalize cb, + void* data, + void* hint) +{ + auto* ref = new RuntimeReference(env, value, cb, data, hint); + // Need second pass callback to call finalizer + ref->SetWeak(cb != nullptr); + + return ref; +} + +void RuntimeReference::DeleteReference(RuntimeReference* ref) +{ + // If reference is not added into first pass callbacks, delete this direct. + if (ref->persistent.IsWeak()) { + delete ref; + return; + } + + // If reference is added into first pass callbacks, reset finalizer function. + ref->ResetFinalizer(); +} + +inline void RuntimeReference::SetWeak(bool needSecondPass) +{ + if (needSecondPass) { + persistent.SetWeak(this, FirstPassCallback, v8::WeakCallbackType::kParameter); + } else { + persistent.SetWeak(this, FirstPassCallbackWithoutFinalizer, v8::WeakCallbackType::kParameter); + } +} + +void RuntimeReference::FirstPassCallback(const v8::WeakCallbackInfo& data) +{ + RuntimeReference* reference = data.GetParameter(); + + reference->persistent.Reset(); + data.SetSecondPassCallback(RuntimeReference::SecondPassCallback); +} + +void RuntimeReference::SecondPassCallback(const v8::WeakCallbackInfo& data) +{ + RuntimeReference* reference = data.GetParameter(); + + reference->Finalize(); +} + +void RuntimeReference::FirstPassCallbackWithoutFinalizer(const v8::WeakCallbackInfo& data) +{ + RuntimeReference* reference = data.GetParameter(); + + reference->persistent.Reset(); + delete reference; +} + +} // namespace v8impl \ No newline at end of file diff --git a/src/jsvm_reference.h b/src/jsvm_reference.h new file mode 100644 index 0000000000000000000000000000000000000000..a0d9a4f4b976bdff035d168c008bab48a17e62fa --- /dev/null +++ b/src/jsvm_reference.h @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2024 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 SRC_JSVM_REFERENCE_ +#define SRC_JSVM_REFERENCE_ +#include + +#include "jsvm_types.h" +#include "jsvm_util.h" + +namespace v8impl { +class RefTracker; +using RefList = RefTracker; + +class RefTracker { +public: + RefTracker() : next(nullptr), prev(nullptr) {} + + virtual ~RefTracker() = default; + + static void FinalizeAll(RefList* list); + +protected: + virtual void Finalize(); + + inline void Link(RefList* list); + + inline void Unlink() + { + if (prev != nullptr) { + prev->next = next; + } + if (next != nullptr) { + next->prev = prev; + } + prev = nullptr; + next = nullptr; + } + +private: + RefList* next; + RefList* prev; +}; + +class UserReference final : public RefTracker { +public: + static UserReference* New(JSVM_Env env, v8::Local value, uint32_t initialRefcount); + + static UserReference* NewData(JSVM_Env env, v8::Local data, uint32_t initialRefcount); + + ~UserReference() override; + + // Increase and decrease reference + uint32_t Ref(); + uint32_t Unref(); + uint32_t RefCount(); + + // Get v8::Local value + v8::Local Get(); + v8::Local GetData(); + + bool IsValue() + { + return isValue; + } + +protected: + UserReference(JSVM_Env env, v8::Local value, bool isValue, uint32_t initialRefcount); + + void Finalize() override; + +private: + void SetWeak(); + +private: + v8impl::Persistent persistent; + bool isValue; + JSVM_Env env; + uint32_t refcount; + bool canBeWeak; +}; + +class FinalizerTracker : public RefTracker { +protected: + FinalizerTracker(JSVM_Env env, JSVM_Finalize cb, void* data, void* hint); + +public: + static FinalizerTracker* New(JSVM_Env env, JSVM_Finalize cb, void* finalizeData, void* finalizeHint); + + ~FinalizerTracker() override; + + void* GetData() + { + return data; + } + +protected: + void ResetFinalizer(); + + void CallFinalizer(); + + void Finalize() override; + + void ResetEnv() + { + env = nullptr; + } + +private: + JSVM_Env env; + JSVM_Finalize cb; + void* data; + void* hint; +}; + +class RuntimeReference : public FinalizerTracker { +protected: + RuntimeReference(JSVM_Env env, v8::Local value, JSVM_Finalize cb, void* data, void* hint); + +public: + static RuntimeReference* New(JSVM_Env env, v8::Local value, void* data); + static RuntimeReference* New(JSVM_Env env, v8::Local value, JSVM_Finalize cb, void* data, void* hint); + static void DeleteReference(RuntimeReference* ref); + +private: + inline void SetWeak(bool needSecondPass); + static void FirstPassCallback(const v8::WeakCallbackInfo& data); + static void SecondPassCallback(const v8::WeakCallbackInfo& data); + static void FirstPassCallbackWithoutFinalizer(const v8::WeakCallbackInfo& data); + +private: + v8impl::Persistent persistent; +}; + +class TrackedStringResource : public FinalizerTracker { +public: + TrackedStringResource(JSVM_Env env, JSVM_Finalize finalizeCallback, void* data, void* finalizeHint) + : FinalizerTracker(env, finalizeCallback, data, finalizeHint) + {} + +protected: + // The only time Finalize() gets called before Dispose() is if the + // environment is dying. Finalize() expects that the item will be unlinked, + // so we do it here. V8 will still call Dispose() on us later, so we don't do + // any deleting here. We just null out env_ to avoid passing a stale pointer + // to the user's finalizer when V8 does finally call Dispose(). + void Finalize() override + { + Unlink(); + ResetEnv(); + } + + ~TrackedStringResource() override + { + CallFinalizer(); + } +}; + +class ExternalOneByteStringResource : public v8::String::ExternalOneByteStringResource, TrackedStringResource { +public: + ExternalOneByteStringResource(JSVM_Env env, + char* string, + const size_t length, + JSVM_Finalize finalizeCallback, + void* finalizeHint) + : TrackedStringResource(env, finalizeCallback, string, finalizeHint), string_(string), length_(length) + {} + + const char* data() const override + { + return string_; + } + size_t length() const override + { + return length_; + } + +private: + const char* string_; + const size_t length_; +}; + +class ExternalStringResource : public v8::String::ExternalStringResource, TrackedStringResource { +public: + ExternalStringResource(JSVM_Env env, + char16_t* string, + const size_t length, + JSVM_Finalize finalizeCallback, + void* finalizeHint) + : TrackedStringResource(env, finalizeCallback, string, finalizeHint), + string_(reinterpret_cast(string)), length_(length) + {} + + const uint16_t* data() const override + { + return string_; + } + size_t length() const override + { + return length_; + } + +private: + const uint16_t* string_; + const size_t length_; +}; + +} // namespace v8impl + +#endif \ No newline at end of file