blob: 20b9004917cfbe69013abc4e63a418dd59a331d6 [file] [edit]
#ifndef SRC_CPPGC_HELPERS_H_
#define SRC_CPPGC_HELPERS_H_
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include <type_traits> // 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 <typename T>
static void Wrap(T* ptr, Environment* env, v8::Local<v8::Object> obj) {
CHECK_GE(obj->InternalFieldCount(), T::kInternalFieldCount);
ptr->env_ = env;
v8::Isolate* isolate = env->isolate();
ptr->traced_reference_ = v8::TracedReference<v8::Object>(isolate, obj);
v8::Object::Wrap<v8::CppHeapPointerTag::kDefaultTag>(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<v8::Object> object() const {
return traced_reference_.Get(env_->isolate());
}
Environment* env() const { return env_; }
template <typename T>
static T* Unwrap(v8::Local<v8::Object> 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<T*>(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<v8::Object> 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<decltype(**ptr)>::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<Klass>, public cppgc::NameProvider, public CppgcMixin
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif // SRC_CPPGC_HELPERS_H_