@@ -975,6 +975,166 @@ overview over libuv handles managed by Node.js.
975975
976976<a id =" callback-scopes " ></a >
977977
978+ ### ` CppgcMixin `
979+
980+ V8 comes with a trace-based C++ garbage collection library called
981+ [ Oilpan] [ ] , whose API is in headers under` deps/v8/include/cppgc ` .
982+ In this document we refer to it as ` cppgc ` since that's the namespace
983+ of the library.
984+
985+ C++ objects managed using ` cppgc ` are allocated in the V8 heap
986+ and traced by V8's garbage collector. The ` cppgc ` library provides
987+ APIs for embedders to create references between cppgc-managed objects
988+ and other objects in the V8 heap (such as JavaScript objects or other
989+ objects in the V8 C++ API that can be passed around with V8 handles)
990+ in a way that's understood by V8's garbage collector.
991+ This helps avoiding accidental memory leaks and use-after-frees coming
992+ from incorrect cross-heap reference tracking, especially when there are
993+ cyclic references. This is what powers the
994+ [ unified heap design in Chromium] [ ] to avoid cross-heap memory issues,
995+ and it's being rolled out in Node.js to reap similar benefits.
996+
997+ For general guidance on how to use ` cppgc ` , see the
998+ [ Oilpan documentation in Chromium] [ ] . In Node.js there is a helper
999+ mixin ` node::CppgcMixin ` from ` cppgc_helpers.h ` to help implementing
1000+ ` cppgc ` -managed wrapper objects with a [ ` BaseObject ` ] [ ] -like interface.
1001+ ` cppgc ` -manged objects in Node.js internals should extend this mixin,
1002+ while non-` cppgc ` -managed objects typically extend ` BaseObject ` - the
1003+ latter are being migrated to be ` cppgc ` -managed wherever it's beneficial
1004+ and practical. Typically ` cppgc ` -managed objects are more efficient to
1005+ keep track of (which lowers initialization cost) and work better
1006+ with V8's GC scheduling.
1007+
1008+ A ` cppgc ` -managed native wrapper should look something like this, note
1009+ that per cppgc rules, ` cppgc::GarbageCollected<> ` must be the left-most
1010+ base class.
1011+
1012+ ``` cpp
1013+ #include " cppgc_helpers.h"
1014+
1015+ class MyWrap final : public cppgc::GarbageCollected<MyWrap >,
1016+ public cppgc::NameProvider,
1017+ public CppgcMixin {
1018+ public:
1019+ SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap"
1020+
1021+ MyWrap(Environment* env, v8::Local< v8::Object > object);
1022+ MyWrap* New(Environment* env, v8::Local< v8::Object > object);
1023+
1024+ void Trace(cppgc::Visitor* visitor) const final;
1025+ }
1026+ ```
1027+
1028+ `cppgc::GarbageCollected` objects should not be allocated with usual C++
1029+ primitives (e.g. using `new` or `std::make_unique` is forbidden). Instead
1030+ they must be allocated using `cppgc::MakeGarbageCollected` - this would
1031+ allocate them in the V8 heap and allow V8's garbage collector to trace them.
1032+ It's recommended to use a `New` method to prepare the arguments and invoke
1033+ `cppgc::MakeGarbageCollected` (which forwards the arguments to the child class
1034+ constructor).
1035+
1036+ ```cpp
1037+ MyWrap::MyWrap(Environment* env, v8::Local<v8::Object> object) {
1038+ // This cannot invoke the mixin constructor and has to invoke a static
1039+ // method from it, per cppgc rules. node::CppgcMixin provides
1040+ // InitializeCppgc() for wrapping the C++ object with the provided
1041+ // JavaScript object.
1042+ InitializeCppgc(this, env, object);
1043+ }
1044+
1045+ MyWrap* MyWrap::New(Environment* env, v8::Local<v8::Object> object) {
1046+ // Per cppgc rules, the constructor of MyWrap cannot be invoked directly.
1047+ // It's recommended to implement a New() static method that prepares
1048+ // and forwards the necessary arguments to cppgc::MakeGarbageCollected()
1049+ // and just return the raw pointer around - do not use any C++ smart
1050+ // pointer with this, as this is not managed by the native memory
1051+ // allocator but by V8.
1052+ return cppgc::MakeGarbageCollected<MyWrap>(
1053+ env->isolate()->GetCppHeap()->GetAllocationHandle(), env, object);
1054+ }
1055+ ```
1056+
1057+ ` cppgc::GarbageCollected ` types are expected to implement a
1058+ ` void Trace(cppgc::Visitor* visitor) const ` method. When they are the
1059+ final class in the hierarchy, this method must be marked ` final ` . For
1060+ classes extending ` node::CppgcMixn ` , this should typically dispatch a
1061+ call to ` CppgcMixin::Trace() ` first, then trace any additional owned data
1062+ it has. See ` deps/v8/include/cppgc/garbage-collected.h ` see what types of
1063+ data can be traced.
1064+
1065+ ``` cpp
1066+ void MyWrap::Trace (cppgc::Visitor* visitor) const {
1067+ CppgcMixin::Trace(visitor);
1068+ visitor->Trace(...); // Trace any additional data MyWrap has
1069+ }
1070+ ```
1071+
1072+ #### Creating C++ to JavaScript references in cppgc-managed objects
1073+
1074+ Unlike `BaseObject` which typically uses a `v8::Global` (either weak or strong)
1075+ to reference an object from the V8 heap, cppgc-managed objects are expected to
1076+ use `v8::TracedReference` (which supports any `v8::Data`). For example if the
1077+ `MyWrap` object owns a `v8::UnboundScript`, in the class body the reference
1078+ should be declared as
1079+
1080+ ```cpp
1081+ class MyWrap : ... {
1082+ v8::TracedReference<v8::UnboundScript> script;
1083+ }
1084+ ```
1085+
1086+ V8's garbage collector traces the references from ` MyWrap ` through the
1087+ ` MyWrap::Trace() ` method, which should call ` cppgc::Visitor::Trace ` on the
1088+ ` v8::TracedReference ` .
1089+
1090+ ``` cpp
1091+ void MyWrap::Trace (cppgc::Visitor* visitor) const {
1092+ CppgcMixin::Trace(visitor);
1093+ visitor->Trace(script); // v8::TracedReference is supported by cppgc::Visitor
1094+ }
1095+ ```
1096+
1097+ As long as a `MyWrap` object is alive, the `v8::UnboundScript` in its
1098+ `v8::TracedReference` will be kept alive. When the `MyWrap` object is no longer
1099+ reachable from the V8 heap, and there are no other references to the
1100+ `v8::UnboundScript` it owns, the `v8::UnboundScript` will be garbage collected
1101+ along with its owning `MyWrap`. The reference will also be automatically
1102+ captured in the heap snapshots.
1103+
1104+ #### Creating JavaScript to C++ references for cppgc-managed objects
1105+
1106+ All C++ objects using `node::CppgcMixin` have a counterpart JavaScript object,
1107+ which can be obtained in C++ using `node::CppgcMixin::object()`.
1108+ The two references each other internally - this cycle is well-understood by V8's
1109+ garbage collector and can be managed properly. To create a reference from another
1110+ JavaScript object to a C++ wrapper extending `node::CppgcMixin`, just create a
1111+ JavaScript to JavaScript reference via its counterpart JavaScript object instead.
1112+
1113+ ```cpp
1114+ MyWrap* wrap = ....; // Obtain a reference to the cppgc-managed object.
1115+ Local<Object> referrer = ...; // This is the referrer object.
1116+ // To reference the C++ wrap from the JavaScript referrer, simply creates
1117+ // a usual JavaScript property reference - the key can be a symbol or a
1118+ // number too if necessary, or it can be a private symbol property added
1119+ // using SetPrivate(). wrap->object() can also be passed to the JavaScript
1120+ // land, which can be referenced by any JavaScript objects in an invisible
1121+ // manner using a WeakMap or being inside a closure.
1122+ referrer->Set(
1123+ context, FIXED_ONE_BYTE_STRING(isolate, "ref"), wrap->object()
1124+ ).ToLocalChecked();
1125+ ```
1126+
1127+ Typically, a newly created cppgc-managed wrapper object should be held alive
1128+ by the JavaScript land (for example, by being returned by a method and
1129+ staying alive in a closure). Long-lived cppgc objects can also
1130+ be held alive from C++ using persistent handles (see
1131+ ` deps/v8/include/cppgc/persistent.h ` ) or as members of other living
1132+ cppgc-managed objects (see ` deps/v8/include/cppgc/member.h ` ) if necessary.
1133+ Its destructor will be called when no other objects from the V8 heap reference
1134+ it, this can happen at any time after the garbage collector notices that
1135+ it's no longer reachable and before the V8 isolate is torn down.
1136+ See the [ Oilpan documentation in Chromium] [ ] for more details.
1137+
9781138### Callback scopes
9791139
9801140The public ` CallbackScope ` and the internally used ` InternalCallbackScope `
@@ -1082,6 +1242,8 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
10821242[ ECMAScript realm ] : https://tc39.es/ecma262/#sec-code-realms
10831243[ JavaScript value handles ] : #js-handles
10841244[ N-API ] : https://nodejs.org/api/n-api.html
1245+ [ Oilpan ] : https://v8.dev/blog/oilpan-library
1246+ [ Oilpan documentation in Chromium ] : https://chromium.googlesource.com/v8/v8/+/main/include/cppgc/README.md
10851247[ `BaseObject` ] : #baseobject
10861248[ `Context` ] : #context
10871249[ `Environment` ] : #environment
@@ -1117,3 +1279,4 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
11171279[ libuv handles ] : #libuv-handles-and-requests
11181280[ libuv requests ] : #libuv-handles-and-requests
11191281[ reference documentation for the libuv API ] : http://docs.libuv.org/en/v1.x/
1282+ [ unified heap design in Chromium ] : https://docs.google.com/document/d/1Hs60Zx1WPJ_LUjGvgzt1OQ5Cthu-fG-zif-vquUH_8c/edit
0 commit comments