66#include <type_traits> // std::remove_reference
77#include "cppgc/garbage-collected.h"
88#include "cppgc/name-provider.h"
9- #include "env .h"
9+ #include "cppgc/persistent .h"
1010#include "memory_tracker.h"
11+ #include "util.h"
1112#include "v8-cppgc.h"
1213#include "v8-sandbox.h"
1314#include "v8.h"
1415
1516namespace node {
1617
18+ class Environment;
19+ class Realm;
20+ class CppgcWrapperListNode;
21+
1722/**
1823 * This is a helper mixin with a BaseObject-like interface to help
1924 * implementing wrapper objects managed by V8's cppgc (Oilpan) library.
@@ -25,20 +30,29 @@ namespace node {
2530 * with V8's GC scheduling.
2631 *
2732 * 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
2934 * position in the hierarchy (which ensures cppgc::GarbageCollected
3035 * is at the left-most position).
3136 *
32- * class Klass final : CPPGC_MIXIN(Klass ) {
37+ * class MyWrap final : CPPGC_MIXIN(MyWrap ) {
3338 * 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 "
3540 * void Trace(cppgc::Visitor* visitor) const final {
3641 * CppgcMixin::Trace(visitor);
3742 * visitor->Trace(...); // Trace any additional owned traceable data
3843 * }
3944 * }
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+ * }
4054 */
41- class CppgcMixin : public cppgc::GarbageCollectedMixin {
55+ class CppgcMixin : public cppgc::GarbageCollectedMixin, public MemoryRetainer {
4256 public:
4357 // To help various callbacks access wrapper objects with different memory
4458 // management, cppgc-managed objects share the same layout as BaseObjects.
@@ -48,48 +62,58 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin {
4862 // invoked from the child class constructor, per cppgc::GarbageCollectedMixin
4963 // rules.
5064 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);
6268
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);
6574 }
6675
67- Environment* env() const { return env_; }
68-
6976 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);
8478
8579 // Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace()
8680 // methods.
8781 void Trace(cppgc::Visitor* visitor) const override {
8882 visitor->Trace(traced_reference_);
8983 }
9084
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+
91115 private:
92- Environment* env_ ;
116+ Realm* realm_ = nullptr ;
93117 v8::TracedReference<v8::Object> traced_reference_;
94118};
95119
@@ -105,7 +129,8 @@ class CppgcMixin : public cppgc::GarbageCollectedMixin {
105129#define SET_CPPGC_NAME(Klass) \
106130 inline const char* GetHumanReadableName() const final { \
107131 return "Node / " #Klass; \
108- }
132+ } \
133+ inline const char* MemoryInfoName() const override { return #Klass; }
109134
110135/**
111136 * Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types
0 commit comments