Skip to content

Commit 6ce0b88

Browse files
src,tools: initialize cppgc
This patch: - Initializes cppgc in InitializeOncePerProcess() when kNoInitializeCppgc is not set - Create a CppHeap and attach it to the Isolate when there isn't one already during IsolateData initialization. The CppHeap is detached and terminated when IsolateData is freed. - Publishes the cppgc headers in the tarball. This allows C++ addons to start using cppgc to manage objects. A helper node::SetCppgcReference() is also added to help addons enable cppgc tracing in a user-defined object. Co-authored-by: Joyee Cheung <joyeec9h3@gmail.com> Refs: nodejs#40786 PR-URL: nodejs#45704 Refs: https://docs.google.com/document/d/1ny2Qz_EsUnXGKJRGxoA-FXIE2xpLgaMAN6jD7eAkqFQ/edit Reviewed-By: Stephen Belanger <admin@stephenbelanger.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
1 parent 9891d34 commit 6ce0b88

File tree

11 files changed

+290
-3
lines changed

11 files changed

+290
-3
lines changed

src/env-inl.h

+26
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include "node_realm-inl.h"
3535
#include "util-inl.h"
3636
#include "uv.h"
37+
#include "v8-cppgc.h"
3738
#include "v8.h"
3839

3940
#include <cstddef>
@@ -61,6 +62,31 @@ inline uv_loop_t* IsolateData::event_loop() const {
6162
return event_loop_;
6263
}
6364

65+
inline void IsolateData::SetCppgcReference(v8::Isolate* isolate,
66+
v8::Local<v8::Object> object,
67+
void* wrappable) {
68+
v8::CppHeap* heap = isolate->GetCppHeap();
69+
CHECK_NOT_NULL(heap);
70+
v8::WrapperDescriptor descriptor = heap->wrapper_descriptor();
71+
uint16_t required_size = std::max(descriptor.wrappable_instance_index,
72+
descriptor.wrappable_type_index);
73+
CHECK_GT(object->InternalFieldCount(), required_size);
74+
75+
uint16_t* id_ptr = nullptr;
76+
{
77+
Mutex::ScopedLock lock(isolate_data_mutex_);
78+
auto it =
79+
wrapper_data_map_.find(descriptor.embedder_id_for_garbage_collected);
80+
CHECK_NE(it, wrapper_data_map_.end());
81+
id_ptr = &(it->second->cppgc_id);
82+
}
83+
84+
object->SetAlignedPointerInInternalField(descriptor.wrappable_type_index,
85+
id_ptr);
86+
object->SetAlignedPointerInInternalField(descriptor.wrappable_instance_index,
87+
wrappable);
88+
}
89+
6490
inline uint16_t* IsolateData::embedder_id_for_cppgc() const {
6591
return &(wrapper_data_->cppgc_id);
6692
}

src/env.cc

+26
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ using errors::TryCatchScope;
3737
using v8::Array;
3838
using v8::Boolean;
3939
using v8::Context;
40+
using v8::CppHeap;
41+
using v8::CppHeapCreateParams;
4042
using v8::EmbedderGraph;
4143
using v8::EscapableHandleScope;
4244
using v8::Function;
@@ -61,6 +63,7 @@ using v8::TracingController;
6163
using v8::TryCatch;
6264
using v8::Undefined;
6365
using v8::Value;
66+
using v8::WrapperDescriptor;
6467
using worker::Worker;
6568

6669
int const ContextEmbedderTag::kNodeContextTag = 0x6e6f64;
@@ -538,6 +541,14 @@ IsolateData::IsolateData(Isolate* isolate,
538541
// for embedder ID, V8 could accidentally enable cppgc on them. So
539542
// safe guard against this.
540543
DCHECK_NE(descriptor.wrappable_type_index, BaseObject::kSlot);
544+
} else {
545+
cpp_heap_ = CppHeap::Create(
546+
platform,
547+
CppHeapCreateParams{
548+
{},
549+
WrapperDescriptor(
550+
BaseObject::kEmbedderType, BaseObject::kSlot, cppgc_id)});
551+
isolate->AttachCppHeap(cpp_heap_.get());
541552
}
542553
// We do not care about overflow since we just want this to be different
543554
// from the cppgc id.
@@ -565,6 +576,21 @@ IsolateData::IsolateData(Isolate* isolate,
565576
}
566577
}
567578

579+
IsolateData::~IsolateData() {
580+
if (cpp_heap_ != nullptr) {
581+
// The CppHeap must be detached before being terminated.
582+
isolate_->DetachCppHeap();
583+
cpp_heap_->Terminate();
584+
}
585+
}
586+
587+
// Public API
588+
void SetCppgcReference(Isolate* isolate,
589+
Local<Object> object,
590+
void* wrappable) {
591+
IsolateData::SetCppgcReference(isolate, object, wrappable);
592+
}
593+
568594
void IsolateData::MemoryInfo(MemoryTracker* tracker) const {
569595
#define V(PropertyName, StringValue) \
570596
tracker->TrackField(#PropertyName, PropertyName());

src/env.h

+10
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@
6262
#include <unordered_set>
6363
#include <vector>
6464

65+
namespace v8 {
66+
class CppHeap;
67+
}
68+
6569
namespace node {
6670

6771
namespace shadow_realm {
@@ -136,6 +140,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
136140
MultiIsolatePlatform* platform = nullptr,
137141
ArrayBufferAllocator* node_allocator = nullptr,
138142
const SnapshotData* snapshot_data = nullptr);
143+
~IsolateData();
139144

140145
SET_MEMORY_INFO_NAME(IsolateData)
141146
SET_SELF_SIZE(IsolateData)
@@ -148,6 +153,10 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
148153
uint16_t* embedder_id_for_cppgc() const;
149154
uint16_t* embedder_id_for_non_cppgc() const;
150155

156+
static inline void SetCppgcReference(v8::Isolate* isolate,
157+
v8::Local<v8::Object> object,
158+
void* wrappable);
159+
151160
inline uv_loop_t* event_loop() const;
152161
inline MultiIsolatePlatform* platform() const;
153162
inline const SnapshotData* snapshot_data() const;
@@ -229,6 +238,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer {
229238
NodeArrayBufferAllocator* const node_allocator_;
230239
MultiIsolatePlatform* platform_;
231240
const SnapshotData* snapshot_data_;
241+
std::unique_ptr<v8::CppHeap> cpp_heap_;
232242
std::shared_ptr<PerIsolateOptions> options_;
233243
worker::Worker* worker_context_ = nullptr;
234244
bool is_building_snapshot_ = false;

src/node.cc

+14
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
#endif // NODE_USE_V8_PLATFORM
6464
#include "v8-profiler.h"
6565

66+
#include "cppgc/platform.h"
67+
6668
#if HAVE_INSPECTOR
6769
#include "inspector/worker_inspector.h" // ParentInspectorHandle
6870
#endif
@@ -1116,6 +1118,14 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args,
11161118
V8::Initialize();
11171119
}
11181120

1121+
if (!(flags & ProcessInitializationFlags::kNoInitializeCppgc)) {
1122+
v8::PageAllocator* allocator = nullptr;
1123+
if (result->platform_ != nullptr) {
1124+
allocator = result->platform_->GetPageAllocator();
1125+
}
1126+
cppgc::InitializeProcess(allocator);
1127+
}
1128+
11191129
performance::performance_v8_start = PERFORMANCE_NOW();
11201130
per_process::v8_initialized = true;
11211131

@@ -1135,6 +1145,10 @@ void TearDownOncePerProcess() {
11351145
ResetSignalHandlers();
11361146
}
11371147

1148+
if (!(flags & ProcessInitializationFlags::kNoInitializeCppgc)) {
1149+
cppgc::ShutdownProcess();
1150+
}
1151+
11381152
per_process::v8_initialized = false;
11391153
if (!(flags & ProcessInitializationFlags::kNoInitializeV8)) {
11401154
V8::Dispose();

src/node.h

+24-1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,10 @@ enum Flags : uint32_t {
261261
kNoUseLargePages = 1 << 11,
262262
// Skip printing output for --help, --version, --v8-options.
263263
kNoPrintHelpOrVersionOutput = 1 << 12,
264+
// Do not perform cppgc initialization. If set, the embedder must call
265+
// cppgc::InitializeProcess() before creating a Node.js environment
266+
// and call cppgc::ShutdownProcess() before process shutdown.
267+
kNoInitializeCppgc = 1 << 13,
264268

265269
// Emulate the behavior of InitializeNodeWithArgs() when passing
266270
// a flags argument to the InitializeOncePerProcess() replacement
@@ -269,7 +273,7 @@ enum Flags : uint32_t {
269273
kNoStdioInitialization | kNoDefaultSignalHandling | kNoInitializeV8 |
270274
kNoInitializeNodeV8Platform | kNoInitOpenSSL |
271275
kNoParseGlobalDebugVariables | kNoAdjustResourceLimits |
272-
kNoUseLargePages | kNoPrintHelpOrVersionOutput,
276+
kNoUseLargePages | kNoPrintHelpOrVersionOutput | kNoInitializeCppgc,
273277
};
274278
} // namespace ProcessInitializationFlags
275279
namespace ProcessFlags = ProcessInitializationFlags; // Legacy alias.
@@ -1486,6 +1490,25 @@ void RegisterSignalHandler(int signal,
14861490
bool reset_handler = false);
14871491
#endif // _WIN32
14881492

1493+
// Configure the layout of the JavaScript object with a cppgc::GarbageCollected
1494+
// instance so that when the JavaScript object is reachable, the garbage
1495+
// collected instance would have its Trace() method invoked per the cppgc
1496+
// contract. To make it work, the process must have called
1497+
// cppgc::InitializeProcess() before, which is usually the case for addons
1498+
// loaded by the stand-alone Node.js executable. Embedders of Node.js can use
1499+
// either need to call it themselves or make sure that
1500+
// ProcessInitializationFlags::kNoInitializeCppgc is *not* set for cppgc to
1501+
// work.
1502+
// If the CppHeap is owned by Node.js, which is usually the case for addon,
1503+
// the object must be created with at least two internal fields available,
1504+
// and the first two internal fields would be configured by Node.js.
1505+
// This may be superseded by a V8 API in the future, see
1506+
// https://bugs.chromium.org/p/v8/issues/detail?id=13960. Until then this
1507+
// serves as a helper for Node.js isolates.
1508+
NODE_EXTERN void SetCppgcReference(v8::Isolate* isolate,
1509+
v8::Local<v8::Object> object,
1510+
void* wrappable);
1511+
14891512
} // namespace node
14901513

14911514
#endif // SRC_NODE_H_

src/node_main_instance.cc

+2
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ NodeMainInstance::~NodeMainInstance() {
6868
return;
6969
}
7070
// This should only be done on a main instance that owns its isolate.
71+
// IsolateData must be freed before UnregisterIsolate() is called.
72+
isolate_data_.reset();
7173
platform_->UnregisterIsolate(isolate_);
7274
isolate_->Dispose();
7375
}

src/node_worker.cc

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "node_snapshot_builder.h"
1212
#include "permission/permission.h"
1313
#include "util-inl.h"
14+
#include "v8-cppgc.h"
1415

1516
#include <memory>
1617
#include <string>

test/addons/cppgc-object/binding.cc

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include <cppgc/allocation.h>
2+
#include <cppgc/garbage-collected.h>
3+
#include <cppgc/heap.h>
4+
#include <node.h>
5+
#include <v8-cppgc.h>
6+
#include <v8.h>
7+
#include <algorithm>
8+
9+
class CppGCed : public cppgc::GarbageCollected<CppGCed> {
10+
public:
11+
static uint16_t states[2];
12+
static constexpr int kDestructCount = 0;
13+
static constexpr int kTraceCount = 1;
14+
15+
static void New(const v8::FunctionCallbackInfo<v8::Value>& args) {
16+
v8::Isolate* isolate = args.GetIsolate();
17+
v8::Local<v8::Object> js_object = args.This();
18+
CppGCed* gc_object = cppgc::MakeGarbageCollected<CppGCed>(
19+
isolate->GetCppHeap()->GetAllocationHandle());
20+
node::SetCppgcReference(isolate, js_object, gc_object);
21+
args.GetReturnValue().Set(js_object);
22+
}
23+
24+
static v8::Local<v8::Function> GetConstructor(
25+
v8::Local<v8::Context> context) {
26+
auto ft = v8::FunctionTemplate::New(context->GetIsolate(), New);
27+
auto ot = ft->InstanceTemplate();
28+
v8::WrapperDescriptor descriptor =
29+
context->GetIsolate()->GetCppHeap()->wrapper_descriptor();
30+
uint16_t required_size = std::max(descriptor.wrappable_instance_index,
31+
descriptor.wrappable_type_index);
32+
ot->SetInternalFieldCount(required_size + 1);
33+
return ft->GetFunction(context).ToLocalChecked();
34+
}
35+
36+
CppGCed() = default;
37+
38+
~CppGCed() { states[kDestructCount]++; }
39+
40+
void Trace(cppgc::Visitor* visitor) const { states[kTraceCount]++; }
41+
};
42+
43+
uint16_t CppGCed::states[] = {0, 0};
44+
45+
void InitModule(v8::Local<v8::Object> exports) {
46+
v8::Isolate* isolate = v8::Isolate::GetCurrent();
47+
auto context = isolate->GetCurrentContext();
48+
49+
auto store = v8::ArrayBuffer::NewBackingStore(
50+
CppGCed::states,
51+
sizeof(uint16_t) * 2,
52+
[](void*, size_t, void*) {},
53+
nullptr);
54+
auto ab = v8::ArrayBuffer::New(isolate, std::move(store));
55+
56+
exports
57+
->Set(context,
58+
v8::String::NewFromUtf8(isolate, "CppGCed").ToLocalChecked(),
59+
CppGCed::GetConstructor(context))
60+
.FromJust();
61+
exports
62+
->Set(context,
63+
v8::String::NewFromUtf8(isolate, "states").ToLocalChecked(),
64+
v8::Uint16Array::New(ab, 0, 2))
65+
.FromJust();
66+
exports
67+
->Set(context,
68+
v8::String::NewFromUtf8(isolate, "kDestructCount").ToLocalChecked(),
69+
v8::Integer::New(isolate, CppGCed::kDestructCount))
70+
.FromJust();
71+
exports
72+
->Set(context,
73+
v8::String::NewFromUtf8(isolate, "kTraceCount").ToLocalChecked(),
74+
v8::Integer::New(isolate, CppGCed::kTraceCount))
75+
.FromJust();
76+
}
77+
78+
NODE_MODULE(NODE_GYP_MODULE_NAME, InitModule)

test/addons/cppgc-object/binding.gyp

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
}
8+
]
9+
}

test/addons/cppgc-object/test.js

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
'use strict';
2+
3+
// Flags: --expose-gc
4+
5+
const common = require('../../common');
6+
7+
// Verify that addons can create GarbageCollected objects and
8+
// have them traced properly.
9+
10+
const assert = require('assert');
11+
const {
12+
CppGCed, states, kDestructCount, kTraceCount,
13+
} = require(`./build/${common.buildType}/binding`);
14+
15+
assert.strictEqual(states[kDestructCount], 0);
16+
assert.strictEqual(states[kTraceCount], 0);
17+
18+
let array = [];
19+
const count = 100;
20+
for (let i = 0; i < count; ++i) {
21+
array.push(new CppGCed());
22+
}
23+
24+
globalThis.gc();
25+
26+
setTimeout(async function() {
27+
// GC should have invoked Trace() on at least some of the CppGCed objects,
28+
// but they should all be alive at this point.
29+
assert.strictEqual(states[kDestructCount], 0);
30+
assert.notStrictEqual(states[kTraceCount], 0);
31+
32+
// Replace the old CppGCed objects with new ones, after GC we should have
33+
// destructed all the old ones and called Trace() on the
34+
// new ones.
35+
for (let i = 0; i < count; ++i) {
36+
array[i] = new CppGCed();
37+
}
38+
await common.gcUntil(
39+
'All old CppGCed are destroyed',
40+
() => states[kDestructCount] === count,
41+
);
42+
// Release all the CppGCed objects, after GC we should have destructed
43+
// all of them.
44+
array = null;
45+
globalThis.gc();
46+
47+
await common.gcUntil(
48+
'All old CppGCed are destroyed',
49+
() => states[kDestructCount] === count * 2,
50+
);
51+
}, 1);

0 commit comments

Comments
 (0)