◐ Shell
clean mode source ↗

src: track cppgc wrappers with a list in Realm · nodejs/node@a35cc21

@@ -6,14 +6,19 @@

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"

14151516

namespace node {

161718+

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);

626863-

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

}

667567-

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);

84788579

// 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

}

908485+

// 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; }

109134110135

/**

111136

* Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types