|
6 | 6 | #include <type_traits> // std::remove_reference |
7 | 7 | #include "cppgc/garbage-collected.h" |
8 | 8 | #include "cppgc/name-provider.h" |
9 | | -#include "env.h" |
| 9 | +#include "cppgc/persistent.h" |
10 | 10 | #include "memory_tracker.h" |
| 11 | +#include "util.h" |
11 | 12 | #include "v8-cppgc.h" |
12 | 13 | #include "v8-sandbox.h" |
13 | 14 | #include "v8.h" |
14 | 15 |
|
15 | 16 | namespace node { |
16 | 17 |
|
| 18 | +class Environment; |
| 19 | +class Realm; |
| 20 | +class CppgcWrapperListNode; |
| 21 | + |
17 | 22 | /** |
18 | 23 | * This is a helper mixin with a BaseObject-like interface to help |
19 | 24 | * implementing wrapper objects managed by V8's cppgc (Oilpan) library. |
@@ -25,20 +30,29 @@ namespace node { |
25 | 30 | * with V8's GC scheduling. |
26 | 31 | * |
27 | 32 | * A cppgc-managed native wrapper should look something like this, note |
28 | | - * that per cppgc rules, CPPGC_MIXIN(Klass) must be at the left-most |
| 33 | + * that per cppgc rules, CPPGC_MIXIN(MyWrap) must be at the left-most |
29 | 34 | * position in the hierarchy (which ensures cppgc::GarbageCollected |
30 | 35 | * is at the left-most position). |
31 | 36 | * |
32 | | - * class Klass final : CPPGC_MIXIN(Klass) { |
| 37 | + * class MyWrap final : CPPGC_MIXIN(MyWrap) { |
33 | 38 | * public: |
34 | | - * SET_CPPGC_NAME(Klass) // Sets the heap snapshot name to "Node / Klass" |
| 39 | + * SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap" |
35 | 40 | * void Trace(cppgc::Visitor* visitor) const final { |
36 | 41 | * CppgcMixin::Trace(visitor); |
37 | 42 | * visitor->Trace(...); // Trace any additional owned traceable data |
38 | 43 | * } |
39 | 44 | * } |
| 45 | + * |
| 46 | + * If the wrapper needs to perform cleanups when it's destroyed and that |
| 47 | + * cleanup relies on a living Node.js `Realm`, it should implement a |
| 48 | + * pattern like this: |
| 49 | + * |
| 50 | + * ~MyWrap() { this->Destroy(); } |
| 51 | + * void Clean(Realm* env) override { |
| 52 | + * // Do cleanup that relies on a living Environemnt. |
| 53 | + * } |
40 | 54 | */ |
41 | | -class CppgcMixin : public cppgc::GarbageCollectedMixin { |
| 55 | +class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer { |
42 | 56 | public: |
43 | 57 | // To help various callbacks access wrapper objects with different memory |
44 | 58 | // management, cppgc-managed objects share the same layout as BaseObjects. |
@@ -48,48 +62,58 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin { |
48 | 62 | // invoked from the child class constructor, per cppgc::GarbageCollectedMixin |
49 | 63 | // rules. |
50 | 64 | template <typename T> |
51 | | - static void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj) { |
52 | | - CHECK_GE(obj->InternalFieldCount(), T::kInternalFieldCount); |
53 | | - ptr->env_ = env; |
54 | | - v8::Isolate* isolate = env->isolate(); |
55 | | - ptr->traced_reference_ = v8::TracedReference<v8::Object>(isolate, obj); |
56 | | - v8::Object::Wrap<v8::CppHeapPointerTag::kDefaultTag>(isolate, obj, ptr); |
57 | | - // Keep the layout consistent with BaseObjects. |
58 | | - obj->SetAlignedPointerInInternalField( |
59 | | - kEmbedderType, env->isolate_data()->embedder_id_for_cppgc()); |
60 | | - obj->SetAlignedPointerInInternalField(kSlot, ptr); |
61 | | - } |
| 65 | + static inline void Wrap(T* ptr, Realm* realm, v8::Local<v8::Object> obj); |
| 66 | + template <typename T> |
| 67 | + static inline void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj); |
62 | 68 |
|
63 | | - v8::Local<v8::Object> object() const { |
64 | | - return traced_reference_.Get(env_->isolate()); |
| 69 | + inline v8::Local<v8::Object> object() const; |
| 70 | + inline Environment* env() const; |
| 71 | + inline Realm* realm() const { return realm_; } |
| 72 | + inline v8::Local<v8::Object> object(v8::Isolate* isolate) const { |
| 73 | + return traced_reference_.Get(isolate); |
65 | 74 | } |
66 | 75 |
|
67 | | - Environment* env() const { return env_; } |
68 | | - |
69 | 76 | template <typename T> |
70 | | - static T* Unwrap(v8::Local<v8::Object> obj) { |
71 | | - // We are not using v8::Object::Unwrap currently because that requires |
72 | | - // access to isolate which the ASSIGN_OR_RETURN_UNWRAP macro that we'll shim |
73 | | - // with ASSIGN_OR_RETURN_UNWRAP_GC doesn't take, and we also want a |
74 | | - // signature consistent with BaseObject::Unwrap() to avoid churn. Since |
75 | | - // cppgc-managed objects share the same layout as BaseObjects, just unwrap |
76 | | - // from the pointer in the internal field, which should be valid as long as |
77 | | - // the object is still alive. |
78 | | - if (obj->InternalFieldCount() != T::kInternalFieldCount) { |
79 | | - return nullptr; |
80 | | - } |
81 | | - T* ptr = static_cast<T*>(obj->GetAlignedPointerFromInternalField(T::kSlot)); |
82 | | - return ptr; |
83 | | - } |
| 77 | + static inline T* Unwrap(v8::Local<v8::Object> obj); |
84 | 78 |
|
85 | 79 | // Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace() |
86 | 80 | // methods. |
87 | 81 | void Trace(cppgc::Visitor* visitor) const override { |
88 | 82 | visitor->Trace(traced_reference_); |
89 | 83 | } |
90 | 84 |
|
| 85 | + // TODO(joyeecheung): use ObjectSizeTrait; |
| 86 | + inline size_t SelfSize() const override { return sizeof(*this); } |
| 87 | + inline bool IsCppgcWrapper() const override { return true; } |
| 88 | + |
| 89 | + // This is run for all the remaining Cppgc wrappers tracked in the Realm |
| 90 | + // during Realm shutdown. The destruction of the wrappers would happen later, |
| 91 | + // when the final garbage collection is triggered when CppHeap is torn down as |
| 92 | + // part of the Isolate teardown. If subclasses of CppgcMixin wish to perform |
| 93 | + // cleanups that depend on the Realm during destruction, they should implment |
| 94 | + // it in a Clean() override, and then call this->Finalize() from their |
| 95 | + // destructor. Outside of Finalize(), subclasses should avoid calling |
| 96 | + // into JavaScript or perform any operation that can trigger garbage |
| 97 | + // collection during the destruction. |
| 98 | + void Finalize() { |
| 99 | + if (realm_ == nullptr) return; |
| 100 | + this->Clean(realm_); |
| 101 | + realm_ = nullptr; |
| 102 | + } |
| 103 | + |
| 104 | + // The default implementation of Clean() is a no-op. If subclasses wish |
| 105 | + // to perform cleanup that require a living Realm, they should |
| 106 | + // should put the cleanups in a Clean() override, and call this->Finalize() |
| 107 | + // in the destructor, instead of doing those cleanups directly in the |
| 108 | + // destructor. |
| 109 | + virtual void Clean(Realm* realm) {} |
| 110 | + |
| 111 | + inline ~CppgcMixin(); |
| 112 | + |
| 113 | + friend class CppgcWrapperListNode; |
| 114 | + |
91 | 115 | private: |
92 | | - Environment* env_; |
| 116 | + Realm* realm_ = nullptr; |
93 | 117 | v8::TracedReference<v8::Object> traced_reference_; |
94 | 118 | }; |
95 | 119 |
|
@@ -105,7 +129,8 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin { |
105 | 129 | #define SET_CPPGC_NAME(Klass) \ |
106 | 130 | inline const char* GetHumanReadableName() const final { \ |
107 | 131 | return "Node / " #Klass; \ |
108 | | - } |
| 132 | + } \ |
| 133 | + inline const char* MemoryInfoName() const override { return #Klass; } |
109 | 134 |
|
110 | 135 | /** |
111 | 136 | * Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types |
|