@@ -975,6 +975,228 @@ 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:
1009+
1010+ ``` cpp
1011+ #include " cppgc_helpers.h"
1012+
1013+ // CPPGC_MIXIN is a helper macro for inheriting from cppgc::GarbageCollected,
1014+ // cppgc::NameProvider and public CppgcMixin. Per cppgc rules, it must be
1015+ // placed at the left-most position in the class hierarchy.
1016+ class MyWrap final : CPPGC_MIXIN(ContextifyScript) {
1017+ public:
1018+ SET_CPPGC_NAME(MyWrap) // Sets the heap snapshot name to "Node / MyWrap"
1019+
1020+ // The constructor can only be called by ` cppgc::MakeGarbageCollected() ` .
1021+ MyWrap(Environment* env, v8::Local< v8::Object > object);
1022+
1023+ // Helper for constructing MyWrap via ` cppgc::MakeGarbageCollected() ` .
1024+ // Can be invoked by other C++ code outside of this class if necessary.
1025+ // In that case the raw pointer returned may need to be managed by
1026+ // cppgc::Persistent<> or cppgc::Member<> with corresponding tracing code.
1027+ static MyWrap* New(Environment* env, v8::Local< v8::Object > object);
1028+ // Binding method to help constructing MyWrap in JavaScript.
1029+ static void New(const v8::FunctionCallbackInfo< v8::Value > & args);
1030+
1031+ void Trace(cppgc::Visitor* visitor) const final;
1032+ }
1033+ ```
1034+
1035+ `cppgc::GarbageCollected` types are expected to implement a
1036+ `void Trace(cppgc::Visitor* visitor) const` method. When they are the
1037+ final class in the hierarchy, this method must be marked `final`. For
1038+ classes extending `node::CppgcMixn`, this should typically dispatch a
1039+ call to `CppgcMixin::Trace()` first, then trace any additional owned data
1040+ it has. See `deps/v8/include/cppgc/garbage-collected.h` see what types of
1041+ data can be traced.
1042+
1043+ ```cpp
1044+ void MyWrap::Trace(cppgc::Visitor* visitor) const {
1045+ CppgcMixin::Trace(visitor);
1046+ visitor->Trace(...); // Trace any additional data MyWrap has
1047+ }
1048+ ```
1049+
1050+ #### Constructing and wrapping ` cppgc ` -managed objects
1051+
1052+ C++ objects subclassing ` node::CppgcMixin ` have a counterpart JavaScript object.
1053+ The two references each other internally - this cycle is well-understood by V8's
1054+ garbage collector and can be managed properly.
1055+
1056+ Similar to ` BaseObject ` s, ` cppgc ` -managed wrappers objects must be created from
1057+ object templates with at least ` node::CppgcMixin::kInternalFieldCount ` internal
1058+ fields. To unify handling of the wrappers, the internal fields of
1059+ ` node::CppgcMixin ` wrappers would have the same layout as ` BaseObject ` .
1060+
1061+ ``` cpp
1062+ // To create the v8::FunctionTemplate that can be used to instantiate a
1063+ // v8::Function for that serves as the JavaScript constructor of MyWrap:
1064+ Local<FunctionTemplate> ctor_template = NewFunctionTemplate(isolate, MyWrap::New);
1065+ ctor_template->InstanceTemplate ()->SetInternalFieldCount(
1066+ ContextifyScript::kInternalFieldCount);
1067+ ```
1068+
1069+ `cppgc::GarbageCollected` objects should not be allocated with usual C++
1070+ primitives (e.g. using `new` or `std::make_unique` is forbidden). Instead
1071+ they must be allocated using `cppgc::MakeGarbageCollected` - this would
1072+ allocate them in the V8 heap and allow V8's garbage collector to trace them.
1073+ It's recommended to use a `New` method to wrap the `cppgc::MakeGarbageCollected`
1074+ call so that external C++ code does not need to know about its memory management
1075+ scheme to construct it.
1076+
1077+ ```cpp
1078+ MyWrap* MyWrap::New(Environment* env, v8::Local<v8::Object> object) {
1079+ // Per cppgc rules, the constructor of MyWrap cannot be invoked directly.
1080+ // It's recommended to implement a New() static method that prepares
1081+ // and forwards the necessary arguments to cppgc::MakeGarbageCollected()
1082+ // and just return the raw pointer around - do not use any C++ smart
1083+ // pointer with this, as this is not managed by the native memory
1084+ // allocator but by V8.
1085+ return cppgc::MakeGarbageCollected<MyWrap>(
1086+ env->isolate()->GetCppHeap()->GetAllocationHandle(), env, object);
1087+ }
1088+
1089+ // Binding method to be invoked by JavaScript.
1090+ void MyWrap::New(const FunctionCallbackInfo<Value>& args) {
1091+ Environment* env = Environment::GetCurrent(args);
1092+ Isolate* isolate = env->isolate();
1093+ Local<Context> context = env->context();
1094+
1095+ CHECK(args.IsConstructCall());
1096+
1097+ // Get more arguments from JavaScript land if necessary.
1098+ New(env, args.This());
1099+ }
1100+ ```
1101+
1102+ In the constructor of ` node::CppgcMixin ` types, use
1103+ ` node::CppgcMixin::Wrap() ` to finish the wrapping so that
1104+ V8 can trace the C++ object from the JavaScript object.
1105+
1106+ ``` cpp
1107+ MyWrap::MyWrap (Environment* env, v8::Local< v8::Object > object) {
1108+ // This cannot invoke the mixin constructor and has to invoke via a static
1109+ // method from it, per cppgc rules.
1110+ CppgcMixin::Wrap(this, env, object);
1111+ }
1112+ ```
1113+
1114+ #### Unwrapping `cppgc`-managed wrapper objects
1115+
1116+ When given a `v8::Local<v8::Object>` that is known to be the JavaScript
1117+ wrapper object for `MyWrap`, uses the `node::CppgcMixin::Unwrap()` to
1118+ get the C++ object from it:
1119+
1120+ ```cpp
1121+ v8::Local<v8::Object> object = ...; // Obtain the JavaScript from somewhere.
1122+ MyWrap* wrap = CppgcMixin::Unwrap<MyWrap>(object);
1123+ ```
1124+
1125+ Similar to ` ASSIGN_OR_RETURN_UNWRAP ` , there is a ` ASSIGN_OR_RETURN_UNWRAP_CPPGC `
1126+ that can be used in binding methods to return early if the JavaScript object does
1127+ not wrap the desired type. And similar to ` BaseObject ` , ` node::CppgcMixin `
1128+ provides ` env() ` and ` object() ` methods to quickly access the associated
1129+ ` node::Environment ` and its JavaScript wrapper object.
1130+
1131+ ``` cpp
1132+ ASSIGN_OR_RETURN_UNWRAP_CPPGC (&wrap, object);
1133+ CHECK_EQ(wrap->object(), object);
1134+ ```
1135+
1136+ #### Creating C++ to JavaScript references in cppgc-managed objects
1137+
1138+ Unlike `BaseObject` which typically uses a `v8::Global` (either weak or strong)
1139+ to reference an object from the V8 heap, cppgc-managed objects are expected to
1140+ use `v8::TracedReference` (which supports any `v8::Data`). For example if the
1141+ `MyWrap` object owns a `v8::UnboundScript`, in the class body the reference
1142+ should be declared as
1143+
1144+ ```cpp
1145+ class MyWrap : ... {
1146+ v8::TracedReference<v8::UnboundScript> script;
1147+ }
1148+ ```
1149+
1150+ V8's garbage collector traces the references from ` MyWrap ` through the
1151+ ` MyWrap::Trace() ` method, which should call ` cppgc::Visitor::Trace ` on the
1152+ ` v8::TracedReference ` .
1153+
1154+ ``` cpp
1155+ void MyWrap::Trace (cppgc::Visitor* visitor) const {
1156+ CppgcMixin::Trace(visitor);
1157+ visitor->Trace(script); // v8::TracedReference is supported by cppgc::Visitor
1158+ }
1159+ ```
1160+
1161+ As long as a `MyWrap` object is alive, the `v8::UnboundScript` in its
1162+ `v8::TracedReference` will be kept alive. When the `MyWrap` object is no longer
1163+ reachable from the V8 heap, and there are no other references to the
1164+ `v8::UnboundScript` it owns, the `v8::UnboundScript` will be garbage collected
1165+ along with its owning `MyWrap`. The reference will also be automatically
1166+ captured in the heap snapshots.
1167+
1168+ #### Creating JavaScript to C++ references for cppgc-managed objects
1169+
1170+ To create a reference from another JavaScript object to a C++ wrapper
1171+ extending `node::CppgcMixin`, just create a JavaScript to JavaScript
1172+ reference using the JavaScript side of the wrapper, which can be accessed
1173+ using `node::CppgcMixin::object()`.
1174+
1175+ ```cpp
1176+ MyWrap* wrap = ....; // Obtain a reference to the cppgc-managed object.
1177+ Local<Object> referrer = ...; // This is the referrer object.
1178+ // To reference the C++ wrap from the JavaScript referrer, simply creates
1179+ // a usual JavaScript property reference - the key can be a symbol or a
1180+ // number too if necessary, or it can be a private symbol property added
1181+ // using SetPrivate(). wrap->object() can also be passed to the JavaScript
1182+ // land, which can be referenced by any JavaScript objects in an invisible
1183+ // manner using a WeakMap or being inside a closure.
1184+ referrer->Set(
1185+ context, FIXED_ONE_BYTE_STRING(isolate, "ref"), wrap->object()
1186+ ).ToLocalChecked();
1187+ ```
1188+
1189+ Typically, a newly created cppgc-managed wrapper object should be held alive
1190+ by the JavaScript land (for example, by being returned by a method and
1191+ staying alive in a closure). Long-lived cppgc objects can also
1192+ be held alive from C++ using persistent handles (see
1193+ ` deps/v8/include/cppgc/persistent.h ` ) or as members of other living
1194+ cppgc-managed objects (see ` deps/v8/include/cppgc/member.h ` ) if necessary.
1195+ Its destructor will be called when no other objects from the V8 heap reference
1196+ it, this can happen at any time after the garbage collector notices that
1197+ it's no longer reachable and before the V8 isolate is torn down.
1198+ See the [ Oilpan documentation in Chromium] [ ] for more details.
1199+
9781200### Callback scopes
9791201
9801202The public ` CallbackScope ` and the internally used ` InternalCallbackScope `
@@ -1082,6 +1304,8 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
10821304[ ECMAScript realm ] : https://tc39.es/ecma262/#sec-code-realms
10831305[ JavaScript value handles ] : #js-handles
10841306[ N-API ] : https://nodejs.org/api/n-api.html
1307+ [ Oilpan ] : https://v8.dev/blog/oilpan-library
1308+ [ Oilpan documentation in Chromium ] : https://chromium.googlesource.com/v8/v8/+/main/include/cppgc/README.md
10851309[ `BaseObject` ] : #baseobject
10861310[ `Context` ] : #context
10871311[ `Environment` ] : #environment
@@ -1117,3 +1341,4 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
11171341[ libuv handles ] : #libuv-handles-and-requests
11181342[ libuv requests ] : #libuv-handles-and-requests
11191343[ reference documentation for the libuv API ] : http://docs.libuv.org/en/v1.x/
1344+ [ unified heap design in Chromium ] : https://docs.google.com/document/d/1Hs60Zx1WPJ_LUjGvgzt1OQ5Cthu-fG-zif-vquUH_8c/edit
0 commit comments