#ifndef SRC_CPPGC_HELPERS_H_ #define SRC_CPPGC_HELPERS_H_ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include // std::remove_reference #include "cppgc/garbage-collected.h" #include "cppgc/name-provider.h" #include "env.h" #include "memory_tracker.h" #include "v8-cppgc.h" #include "v8-sandbox.h" #include "v8.h" namespace node { /** * This is a helper mixin with a BaseObject-like interface to help * implementing wrapper objects managed by V8's cppgc (Oilpan) library. * cppgc-manged objects in Node.js internals should extend this mixin, * while non-cppgc-managed objects typically extend BaseObject - the * latter are being migrated to be cppgc-managed wherever it's beneficial * and practical. Typically cppgc-managed objects are more efficient to * keep track of (which lowers initialization cost) and work better * with V8's GC scheduling. * * A cppgc-managed native wrapper should look something like this, note * that per cppgc rules, CPPGC_MIXIN(Klass) must be at the left-most * position in the hierarchy (which ensures cppgc::GarbageCollected * is at the left-most position). * * class Klass final : CPPGC_MIXIN(Klass) { * public: * SET_CPPGC_NAME(Klass) // Sets the heap snapshot name to "Node / Klass" * void Trace(cppgc::Visitor* visitor) const final { * CppgcMixin::Trace(visitor); * visitor->Trace(...); // Trace any additional owned traceable data * } * } */ class CppgcMixin : public cppgc::GarbageCollectedMixin { public: // To help various callbacks access wrapper objects with different memory // management, cppgc-managed objects share the same layout as BaseObjects. enum InternalFields { kEmbedderType = 0, kSlot, kInternalFieldCount }; // The initialization cannot be done in the mixin constructor but has to be // invoked from the child class constructor, per cppgc::GarbageCollectedMixin // rules. template static void Wrap(T* ptr, Environment* env, v8::Local obj) { CHECK_GE(obj->InternalFieldCount(), T::kInternalFieldCount); ptr->env_ = env; v8::Isolate* isolate = env->isolate(); ptr->traced_reference_ = v8::TracedReference(isolate, obj); v8::Object::Wrap(isolate, obj, ptr); // Keep the layout consistent with BaseObjects. obj->SetAlignedPointerInInternalField( kEmbedderType, env->isolate_data()->embedder_id_for_cppgc()); obj->SetAlignedPointerInInternalField(kSlot, ptr); } v8::Local object() const { return traced_reference_.Get(env_->isolate()); } Environment* env() const { return env_; } template static T* Unwrap(v8::Local obj) { // We are not using v8::Object::Unwrap currently because that requires // access to isolate which the ASSIGN_OR_RETURN_UNWRAP macro that we'll shim // with ASSIGN_OR_RETURN_UNWRAP_GC doesn't take, and we also want a // signature consistent with BaseObject::Unwrap() to avoid churn. Since // cppgc-managed objects share the same layout as BaseObjects, just unwrap // from the pointer in the internal field, which should be valid as long as // the object is still alive. if (obj->InternalFieldCount() != T::kInternalFieldCount) { return nullptr; } T* ptr = static_cast(obj->GetAlignedPointerFromInternalField(T::kSlot)); return ptr; } // Subclasses are expected to invoke CppgcMixin::Trace() in their own Trace() // methods. void Trace(cppgc::Visitor* visitor) const override { visitor->Trace(traced_reference_); } private: Environment* env_; v8::TracedReference traced_reference_; }; // If the class doesn't have additional owned traceable data, use this macro to // save the implementation of a custom Trace() method. #define DEFAULT_CPPGC_TRACE() \ void Trace(cppgc::Visitor* visitor) const final { \ CppgcMixin::Trace(visitor); \ } // This macro sets the node name in the heap snapshot with a "Node /" prefix. // Classes that use this macro must extend cppgc::NameProvider. #define SET_CPPGC_NAME(Klass) \ inline const char* GetHumanReadableName() const final { \ return "Node / " #Klass; \ } /** * Similar to ASSIGN_OR_RETURN_UNWRAP() but works on cppgc-managed types * inheriting CppgcMixin. */ #define ASSIGN_OR_RETURN_UNWRAP_CPPGC(ptr, obj, ...) \ do { \ *ptr = CppgcMixin::Unwrap< \ typename std::remove_reference::type>(obj); \ if (*ptr == nullptr) return __VA_ARGS__; \ } while (0) } // namespace node /** * Helper macro the manage the cppgc-based wrapper hierarchy. This must * be used at the left-most position - right after `:` in the class inheritance, * like this: * class Klass : CPPGC_MIXIN(Klass) ... {} * * This needs to disable linters because it will be at odds with * clang-format. */ #define CPPGC_MIXIN(Klass) \ public /* NOLINT(whitespace/indent) */ \ cppgc::GarbageCollected, public cppgc::NameProvider, public CppgcMixin #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #endif // SRC_CPPGC_HELPERS_H_