@@ -975,6 +975,166 @@ overview over libuv handles managed by Node.js.
975
975
976
976
<a id =" callback-scopes " ></a >
977
977
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
+
978
1138
### Callback scopes
979
1139
980
1140
The public ` CallbackScope ` and the internally used ` InternalCallbackScope `
@@ -1082,6 +1242,8 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
1082
1242
[ ECMAScript realm ] : https://tc39.es/ecma262/#sec-code-realms
1083
1243
[ JavaScript value handles ] : #js-handles
1084
1244
[ 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
1085
1247
[ `BaseObject` ] : #baseobject
1086
1248
[ `Context` ] : #context
1087
1249
[ `Environment` ] : #environment
@@ -1117,3 +1279,4 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
1117
1279
[ libuv handles ] : #libuv-handles-and-requests
1118
1280
[ libuv requests ] : #libuv-handles-and-requests
1119
1281
[ 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