@@ -975,6 +975,228 @@ 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:
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
+
978
1200
### Callback scopes
979
1201
980
1202
The public ` CallbackScope ` and the internally used ` InternalCallbackScope `
@@ -1082,6 +1304,8 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
1082
1304
[ ECMAScript realm ] : https://tc39.es/ecma262/#sec-code-realms
1083
1305
[ JavaScript value handles ] : #js-handles
1084
1306
[ 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
1085
1309
[ `BaseObject` ] : #baseobject
1086
1310
[ `Context` ] : #context
1087
1311
[ `Environment` ] : #environment
@@ -1117,3 +1341,4 @@ static void GetUserInfo(const FunctionCallbackInfo<Value>& args) {
1117
1341
[ libuv handles ] : #libuv-handles-and-requests
1118
1342
[ libuv requests ] : #libuv-handles-and-requests
1119
1343
[ 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