| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent f2f4ce9 commit 099159c
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -34,6 +34,7 @@ | |||
| 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 | } | |
| 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()); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -62,6 +62,10 @@ | |||
| 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; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -63,6 +63,8 @@ | |||
| 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(); | |
| 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_ | |
| 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 | } | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,6 +11,7 @@ | |||
| 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> | |
| Original file line number | Diff line number | Diff 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) | ||
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff 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); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments