From e747f405a4f89730e805f28bff94fb9ce9a8e695 Mon Sep 17 00:00:00 2001 From: snek Date: Thu, 27 Jun 2024 08:29:46 -0700 Subject: [PATCH] fix: remove use of deprecated apis (#1488) --- Cargo.lock | 7 + Cargo.toml | 1 + examples/cppgc-object.rs | 57 ++-- examples/cppgc.rs | 80 +++--- src/binding.cc | 168 +++++++++--- src/binding.hpp | 15 +- src/cppgc.rs | 499 ++++++++++++++++++++++++++--------- src/handle.rs | 69 +++++ src/isolate.rs | 17 +- src/isolate_create_params.rs | 8 + src/lib.rs | 1 + src/object.rs | 68 +++++ src/snapshot.rs | 9 +- tests/slots.rs | 2 +- tests/test_api.rs | 20 +- tests/test_cppgc.rs | 121 ++++++--- 16 files changed, 842 insertions(+), 300 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ff6f96a..6129be66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1020,6 +1020,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1457,6 +1463,7 @@ dependencies = [ "home", "miniz_oxide", "once_cell", + "paste", "rustversion", "trybuild", "which 6.0.1", diff --git a/Cargo.toml b/Cargo.toml index 16c79455..2c25053d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ use_custom_libcxx = [] [dependencies] bitflags = "2.5" once_cell = "1.19" +paste = "1.0" [build-dependencies] miniz_oxide = "0.7.2" diff --git a/examples/cppgc-object.rs b/examples/cppgc-object.rs index 47ccf856..82839588 100644 --- a/examples/cppgc-object.rs +++ b/examples/cppgc-object.rs @@ -19,9 +19,7 @@ impl Drop for Wrappable { } } -// Set a custom embedder ID for the garbage collector. cppgc will use this ID to -// identify the object that it manages. -const DEFAULT_CPP_GC_EMBEDDER_ID: u16 = 0xde90; +const TAG: u16 = 1; fn main() { let platform = v8::new_default_platform(0, false).make_shared(); @@ -32,18 +30,10 @@ fn main() { v8::cppgc::initalize_process(platform.clone()); { - let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); - - // Create a managed heap. - let heap = v8::cppgc::Heap::create( - platform, - v8::cppgc::HeapCreateParams::new(v8::cppgc::WrapperDescriptor::new( - 0, - 1, - DEFAULT_CPP_GC_EMBEDDER_ID, - )), - ); - isolate.attach_cpp_heap(&heap); + let heap = + v8::cppgc::Heap::create(platform, v8::cppgc::HeapCreateParams::default()); + let isolate = + &mut v8::Isolate::new(v8::CreateParams::default().cpp_heap(heap)); let handle_scope = &mut v8::HandleScope::new(isolate); let context = v8::Context::new(handle_scope); @@ -57,24 +47,31 @@ fn main() { mut rv: v8::ReturnValue| { let id = args.get(0).to_rust_string_lossy(scope); - let templ = v8::ObjectTemplate::new(scope); - templ.set_internal_field_count(2); + fn empty( + _scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, + ) { + } + let templ = v8::FunctionTemplate::new(scope, empty); + let func = templ.get_function(scope).unwrap(); + let obj = func.new_instance(scope, &[]).unwrap(); - let obj = templ.new_instance(scope).unwrap(); + assert!(obj.is_api_wrapper()); - let member = v8::cppgc::make_garbage_collected( - scope.get_cpp_heap().unwrap(), - Box::new(Wrappable { - trace_count: Cell::new(0), - id, - }), - ); + let member = unsafe { + v8::cppgc::make_garbage_collected( + scope.get_cpp_heap().unwrap(), + Wrappable { + trace_count: Cell::new(0), + id, + }, + ) + }; - obj.set_aligned_pointer_in_internal_field( - 0, - &DEFAULT_CPP_GC_EMBEDDER_ID as *const u16 as _, - ); - obj.set_aligned_pointer_in_internal_field(1, member.handle as _); + unsafe { + v8::Object::wrap::(scope, obj, &member); + } rv.set(obj.into()); }, diff --git a/examples/cppgc.rs b/examples/cppgc.rs index abcf5b6e..50ed127e 100644 --- a/examples/cppgc.rs +++ b/examples/cppgc.rs @@ -1,47 +1,43 @@ // Copyright 2019-2021 the Deno authors. All rights reserved. MIT license. // // This sample program shows how to set up a stand-alone cppgc heap. -use std::ops::Deref; // Simple string rope to illustrate allocation and garbage collection below. // The rope keeps the next parts alive via regular managed reference. + struct Rope { part: String, - next: Option>, + next: v8::cppgc::Member, } impl std::fmt::Display for Rope { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.part)?; - if let Some(next) = &self.next { - write!(f, "{}", next.deref())?; + if let Some(next) = self.next.borrow() { + write!(f, "{}", next)?; } Ok(()) } } impl Rope { - pub fn new(part: String, next: Option>) -> Box { - Box::new(Self { part, next }) + pub fn new(part: String, next: v8::cppgc::Member) -> Rope { + Self { part, next } } } impl v8::cppgc::GarbageCollected for Rope { fn trace(&self, visitor: &v8::cppgc::Visitor) { - if let Some(member) = &self.next { - visitor.trace(member); - } + visitor.trace(&self.next); } } impl Drop for Rope { fn drop(&mut self) { - println!("Dropping {}", self.part); + println!("Dropping: {}", self.part); } } -const DEFAULT_CPP_GC_EMBEDDER_ID: u16 = 0xde90; - fn main() { let platform = v8::new_default_platform(0, false).make_shared(); v8::V8::initialize_platform(platform.clone()); @@ -50,36 +46,46 @@ fn main() { { // Create a managed heap. - let heap = v8::cppgc::Heap::create( - platform, - v8::cppgc::HeapCreateParams::new(v8::cppgc::WrapperDescriptor::new( - 0, - 1, - DEFAULT_CPP_GC_EMBEDDER_ID, - )), - ); + let heap = + v8::cppgc::Heap::create(platform, v8::cppgc::HeapCreateParams::default()); // Allocate a string rope on the managed heap. - let rope = v8::cppgc::make_garbage_collected( - &heap, - Rope::new( - String::from("Hello "), - Some(v8::cppgc::make_garbage_collected( - &heap, - Rope::new(String::from("World!"), None), - )), - ), - ); + let rope = unsafe { + v8::cppgc::make_garbage_collected( + &heap, + Rope::new( + String::from("Hello "), + v8::cppgc::make_garbage_collected( + &heap, + Rope::new(String::from("World!"), v8::cppgc::Member::empty()), + ), + ), + ) + }; + + println!("{}", rope.borrow().unwrap()); - println!("{}", unsafe { rope.get() }); // Manually trigger garbage collection. heap.enable_detached_garbage_collections_for_testing(); - heap.collect_garbage_for_testing( - v8::cppgc::EmbedderStackState::MayContainHeapPointers, - ); - heap.collect_garbage_for_testing( - v8::cppgc::EmbedderStackState::NoHeapPointers, - ); + + println!("Collect: MayContainHeapPointers"); + unsafe { + heap.collect_garbage_for_testing( + v8::cppgc::EmbedderStackState::MayContainHeapPointers, + ); + } + + // Should still be live here: + println!("{}", rope.borrow().unwrap()); + + println!("Collect: NoHeapPointers"); + unsafe { + heap.collect_garbage_for_testing( + v8::cppgc::EmbedderStackState::NoHeapPointers, + ); + } + + // Should be dead now. } // Gracefully shutdown the process. diff --git a/src/binding.cc b/src/binding.cc index 82d12529..5b311be2 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -11,6 +11,7 @@ #include "support.h" #include "unicode/locid.h" #include "v8-callbacks.h" +#include "v8/include/cppgc/persistent.h" #include "v8/include/libplatform/libplatform.h" #include "v8/include/v8-cppgc.h" #include "v8/include/v8-fast-api-calls.h" @@ -31,8 +32,6 @@ #include "v8/src/objects/objects.h" #include "v8/src/objects/smi.h" -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - using namespace support; template @@ -405,6 +404,21 @@ void v8__Global__Reset(const v8::Data* data) { global.Reset(); } +void v8__TracedReference__CONSTRUCT( + uninit_t>* buf) { + construct_in_place>(buf); +} + +void v8__TracedReference__Reset(v8::TracedReference* self, + v8::Isolate* isolate, const v8::Data* other) { + self->Reset(isolate, ptr_to_local(other)); +} + +const v8::Data* v8__TracedReference__Get(v8::TracedReference* self, + v8::Isolate* isolate) { + return local_to_ptr(self->Get(isolate)); +} + v8::Isolate* v8__WeakCallbackInfo__GetIsolate( const v8::WeakCallbackInfo* self) { return self->GetIsolate(); @@ -1313,8 +1327,12 @@ void v8__Object__SetAlignedPointerInInternalField(const v8::Object& self, ptr_to_local(&self)->SetAlignedPointerInInternalField(index, value); } +bool v8__Object__IsApiWrapper(const v8::Object& self) { + return ptr_to_local(&self)->IsApiWrapper(); +} + const v8::Value* v8__Object__GetPrototype(const v8::Object& self) { - return local_to_ptr(ptr_to_local(&self)->GetPrototype()); + return local_to_ptr(ptr_to_local(&self)->GetPrototypeV2()); } MaybeBool v8__Object__Set(const v8::Object& self, const v8::Context& context, @@ -1333,7 +1351,7 @@ MaybeBool v8__Object__SetIndex(const v8::Object& self, MaybeBool v8__Object__SetPrototype(const v8::Object& self, const v8::Context& context, const v8::Value& prototype) { - return maybe_to_maybe_bool(ptr_to_local(&self)->SetPrototype( + return maybe_to_maybe_bool(ptr_to_local(&self)->SetPrototypeV2( ptr_to_local(&context), ptr_to_local(&prototype))); } @@ -3729,24 +3747,38 @@ void v8__PropertyDescriptor__set_configurable(v8::PropertyDescriptor* self, extern "C" { -using RustTraceFn = void (*)(void* obj, cppgc::Visitor*); -using RustDestroyFn = void (*)(void* obj); +class RustObj; + +using RustTraceFn = void (*)(const RustObj* obj, cppgc::Visitor*); +using RustDestroyFn = void (*)(const RustObj* obj); class RustObj final : public cppgc::GarbageCollected { public: - explicit RustObj(void* obj, RustTraceFn trace, RustDestroyFn destroy) - : trace_(trace), destroy_(destroy), obj_(obj) {} + explicit RustObj(RustTraceFn trace, RustDestroyFn destroy) + : trace_(trace), destroy_(destroy) {} - ~RustObj() { destroy_(obj_); } + ~RustObj() { destroy_(this); } - void Trace(cppgc::Visitor* visitor) const { trace_(obj_, visitor); } + void Trace(cppgc::Visitor* visitor) const { trace_(this, visitor); } private: RustTraceFn trace_; RustDestroyFn destroy_; - void* obj_; }; +RustObj* v8__Object__Unwrap(v8::Isolate* isolate, const v8::Object& wrapper, + v8::CppHeapPointerTag tag) { + v8::CppHeapPointerTagRange tag_range(tag, tag); + return static_cast( + v8::Object::Unwrap(isolate, ptr_to_local(&wrapper), tag_range)); +} + +void v8__Object__Wrap(v8::Isolate* isolate, const v8::Object& wrapper, + RustObj* value, v8::CppHeapPointerTag tag) { + v8::Object::Wrap(isolate, ptr_to_local(&wrapper), static_cast(value), + tag); +} + void cppgc__initialize_process(v8::Platform* platform) { cppgc::InitializeProcess(platform->GetPageAllocator()); } @@ -3754,31 +3786,15 @@ void cppgc__initialize_process(v8::Platform* platform) { void cppgc__shutdown_process() { cppgc::ShutdownProcess(); } v8::CppHeap* cppgc__heap__create(v8::Platform* platform, - int wrappable_type_index, - int wrappable_instance_index, - uint16_t embedder_id) { - std::unique_ptr heap = v8::CppHeap::Create( - platform, - v8::CppHeapCreateParams{ - {}, - v8::WrapperDescriptor(wrappable_type_index, wrappable_instance_index, - embedder_id), - }); + cppgc::Heap::MarkingType marking_support, + cppgc::Heap::SweepingType sweeping_support) { + v8::CppHeapCreateParams params{{}}; + params.marking_support = marking_support; + params.sweeping_support = sweeping_support; + std::unique_ptr heap = v8::CppHeap::Create(platform, params); return heap.release(); } -void v8__Isolate__AttachCppHeap(v8::Isolate* isolate, v8::CppHeap* cpp_heap) { -// The AttachCppHeap method is deprecated but the alternative of passing -// heap to the Isolate CreateParams is broken. -// -// TODO(@littledivy): Remove this when the above CL is merged. -// https://chromium-review.googlesource.com/c/chromium/src/+/4992764 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - isolate->AttachCppHeap(cpp_heap); -#pragma clang diagnostic pop -} - v8::CppHeap* v8__Isolate__GetCppHeap(v8::Isolate* isolate) { return isolate->GetCppHeap(); } @@ -3795,15 +3811,95 @@ void cppgc__heap__collect_garbage_for_testing( heap->CollectGarbageForTesting(stack_state); } -RustObj* cppgc__make_garbage_collectable(v8::CppHeap* heap, void* obj, +RustObj* cppgc__make_garbage_collectable(v8::CppHeap* heap, size_t size, RustTraceFn trace, RustDestroyFn destroy) { - return cppgc::MakeGarbageCollected(heap->GetAllocationHandle(), obj, + return cppgc::MakeGarbageCollected(heap->GetAllocationHandle(), + cppgc::AdditionalBytes(size), trace, destroy); } -void cppgc__visitor__trace(cppgc::Visitor* visitor, RustObj* member) { +void cppgc__Visitor__Trace__Member(cppgc::Visitor* visitor, + cppgc::Member* member) { visitor->Trace(*member); } +void cppgc__Visitor__Trace__WeakMember(cppgc::Visitor* visitor, + cppgc::WeakMember* member) { + visitor->Trace(*member); +} + +void cppgc__Visitor__Trace__TracedReference( + cppgc::Visitor* visitor, v8::TracedReference* ref) { + visitor->Trace(*ref); +} + +void cppgc__Member__CONSTRUCT(uninit_t>* buf, + RustObj* other) { + construct_in_place>(buf, other); +} + +void cppgc__Member__DESTRUCT(cppgc::Member* self) { + self->~BasicMember(); +} + +RustObj* cppgc__Member__Get(cppgc::Member* member) { + return member->Get(); +} + +void cppgc__Member__Assign(cppgc::Member* member, RustObj* other) { + member->operator=(other); +} + +void cppgc__WeakMember__CONSTRUCT(uninit_t>* buf, + RustObj* other) { + construct_in_place>(buf, other); +} + +void cppgc__WeakMember__DESTRUCT(cppgc::WeakMember* self) { + self->~BasicMember(); +} + +RustObj* cppgc__WeakMember__Get(cppgc::WeakMember* member) { + return member->Get(); +} + +void cppgc__WeakMember__Assign(cppgc::WeakMember* member, + RustObj* other) { + member->operator=(other); +} + +cppgc::Persistent* cppgc__Persistent__CONSTRUCT() { + return new cppgc::Persistent(nullptr); +} + +void cppgc__Persistent__DESTRUCT(cppgc::Persistent* self) { + delete self; +} + +void cppgc__Persistent__Assign(cppgc::Persistent* self, RustObj* ptr) { + self->operator=(ptr); +} + +RustObj* cppgc__Persistent__Get(cppgc::Persistent* self) { + return self->Get(); +} + +cppgc::WeakPersistent* cppgc__WeakPersistent__CONSTRUCT() { + return new cppgc::WeakPersistent(nullptr); +} + +void cppgc__WeakPersistent__DESTRUCT(cppgc::WeakPersistent* self) { + delete self; +} + +void cppgc__WeakPersistent__Assign(cppgc::WeakPersistent* self, + RustObj* ptr) { + self->operator=(ptr); +} + +RustObj* cppgc__WeakPersistent__Get(cppgc::WeakPersistent* self) { + return self->Get(); +} + } // extern "C" diff --git a/src/binding.hpp b/src/binding.hpp index 5fee9723..31e16b09 100644 --- a/src/binding.hpp +++ b/src/binding.hpp @@ -1,3 +1,4 @@ +#include #include /** @@ -5,8 +6,16 @@ * and made available in `crate::binding` in rust. */ -// TODO: In the immediate term, cppgc definitions will go here. -// In the future we should migrate over the rest of our SIZE definitions, -// and eventually entire structs and functions. +namespace { + +class RustObj; + +} static size_t RUST_v8__ScriptOrigin_SIZE = sizeof(v8::ScriptOrigin); + +static size_t RUST_cppgc__Member_SIZE = sizeof(cppgc::Member); +static size_t RUST_cppgc__WeakMember_SIZE = sizeof(cppgc::WeakMember); + +static size_t RUST_v8__TracedReference_SIZE = + sizeof(v8::TracedReference); diff --git a/src/cppgc.rs b/src/cppgc.rs index 8793d482..84915481 100644 --- a/src/cppgc.rs +++ b/src/cppgc.rs @@ -5,6 +5,9 @@ use crate::support::int; use crate::support::Opaque; use crate::support::SharedRef; use crate::support::UniqueRef; +use crate::Data; +use crate::TracedReference; +use std::marker::PhantomData; extern "C" { fn cppgc__initialize_process(platform: *mut Platform); @@ -12,17 +15,16 @@ extern "C" { fn cppgc__heap__create( platform: *mut Platform, - wrappable_type_index: int, - wrappable_instance_index: int, - embedder_id_for_garbage_collected: u16, + marking_support: MarkingType, + sweeping_support: SweepingType, ) -> *mut Heap; fn cppgc__heap__DELETE(heap: *mut Heap); fn cppgc__make_garbage_collectable( heap: *mut Heap, - obj: *mut (), + size: usize, trace: TraceFn, destroy: DestroyFn, - ) -> *mut InnerMember; + ) -> *mut RustObj; fn cppgc__heap__enable_detached_garbage_collections_for_testing( heap: *mut Heap, @@ -32,7 +34,75 @@ extern "C" { stack_state: EmbedderStackState, ); - fn cppgc__visitor__trace(visitor: *const Visitor, member: *const InnerMember); + fn cppgc__Visitor__Trace__Member( + visitor: *const Visitor, + member: *const MemberInner, + ); + fn cppgc__Visitor__Trace__WeakMember( + visitor: *const Visitor, + member: *const WeakMemberInner, + ); + fn cppgc__Visitor__Trace__TracedReference( + visitor: *const Visitor, + reference: *const TracedReference, + ); + + fn cppgc__Member__CONSTRUCT(member: *mut MemberInner, obj: *mut RustObj); + fn cppgc__Member__DESTRUCT(member: *mut MemberInner); + fn cppgc__Member__Get(member: *const MemberInner) -> *mut RustObj; + fn cppgc__Member__Assign(member: *mut MemberInner, other: *mut RustObj); + + fn cppgc__WeakMember__CONSTRUCT( + member: *mut WeakMemberInner, + obj: *mut RustObj, + ); + fn cppgc__WeakMember__DESTRUCT(member: *mut WeakMemberInner); + fn cppgc__WeakMember__Get(member: *const WeakMemberInner) -> *mut RustObj; + fn cppgc__WeakMember__Assign( + member: *mut WeakMemberInner, + other: *mut RustObj, + ); + + fn cppgc__Persistent__CONSTRUCT() -> *mut PersistentInner; + fn cppgc__Persistent__DESTRUCT(this: *mut PersistentInner); + fn cppgc__Persistent__Assign(this: *mut PersistentInner, ptr: *mut RustObj); + fn cppgc__Persistent__Get(this: *const PersistentInner) -> *mut RustObj; + + fn cppgc__WeakPersistent__CONSTRUCT() -> *mut WeakPersistentInner; + fn cppgc__WeakPersistent__DESTRUCT(this: *mut WeakPersistentInner); + fn cppgc__WeakPersistent__Assign( + this: *mut WeakPersistentInner, + ptr: *mut RustObj, + ); + fn cppgc__WeakPersistent__Get( + this: *const WeakPersistentInner, + ) -> *mut RustObj; +} + +type TraceFn = unsafe extern "C" fn(*const RustObj, *mut Visitor); +type DestroyFn = unsafe extern "C" fn(*const RustObj); + +#[doc(hidden)] +#[repr(C)] +pub struct RustObj { + trace: TraceFn, + destroy: DestroyFn, +} + +fn object_offset_for_rust_obj() -> usize { + #[repr(C)] + struct Calc { + header: RustObj, + data: T, + } + + std::mem::offset_of!(Calc, data) +} + +fn get_object_from_rust_obj( + rust_obj: *const RustObj, +) -> *mut T { + unsafe { rust_obj.byte_add(object_offset_for_rust_obj::()) as *mut T } } /// Process-global initialization of the garbage collector. Must be called before @@ -73,8 +143,37 @@ pub unsafe fn shutdown_process() { pub struct Visitor(Opaque); impl Visitor { - pub fn trace(&self, member: &Member) { - unsafe { cppgc__visitor__trace(self, member.handle) } + #[inline(always)] + pub fn trace(&self, member: &impl Traced) { + member.trace(self); + } +} + +#[doc(hidden)] +pub trait Traced { + fn trace(&self, visitor: &Visitor); +} + +impl Traced for Member { + fn trace(&self, visitor: &Visitor) { + unsafe { cppgc__Visitor__Trace__Member(visitor, &self.inner) } + } +} + +impl Traced for WeakMember { + fn trace(&self, visitor: &Visitor) { + unsafe { cppgc__Visitor__Trace__WeakMember(visitor, &self.inner) } + } +} + +impl Traced for TracedReference { + fn trace(&self, visitor: &Visitor) { + unsafe { + cppgc__Visitor__Trace__TracedReference( + visitor, + self as *const TracedReference as *const TracedReference, + ) + } } } @@ -110,54 +209,22 @@ pub enum SweepingType { pub type InternalFieldIndex = int; -/// Describes how V8 wrapper objects maintain references to garbage-collected C++ objects. -pub struct WrapperDescriptor { - /// Index of the wrappable type. - pub wrappable_type_index: InternalFieldIndex, - /// Index of the wrappable instance. - pub wrappable_instance_index: InternalFieldIndex, - /// Embedder id identifying instances of garbage-collected objects. It is expected that - /// the first field of the wrappable type is a uint16_t holding the id. Only references - /// to instances of wrappables types with an id of embedder_id_for_garbage_collected will - /// be considered by Heap. - pub embedder_id_for_garbage_collected: u16, -} - -impl WrapperDescriptor { - pub fn new( - wrappable_type_index: InternalFieldIndex, - wrappable_instance_index: InternalFieldIndex, - embedder_id_for_garbage_collected: u16, - ) -> Self { - Self { - wrappable_type_index, - wrappable_instance_index, - embedder_id_for_garbage_collected, - } - } -} - pub struct HeapCreateParams { - wrapper_descriptor: WrapperDescriptor, /// Specifies which kind of marking are supported by the heap. pub marking_support: MarkingType, /// Specifies which kind of sweeping are supported by the heap. pub sweeping_support: SweepingType, } -impl HeapCreateParams { - pub fn new(wrapper_descriptor: WrapperDescriptor) -> Self { +impl Default for HeapCreateParams { + fn default() -> Self { Self { - wrapper_descriptor, marking_support: MarkingType::IncrementalAndConcurrent, sweeping_support: SweepingType::IncrementalAndConcurrent, } } } -type TraceFn = unsafe extern "C" fn(*mut (), *mut Visitor); -type DestroyFn = unsafe extern "C" fn(*mut ()); - /// A heap for allocating managed C++ objects. /// /// Similar to v8::Isolate, the heap may only be accessed from one thread at a @@ -168,7 +235,7 @@ pub struct Heap(Opaque); impl Drop for Heap { fn drop(&mut self) { - unsafe { cppgc__heap__DELETE(self as *mut Heap) } + unsafe { cppgc__heap__DELETE(self) } } } @@ -177,23 +244,19 @@ impl Heap { platform: SharedRef, params: HeapCreateParams, ) -> UniqueRef { - let WrapperDescriptor { - wrappable_type_index, - wrappable_instance_index, - embedder_id_for_garbage_collected, - } = params.wrapper_descriptor; - unsafe { UniqueRef::from_raw(cppgc__heap__create( &*platform as *const Platform as *mut _, - wrappable_type_index, - wrappable_instance_index, - embedder_id_for_garbage_collected, + params.marking_support, + params.sweeping_support, )) } } - pub fn collect_garbage_for_testing(&self, stack_state: EmbedderStackState) { + pub unsafe fn collect_garbage_for_testing( + &self, + stack_state: EmbedderStackState, + ) { unsafe { cppgc__heap__collect_garbage_for_testing( self as *const Heap as *mut _, @@ -216,99 +279,275 @@ pub trait GarbageCollected { fn trace(&self, _visitor: &Visitor) {} } -#[repr(C)] -pub struct InnerMember { - inner: [usize; 2], - ptr: *mut (), -} - -impl InnerMember { - pub unsafe fn get(&self) -> &T { - unsafe { self.ptr.cast::().as_ref().unwrap() } - } - - pub unsafe fn get_mut(&mut self) -> &mut T { - unsafe { self.ptr.cast::().as_mut().unwrap() } - } -} - -/// Members are used to contain strong pointers to other garbage -/// collected objects. All members fields on garbage collected objects -/// must be trace in the `trace` method. -#[repr(transparent)] -pub struct Member { - pub handle: *mut InnerMember, - _phantom: std::marker::PhantomData, -} - -impl Member { - /// Returns a raw pointer to the object. - /// - /// # Safety - /// - /// There are no guarantees that the object is alive and not garbage collected. - pub unsafe fn get(&self) -> &T { - unsafe { (*self.handle).get() } - } -} - -impl std::ops::Deref for Member { - type Target = T; - - fn deref(&self) -> &Self::Target { - unsafe { self.get() } - } -} - /// Constructs an instance of T, which is a garbage collected type. /// /// The object will be allocated on the heap and managed by cppgc. During /// marking, the object will be traced by calling the `trace` method on it. /// /// During sweeping, the destructor will be called and the memory will be -/// freed using `Box::from_raw`. -pub fn make_garbage_collected( - heap: &Heap, - obj: Box, -) -> Member { - unsafe extern "C" fn destroy(obj: *mut ()) { - let _ = Box::from_raw(obj as *mut T); - } - - unsafe { make_garbage_collected_raw(heap, Box::into_raw(obj), destroy::) } -} - +/// freed. +/// /// # Safety /// -/// By calling this function, you are giving up ownership of `T` to the -/// garbage collector. -/// -/// `obj` must be a pointer to a valid instance of T allocated on the heap. -/// -/// `drop_fn` must be a function that drops the instance of T. This function -/// will be called when the object is garbage collected. -pub unsafe fn make_garbage_collected_raw( +/// The caller must ensure that the returned pointer is always stored on +/// the stack, or moved into one of the Persistent types. +pub unsafe fn make_garbage_collected( heap: &Heap, - obj: *mut T, - destroy: DestroyFn, + obj: T, ) -> Member { unsafe extern "C" fn trace( - obj: *mut (), + obj: *const RustObj, visitor: *mut Visitor, ) { - let obj = unsafe { &*(obj as *const T) }; + let obj = unsafe { &*get_object_from_rust_obj::(obj) }; obj.trace(unsafe { &*visitor }); } - let handle = cppgc__make_garbage_collectable( - heap as *const Heap as *mut _, - obj as _, - trace::, - destroy, - ); + unsafe extern "C" fn destroy(obj: *const RustObj) { + let obj = get_object_from_rust_obj::(obj); + std::ptr::drop_in_place(obj); + } - Member { - handle, - _phantom: std::marker::PhantomData, + let additional_bytes = (object_offset_for_rust_obj::() + - std::mem::size_of::()) + + std::mem::size_of::(); + + let handle = unsafe { + cppgc__make_garbage_collectable( + heap as *const Heap as *mut _, + additional_bytes, + trace::, + destroy::, + ) + }; + + unsafe { + get_object_from_rust_obj::(handle).write(obj); + } + + Member::new(handle) +} + +#[doc(hidden)] +pub trait GetRustObj { + fn get_rust_obj(&self) -> *mut RustObj; +} + +macro_rules! member { + ($( # $attr:tt )* $name:ident) => { + paste::paste! { + #[repr(transparent)] + struct [< $name Inner >]([u8; crate::binding:: [< RUST_cppgc__ $name _SIZE >]]); + + impl [< $name Inner >] { + fn new(ptr: *mut RustObj) -> Self { + let mut this = std::mem::MaybeUninit::uninit(); + unsafe { + [< cppgc__ $name __CONSTRUCT >](this.as_mut_ptr(), ptr); + this.assume_init() + } + } + + #[inline(always)] + fn get(&self) -> *mut RustObj { + // Member may be a compressed pointer, so just read it from C++ + unsafe { [< cppgc__ $name __Get >](self) } + } + + #[inline(always)] + fn assign(&mut self, ptr: *mut RustObj) { + // Assignment has write barriers in the GC, so call into C++ + unsafe { + [< cppgc__ $name __Assign >](self, ptr); + } + } + } + + impl Drop for [< $name Inner >] { + fn drop(&mut self) { + unsafe { + [< cppgc__ $name __DESTRUCT >](self); + } + } + } + + $( # $attr )* + #[repr(transparent)] + pub struct $name { + inner: [< $name Inner >], + _phantom: PhantomData, + } + + impl $name { + pub(crate) fn new(obj: *mut RustObj) -> Self { + Self { + inner: [< $name Inner >]::new(obj), + _phantom: PhantomData, + } + } + + #[doc = "Create a new empty "] + #[doc = stringify!($name)] + #[doc = " which may be set later."] + pub fn empty() -> Self { + Self::new(std::ptr::null_mut()) + } + + #[doc = "Set the object pointed to by this "] + #[doc = stringify!($name)] + #[doc = "."] + pub fn set(&mut self, other: &impl GetRustObj) { + let ptr = other.get_rust_obj(); + self.inner.assign(ptr); + } + + #[doc = "Borrow the object pointed to by this "] + #[doc = stringify!($name)] + #[doc = "."] + pub fn borrow(&self) -> Option<&T> { + let ptr = self.inner.get(); + if ptr.is_null() { + None + } else { + // SAFETY: Either this is a strong reference and the pointer is always valid + // or this is a weak reference and the ptr will be null if it was collected. + Some(unsafe { &*get_object_from_rust_obj(ptr) }) + } + } + } + + impl GetRustObj for $name { + fn get_rust_obj(&self) -> *mut RustObj { + self.inner.get() + } + } + + impl std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct(stringify!($name)).finish() + } + } + } } } + +member! { + /// Members are used in classes to contain strong pointers to other garbage + /// collected objects. All Member fields of a class must be traced in the class' + /// trace method. + Member +} + +member! { + /// WeakMember is similar to Member in that it is used to point to other garbage + /// collected objects. However instead of creating a strong pointer to the + /// object, the WeakMember creates a weak pointer, which does not keep the + /// pointee alive. Hence if all pointers to to a heap allocated object are weak + /// the object will be garbage collected. At the time of GC the weak pointers + /// will automatically be set to null. + WeakMember +} + +macro_rules! persistent { + ($( # $attr:tt )* $name:ident) => { + paste::paste! { + // PersistentBase is extremely particular about move and copy semantics, + // so we allocate it on the heap and only interact with it via calls to C++. + #[repr(C)] + struct [< $name Inner >](Opaque); + + $( # $attr )* + pub struct $name { + inner: *mut [< $name Inner >], + _phantom: PhantomData, + } + + impl $name { + #[doc = "Create a new empty "] + #[doc = stringify!($name)] + #[doc = " which may be set later."] + pub fn empty() -> Self { + let this = unsafe { [< cppgc__ $name __CONSTRUCT >]() }; + Self { + inner: this, + _phantom: PhantomData, + } + } + + #[doc = "Set the object pointed to by this "] + #[doc = stringify!($name)] + #[doc = "."] + pub fn set(&mut self, other: &impl GetRustObj) { + let ptr = other.get_rust_obj(); + self.assign(ptr); + } + + #[doc = "Borrow the object pointed to by this "] + #[doc = stringify!($name)] + #[doc = "."] + pub fn borrow(&self) -> Option<&T> { + let ptr = self.get(); + if ptr.is_null() { + None + } else { + // SAFETY: Either this is a strong reference and the pointer is always valid + // or this is a weak reference and the ptr will be null if it was collected. + Some(unsafe { &*get_object_from_rust_obj(ptr) }) + } + } + + #[inline(always)] + fn assign(&mut self, ptr: *mut RustObj) { + unsafe { + [< cppgc__ $name __Assign >](self.inner, ptr); + } + } + + #[inline(always)] + fn get(&self) -> *mut RustObj { + unsafe { + [< cppgc__ $name __Get >](self.inner) + } + } + + } + + impl Drop for $name { + fn drop(&mut self) { + unsafe { + [< cppgc__ $name __DESTRUCT >](self.inner); + } + } + } + + impl std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + fmt.debug_struct(stringify!($name)).finish() + } + } + + impl GetRustObj for $name { + fn get_rust_obj(&self) -> *mut RustObj { + self.get() + } + } + } + }; +} + +persistent! { + /// Persistent is a way to create a strong pointer from an off-heap object to + /// another on-heap object. As long as the Persistent handle is alive the GC will + /// keep the object pointed to alive. The Persistent handle is always a GC root + /// from the point of view of the GC. Persistent must be constructed and + /// destructed in the same thread. + Persistent +} + +persistent! { + /// WeakPersistent is a way to create a weak pointer from an off-heap object to + /// an on-heap object. The pointer is automatically cleared when the pointee gets + /// collected. WeakPersistent must be constructed and destructed in the same + /// thread. + WeakPersistent +} diff --git a/src/handle.rs b/src/handle.rs index 3bd70c3c..4bbe51c0 100644 --- a/src/handle.rs +++ b/src/handle.rs @@ -35,6 +35,17 @@ extern "C" { this: *const WeakCallbackInfo, callback: extern "C" fn(*const WeakCallbackInfo), ); + + fn v8__TracedReference__CONSTRUCT(this: *mut TracedReference); + fn v8__TracedReference__Reset( + this: *mut TracedReference, + isolate: *mut Isolate, + data: *mut Data, + ); + fn v8__TracedReference__Get( + this: *const TracedReference, + isolate: *mut Isolate, + ) -> *const Data; } /// An object reference managed by the v8 garbage collector. @@ -978,3 +989,61 @@ impl FinalizerMap { self.map.drain().map(|(_, finalizer)| finalizer) } } + +/// A traced handle without destructor that clears the handle. The embedder needs +/// to ensure that the handle is not accessed once the V8 object has been +/// reclaimed. For more details see BasicTracedReference. +#[repr(C)] +pub struct TracedReference { + data: [u8; crate::binding::RUST_v8__TracedReference_SIZE], + _phantom: PhantomData, +} + +impl TracedReference { + /// An empty TracedReference without storage cell. + pub fn empty() -> Self { + let mut this = std::mem::MaybeUninit::uninit(); + unsafe { + v8__TracedReference__CONSTRUCT(this.as_mut_ptr() as _); + this.assume_init() + } + } + + /// Construct a TracedReference from a Local. + /// + /// A new storage cell is created pointing to the same object. + pub fn new(scope: &mut HandleScope<()>, data: Local) -> Self { + let mut this = Self::empty(); + this.reset(scope, Some(data)); + this + } + + pub fn get<'s>( + &self, + scope: &mut HandleScope<'s, ()>, + ) -> Option> { + unsafe { + scope.cast_local(|sd| { + v8__TracedReference__Get( + self as *const Self as *const TracedReference, + sd.get_isolate_ptr(), + ) as *const T + }) + } + } + + /// Always resets the reference. Creates a new reference from `other` if it is + /// non-empty. + pub fn reset(&mut self, scope: &mut HandleScope<()>, data: Option>) { + unsafe { + v8__TracedReference__Reset( + self as *mut Self as *mut TracedReference, + scope.get_isolate_ptr(), + data + .map(|h| h.as_non_null().as_ptr()) + .unwrap_or(std::ptr::null_mut()) + .cast(), + ); + } + } +} diff --git a/src/isolate.rs b/src/isolate.rs index e4aed657..6762fee2 100644 --- a/src/isolate.rs +++ b/src/isolate.rs @@ -460,7 +460,6 @@ extern "C" { change_in_bytes: i64, ) -> i64; fn v8__Isolate__GetCppHeap(isolate: *mut Isolate) -> *mut Heap; - fn v8__Isolate__AttachCppHeap(isolate: *mut Isolate, heap: *mut Heap); fn v8__Isolate__SetPrepareStackTraceCallback( isolate: *mut Isolate, callback: PrepareStackTraceCallback, @@ -637,18 +636,21 @@ impl Isolate { #[allow(clippy::new_ret_no_self)] pub fn snapshot_creator( external_references: Option<&'static ExternalReferences>, + params: Option, ) -> OwnedIsolate { - SnapshotCreator::new(external_references) + SnapshotCreator::new(external_references, params) } #[allow(clippy::new_ret_no_self)] pub fn snapshot_creator_from_existing_snapshot( existing_snapshot_blob: impl Allocated<[u8]>, external_references: Option<&'static ExternalReferences>, + params: Option, ) -> OwnedIsolate { SnapshotCreator::from_existing_snapshot( existing_snapshot_blob, external_references, + params, ) } @@ -1184,17 +1186,6 @@ impl Isolate { } } - /// Attaches a managed C++ heap as an extension to the JavaScript heap. - /// - /// The embedder maintains ownership of the CppHeap. At most one C++ heap - /// can be attached to V8. - #[inline(always)] - pub fn attach_cpp_heap(&mut self, heap: &Heap) { - unsafe { - v8__Isolate__AttachCppHeap(self, heap as *const Heap as *mut _); - } - } - pub fn get_cpp_heap(&mut self) -> Option<&Heap> { unsafe { v8__Isolate__GetCppHeap(self).as_ref() } } diff --git a/src/isolate_create_params.rs b/src/isolate_create_params.rs index 0eef400a..d0fad119 100644 --- a/src/isolate_create_params.rs +++ b/src/isolate_create_params.rs @@ -8,6 +8,7 @@ use crate::support::Allocated; use crate::support::Allocation; use crate::support::Opaque; use crate::support::SharedPtr; +use crate::support::UniqueRef; use std::any::Any; use std::convert::TryFrom; @@ -159,6 +160,13 @@ impl CreateParams { self } + /// A CppHeap used to construct the Isolate. V8 takes ownership of the + /// CppHeap passed this way. + pub fn cpp_heap(mut self, heap: UniqueRef) -> Self { + self.raw.cpp_heap = heap.into_raw(); + self + } + pub(crate) fn finalize(mut self) -> (raw::CreateParams, Box) { if self.raw.array_buffer_allocator_shared.is_null() { self = self.array_buffer_allocator(array_buffer::new_default_allocator()); diff --git a/src/lib.rs b/src/lib.rs index feb40d2e..390a1917 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,6 +98,7 @@ pub use get_property_names_args_builder::*; pub use handle::Global; pub use handle::Handle; pub use handle::Local; +pub use handle::TracedReference; pub use handle::Weak; pub use isolate::GarbageCollectionType; pub use isolate::HeapStatistics; diff --git a/src/object.rs b/src/object.rs index 6bcbd2ed..5366bfb1 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,3 +1,7 @@ +use crate::cppgc::GarbageCollected; +use crate::cppgc::GetRustObj; +use crate::cppgc::Member; +use crate::cppgc::RustObj; use crate::isolate::Isolate; use crate::support::int; use crate::support::MapFnTo; @@ -209,6 +213,18 @@ extern "C" { key: *const Name, out: *mut Maybe, ); + fn v8__Object__Wrap( + isolate: *const Isolate, + wrapper: *const Object, + value: *const RustObj, + tag: u16, + ); + fn v8__Object__Unwrap( + isolate: *const Isolate, + wrapper: *const Object, + tag: u16, + ) -> *mut RustObj; + fn v8__Object__IsApiWrapper(this: *const Object) -> bool; fn v8__Array__New(isolate: *mut Isolate, length: int) -> *const Array; fn v8__Array__New_with_elements( @@ -263,6 +279,8 @@ extern "C" { fn v8__Set__As__Array(this: *const Set) -> *const Array; } +const LAST_TAG: u16 = 0x7fff; + impl Object { /// Creates an empty object. #[inline(always)] @@ -680,6 +698,56 @@ impl Object { unsafe { v8__Object__SetAlignedPointerInInternalField(self, index, value) } } + /// Wraps a JS wrapper with a C++ instance. + /// + /// # Safety + /// + /// The `TAG` must be unique to the caller within the heap. + #[allow(clippy::not_unsafe_ptr_arg_deref)] + #[inline(always)] + pub unsafe fn wrap( + scope: &mut HandleScope, + wrapper: Local, + value: &impl GetRustObj, + ) { + // TODO: use a const assert once const expressions are stable + assert!(TAG < LAST_TAG); + let ptr = value.get_rust_obj(); + unsafe { v8__Object__Wrap(scope.get_isolate_ptr(), &*wrapper, ptr, TAG) } + } + + /// Unwraps a JS wrapper object. + /// + /// # Safety + /// + /// The caller must ensure that the returned pointer is always stored on + /// the stack, or moved into one of the Persistent types. + #[inline(always)] + pub unsafe fn unwrap( + scope: &mut HandleScope, + wrapper: Local, + ) -> Member { + // TODO: use a const assert once const expressions are stable + assert!(TAG < LAST_TAG); + let ptr = + unsafe { v8__Object__Unwrap(scope.get_isolate_ptr(), &*wrapper, TAG) }; + Member::new(ptr) + } + + /// Returns true if this object can be generally used to wrap object objects. + /// This means that the object either follows the convention of using embedder + /// fields to denote type/instance pointers or is using the Wrap()/Unwrap() + /// APIs for the same purpose. Returns false otherwise. + /// + /// Note that there may be other objects that use embedder fields but are not + /// used as API wrapper objects. E.g., v8::Promise may in certain configuration + /// use embedder fields but promises are not generally supported as API + /// wrappers. The method will return false in those cases. + #[inline(always)] + pub fn is_api_wrapper(&self) -> bool { + unsafe { v8__Object__IsApiWrapper(self) } + } + /// Sets the integrity level of the object. #[inline(always)] pub fn set_integrity_level( diff --git a/src/snapshot.rs b/src/snapshot.rs index 567b6c28..8ae7368e 100644 --- a/src/snapshot.rs +++ b/src/snapshot.rs @@ -102,8 +102,9 @@ impl SnapshotCreator { #[allow(clippy::new_ret_no_self)] pub(crate) fn new( external_references: Option<&'static ExternalReferences>, + params: Option, ) -> OwnedIsolate { - Self::new_impl(external_references, None::<&[u8]>) + Self::new_impl(external_references, None::<&[u8]>, params) } /// Create an isolate, and set it up for serialization. @@ -113,8 +114,9 @@ impl SnapshotCreator { pub(crate) fn from_existing_snapshot( existing_snapshot_blob: impl Allocated<[u8]>, external_references: Option<&'static ExternalReferences>, + params: Option, ) -> OwnedIsolate { - Self::new_impl(external_references, Some(existing_snapshot_blob)) + Self::new_impl(external_references, Some(existing_snapshot_blob), params) } /// Create and enter an isolate, and set it up for serialization. @@ -124,10 +126,11 @@ impl SnapshotCreator { fn new_impl( external_references: Option<&'static ExternalReferences>, existing_snapshot_blob: Option>, + params: Option, ) -> OwnedIsolate { let mut snapshot_creator: MaybeUninit = MaybeUninit::uninit(); - let mut params = crate::CreateParams::default(); + let mut params = params.unwrap_or_default(); if let Some(external_refs) = external_references { params = params.external_references(&**external_refs); } diff --git a/tests/slots.rs b/tests/slots.rs index e491fbe5..b3a72249 100644 --- a/tests/slots.rs +++ b/tests/slots.rs @@ -326,7 +326,7 @@ fn dropped_context_slots_on_kept_context() { fn clear_all_context_slots() { setup(); - let mut snapshot_creator = v8::Isolate::snapshot_creator(None); + let mut snapshot_creator = v8::Isolate::snapshot_creator(None, None); { let scope = &mut v8::HandleScope::new(&mut snapshot_creator); diff --git a/tests/test_api.rs b/tests/test_api.rs index 31992598..d905fbba 100644 --- a/tests/test_api.rs +++ b/tests/test_api.rs @@ -5395,7 +5395,7 @@ fn snapshot_creator() { let context_data_index; let context_data_index_2; let startup_data = { - let mut snapshot_creator = v8::Isolate::snapshot_creator(None); + let mut snapshot_creator = v8::Isolate::snapshot_creator(None, None); { let scope = &mut v8::HandleScope::new(&mut snapshot_creator); let context = v8::Context::new(scope); @@ -5411,7 +5411,11 @@ fn snapshot_creator() { let startup_data = { let mut snapshot_creator = - v8::Isolate::snapshot_creator_from_existing_snapshot(startup_data, None); + v8::Isolate::snapshot_creator_from_existing_snapshot( + startup_data, + None, + None, + ); { // Check that the SnapshotCreator isolate has been set up correctly. let _ = snapshot_creator.thread_safe_handle(); @@ -5482,7 +5486,7 @@ fn snapshot_creator() { fn snapshot_creator_multiple_contexts() { let _setup_guard = setup::sequential_test(); let startup_data = { - let mut snapshot_creator = v8::Isolate::snapshot_creator(None); + let mut snapshot_creator = v8::Isolate::snapshot_creator(None, None); { let mut scope = v8::HandleScope::new(&mut snapshot_creator); let context = v8::Context::new(&mut scope); @@ -5517,7 +5521,11 @@ fn snapshot_creator_multiple_contexts() { let startup_data = { let mut snapshot_creator = - v8::Isolate::snapshot_creator_from_existing_snapshot(startup_data, None); + v8::Isolate::snapshot_creator_from_existing_snapshot( + startup_data, + None, + None, + ); { let scope = &mut v8::HandleScope::new(&mut snapshot_creator); let context = v8::Context::new(scope); @@ -5655,7 +5663,7 @@ fn external_references() { // First we create the snapshot, there is a single global variable 'a' set to // the value 3. let startup_data = { - let mut snapshot_creator = v8::Isolate::snapshot_creator(Some(refs)); + let mut snapshot_creator = v8::Isolate::snapshot_creator(Some(refs), None); { let scope = &mut v8::HandleScope::new(&mut snapshot_creator); let context = v8::Context::new(scope); @@ -7477,7 +7485,7 @@ fn module_snapshot() { let _setup_guard = setup::sequential_test(); let startup_data = { - let mut snapshot_creator = v8::Isolate::snapshot_creator(None); + let mut snapshot_creator = v8::Isolate::snapshot_creator(None, None); { let scope = &mut v8::HandleScope::new(&mut snapshot_creator); let context = v8::Context::new(scope); diff --git a/tests/test_cppgc.rs b/tests/test_cppgc.rs index 2ff7442c..dd44b8d0 100644 --- a/tests/test_cppgc.rs +++ b/tests/test_cppgc.rs @@ -26,7 +26,7 @@ impl Drop for CppGCGuard { } } -const DEFAULT_CPP_GC_EMBEDDER_ID: u16 = 0xde90; +const TAG: u16 = 1; #[test] fn cppgc_object_wrap() { @@ -35,11 +35,14 @@ fn cppgc_object_wrap() { static TRACE_COUNT: AtomicUsize = AtomicUsize::new(0); static DROP_COUNT: AtomicUsize = AtomicUsize::new(0); - struct Wrap; + struct Wrap { + value: v8::TracedReference, + } impl GarbageCollected for Wrap { - fn trace(&self, _: &Visitor) { + fn trace(&self, visitor: &Visitor) { TRACE_COUNT.fetch_add(1, Ordering::SeqCst); + visitor.trace(&self.value); } } @@ -49,62 +52,84 @@ fn cppgc_object_wrap() { } } - fn op_make_wrap( + fn op_wrap( scope: &mut v8::HandleScope, - _: v8::FunctionCallbackArguments, + args: v8::FunctionCallbackArguments, mut rv: v8::ReturnValue, ) { - let templ = v8::ObjectTemplate::new(scope); - templ.set_internal_field_count(2); + fn empty( + _scope: &mut v8::HandleScope, + _args: v8::FunctionCallbackArguments, + _rv: v8::ReturnValue, + ) { + } + let templ = v8::FunctionTemplate::new(scope, empty); + let func = templ.get_function(scope).unwrap(); + let obj = func.new_instance(scope, &[]).unwrap(); + assert!(obj.is_api_wrapper()); - let obj = templ.new_instance(scope).unwrap(); + let wrap = Wrap { + value: v8::TracedReference::new(scope, args.get(0)), + }; + let member = unsafe { + v8::cppgc::make_garbage_collected(scope.get_cpp_heap().unwrap(), wrap) + }; - let member = v8::cppgc::make_garbage_collected( - scope.get_cpp_heap().unwrap(), - Box::new(Wrap), - ); - - obj.set_aligned_pointer_in_internal_field( - 0, - &DEFAULT_CPP_GC_EMBEDDER_ID as *const u16 as _, - ); - obj.set_aligned_pointer_in_internal_field(1, member.handle as _); + unsafe { + v8::Object::wrap::(scope, obj, &member); + } rv.set(obj.into()); } + fn op_unwrap( + scope: &mut v8::HandleScope, + args: v8::FunctionCallbackArguments, + mut rv: v8::ReturnValue, + ) { + let obj = args.get(0).try_into().unwrap(); + let member = unsafe { v8::Object::unwrap::(scope, obj) }; + rv.set(member.borrow().unwrap().value.get(scope).unwrap()); + } + { - let isolate = &mut v8::Isolate::new(v8::CreateParams::default()); // Create a managed heap. let heap = v8::cppgc::Heap::create( guard.platform.clone(), - v8::cppgc::HeapCreateParams::new(v8::cppgc::WrapperDescriptor::new( - 0, - 1, - DEFAULT_CPP_GC_EMBEDDER_ID, - )), + v8::cppgc::HeapCreateParams::default(), ); - isolate.attach_cpp_heap(&heap); + let isolate = + &mut v8::Isolate::new(v8::CreateParams::default().cpp_heap(heap)); let handle_scope = &mut v8::HandleScope::new(isolate); let context = v8::Context::new(handle_scope); let scope = &mut v8::ContextScope::new(handle_scope, context); let global = context.global(scope); { - let func = v8::Function::new(scope, op_make_wrap).unwrap(); - let name = v8::String::new(scope, "make_wrap").unwrap(); + let func = v8::Function::new(scope, op_wrap).unwrap(); + let name = v8::String::new(scope, "wrap").unwrap(); + global.set(scope, name.into(), func.into()).unwrap(); + } + { + let func = v8::Function::new(scope, op_unwrap).unwrap(); + let name = v8::String::new(scope, "unwrap").unwrap(); global.set(scope, name.into(), func.into()).unwrap(); } - let source = v8::String::new( + execute_script( scope, r#" - make_wrap(); // Inaccessible after scope. - globalThis.wrap = make_wrap(); // Accessible after scope. + { + const x = {}; + const y = unwrap(wrap(x)); // collected + if (x !== y) { + throw new Error('mismatch'); + } + } + + globalThis.wrapped = wrap(wrap({})); // not collected "#, - ) - .unwrap(); - execute_script(scope, source); + ); assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0); @@ -113,24 +138,38 @@ fn cppgc_object_wrap() { assert!(TRACE_COUNT.load(Ordering::SeqCst) > 0); assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); + + execute_script( + scope, + r#" + globalThis.wrapped = undefined; + "#, + ); + + scope + .request_garbage_collection_for_testing(v8::GarbageCollectionType::Full); + + assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 3); } } fn execute_script( context_scope: &mut v8::ContextScope, - script: v8::Local, + source: &str, ) { let scope = &mut v8::HandleScope::new(context_scope); - let try_catch = &mut v8::TryCatch::new(scope); + let scope = &mut v8::TryCatch::new(scope); - let script = v8::Script::compile(try_catch, script, None) - .expect("failed to compile script"); + let source = v8::String::new(scope, source).unwrap(); - if script.run(try_catch).is_none() { - let exception_string = try_catch + let script = + v8::Script::compile(scope, source, None).expect("failed to compile script"); + + if script.run(scope).is_none() { + let exception_string = scope .stack_trace() - .or_else(|| try_catch.exception()) - .map(|value| value.to_rust_string_lossy(try_catch)) + .or_else(|| scope.exception()) + .map(|value| value.to_rust_string_lossy(scope)) .unwrap_or_else(|| "no stack trace".into()); panic!("{}", exception_string);