GeezerVison is showing github.com — src,tools: initialize cppgc · nodejs/node@099159c
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: src/env-inl.h +26Lines changed: 26 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 34 | 34 | #include "node_realm-inl.h" | 35 | 35 | #include "util-inl.h" | 36 | 36 | #include "uv.h" | | 37 | +#include "v8-cppgc.h" | 37 | 38 | #include "v8.h" | 38 | 39 |
| 39 | 40 | #include <cstddef> | @@ -61,6 +62,31 @@ inline uv_loop_t* IsolateData::event_loop() const { | 61 | 62 | return event_loop_; | 62 | 63 | } | 63 | 64 |
| | 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 | + | 64 | 90 | inline uint16_t* IsolateData::embedder_id_for_cppgc() const { | 65 | 91 | return &(wrapper_data_->cppgc_id); | 66 | 92 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/env.cc +26Lines changed: 26 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -37,6 +37,8 @@ using errors::TryCatchScope; | 37 | 37 | using v8::Array; | 38 | 38 | using v8::Boolean; | 39 | 39 | using v8::Context; | | 40 | +using v8::CppHeap; | | 41 | +using v8::CppHeapCreateParams; | 40 | 42 | using v8::EmbedderGraph; | 41 | 43 | using v8::EscapableHandleScope; | 42 | 44 | using v8::Function; | @@ -61,6 +63,7 @@ using v8::TracingController; | 61 | 63 | using v8::TryCatch; | 62 | 64 | using v8::Undefined; | 63 | 65 | using v8::Value; | | 66 | +using v8::WrapperDescriptor; | 64 | 67 | using worker::Worker; | 65 | 68 |
| 66 | 69 | int const ContextEmbedderTag::kNodeContextTag = 0x6e6f64; | @@ -538,6 +541,14 @@ IsolateData::IsolateData(Isolate* isolate, | 538 | 541 | // for embedder ID, V8 could accidentally enable cppgc on them. So | 539 | 542 | // safe guard against this. | 540 | 543 | 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()); | 541 | 552 | } | 542 | 553 | // We do not care about overflow since we just want this to be different | 543 | 554 | // from the cppgc id. | @@ -565,6 +576,21 @@ IsolateData::IsolateData(Isolate* isolate, | 565 | 576 | } | 566 | 577 | } | 567 | 578 |
| | 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 | + | 568 | 594 | void IsolateData::MemoryInfo(MemoryTracker* tracker) const { | 569 | 595 | #define V(PropertyName, StringValue) \ | 570 | 596 | tracker->TrackField(#PropertyName, PropertyName()); | |
Collapse file Copy file name to clipboardExpand all lines: src/env.h +10Lines changed: 10 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 62 | 62 | #include <unordered_set> | 63 | 63 | #include <vector> | 64 | 64 |
| | 65 | +namespace v8 { | | 66 | +class CppHeap; | | 67 | +} | | 68 | + | 65 | 69 | namespace node { | 66 | 70 |
| 67 | 71 | namespace shadow_realm { | @@ -136,6 +140,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { | 136 | 140 | MultiIsolatePlatform* platform = nullptr, | 137 | 141 | ArrayBufferAllocator* node_allocator = nullptr, | 138 | 142 | const SnapshotData* snapshot_data = nullptr); | | 143 | + ~IsolateData(); | 139 | 144 |
| 140 | 145 | SET_MEMORY_INFO_NAME(IsolateData) | 141 | 146 | SET_SELF_SIZE(IsolateData) | @@ -148,6 +153,10 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { | 148 | 153 | uint16_t* embedder_id_for_cppgc() const; | 149 | 154 | uint16_t* embedder_id_for_non_cppgc() const; | 150 | 155 |
| | 156 | + static inline void SetCppgcReference(v8::Isolate* isolate, | | 157 | + v8::Local<v8::Object> object, | | 158 | + void* wrappable); | | 159 | + | 151 | 160 | inline uv_loop_t* event_loop() const; | 152 | 161 | inline MultiIsolatePlatform* platform() const; | 153 | 162 | inline const SnapshotData* snapshot_data() const; | @@ -229,6 +238,7 @@ class NODE_EXTERN_PRIVATE IsolateData : public MemoryRetainer { | 229 | 238 | NodeArrayBufferAllocator* const node_allocator_; | 230 | 239 | MultiIsolatePlatform* platform_; | 231 | 240 | const SnapshotData* snapshot_data_; | | 241 | + std::unique_ptr<v8::CppHeap> cpp_heap_; | 232 | 242 | std::shared_ptr<PerIsolateOptions> options_; | 233 | 243 | worker::Worker* worker_context_ = nullptr; | 234 | 244 | bool is_building_snapshot_ = false; | |
Collapse file Copy file name to clipboardExpand all lines: src/node.cc +14Lines changed: 14 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 63 | 63 | #endif // NODE_USE_V8_PLATFORM | 64 | 64 | #include "v8-profiler.h" | 65 | 65 |
| | 66 | +#include "cppgc/platform.h" | | 67 | + | 66 | 68 | #if HAVE_INSPECTOR | 67 | 69 | #include "inspector/worker_inspector.h" // ParentInspectorHandle | 68 | 70 | #endif | @@ -1116,6 +1118,14 @@ InitializeOncePerProcessInternal(const std::vector<std::string>& args, | 1116 | 1118 | V8::Initialize(); | 1117 | 1119 | } | 1118 | 1120 |
| | 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 | + | 1119 | 1129 | performance::performance_v8_start = PERFORMANCE_NOW(); | 1120 | 1130 | per_process::v8_initialized = true; | 1121 | 1131 |
| @@ -1135,6 +1145,10 @@ void TearDownOncePerProcess() { | 1135 | 1145 | ResetSignalHandlers(); | 1136 | 1146 | } | 1137 | 1147 |
| | 1148 | + if (!(flags & ProcessInitializationFlags::kNoInitializeCppgc)) { | | 1149 | + cppgc::ShutdownProcess(); | | 1150 | + } | | 1151 | + | 1138 | 1152 | per_process::v8_initialized = false; | 1139 | 1153 | if (!(flags & ProcessInitializationFlags::kNoInitializeV8)) { | 1140 | 1154 | V8::Dispose(); | |
Collapse file Copy file name to clipboardExpand all lines: src/node.h +24-1Lines changed: 24 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -261,6 +261,10 @@ enum Flags : uint32_t { | 261 | 261 | kNoUseLargePages = 1 << 11, | 262 | 262 | // Skip printing output for --help, --version, --v8-options. | 263 | 263 | 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, | 264 | 268 |
| 265 | 269 | // Emulate the behavior of InitializeNodeWithArgs() when passing | 266 | 270 | // a flags argument to the InitializeOncePerProcess() replacement | @@ -269,7 +273,7 @@ enum Flags : uint32_t { | 269 | 273 | kNoStdioInitialization | kNoDefaultSignalHandling | kNoInitializeV8 | | 270 | 274 | kNoInitializeNodeV8Platform | kNoInitOpenSSL | | 271 | 275 | kNoParseGlobalDebugVariables | kNoAdjustResourceLimits | | 272 | | - kNoUseLargePages | kNoPrintHelpOrVersionOutput, | | 276 | + kNoUseLargePages | kNoPrintHelpOrVersionOutput | kNoInitializeCppgc, | 273 | 277 | }; | 274 | 278 | } // namespace ProcessInitializationFlags | 275 | 279 | namespace ProcessFlags = ProcessInitializationFlags; // Legacy alias. | @@ -1486,6 +1490,25 @@ void RegisterSignalHandler(int signal, | 1486 | 1490 | bool reset_handler = false); | 1487 | 1491 | #endif // _WIN32 | 1488 | 1492 |
| | 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 | + | 1489 | 1512 | } // namespace node | 1490 | 1513 |
| 1491 | 1514 | #endif // SRC_NODE_H_ |
Collapse file Copy file name to clipboardExpand all lines: src/node_main_instance.cc +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -68,6 +68,8 @@ NodeMainInstance::~NodeMainInstance() { | 68 | 68 | return; | 69 | 69 | } | 70 | 70 | // 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(); | 71 | 73 | platform_->UnregisterIsolate(isolate_); | 72 | 74 | isolate_->Dispose(); | 73 | 75 | } | |
Collapse file Copy file name to clipboardExpand all lines: src/node_worker.cc +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 11 | 11 | #include "node_snapshot_builder.h" | 12 | 12 | #include "permission/permission.h" | 13 | 13 | #include "util-inl.h" | | 14 | +#include "v8-cppgc.h" | 14 | 15 |
| 15 | 16 | #include <memory> | 16 | 17 | #include <string> | |
Collapse file Copy file name to clipboard +78Lines changed: 78 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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) |
Collapse file Copy file name to clipboard +9Lines changed: 9 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +{ | | 2 | + 'targets': [ | | 3 | + { | | 4 | + 'target_name': 'binding', | | 5 | + 'sources': [ 'binding.cc' ], | | 6 | + 'includes': ['../common.gypi'], | | 7 | + } | | 8 | + ] | | 9 | +} |
Collapse file Copy file name to clipboard +51Lines changed: 51 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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); |
|