mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-12-25 16:49:29 -05:00
Add cppgc
bindings (#1336)
https://v8.dev/blog/high-performance-cpp-gc Oilpan in Deno design doc: https://www.notion.so/denolandinc/Oilpan-cppgc-in-Deno-e194f4268e9f4135ba97610ff7d3a949?pvs=4 Oilpan can be used to implement GC'able resources in Deno Closes https://github.com/denoland/rusty_v8/issues/933
This commit is contained in:
parent
fe574ecc4d
commit
7072da4199
7 changed files with 743 additions and 0 deletions
129
examples/cppgc-object.rs
Normal file
129
examples/cppgc-object.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
|
||||
use std::cell::Cell;
|
||||
|
||||
struct Wrappable {
|
||||
id: String,
|
||||
trace_count: Cell<u16>,
|
||||
}
|
||||
|
||||
impl v8::cppgc::GarbageCollected for Wrappable {
|
||||
fn trace(&self, _visitor: &v8::cppgc::Visitor) {
|
||||
println!("Wrappable::trace() {}", self.id);
|
||||
self.trace_count.set(self.trace_count.get() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Wrappable {
|
||||
fn drop(&mut self) {
|
||||
println!("Wrappable::drop() {}", self.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
fn main() {
|
||||
let platform = v8::new_default_platform(0, false).make_shared();
|
||||
v8::V8::set_flags_from_string("--no_freeze_flags_after_init --expose-gc");
|
||||
v8::V8::initialize_platform(platform.clone());
|
||||
v8::V8::initialize();
|
||||
|
||||
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 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,
|
||||
|scope: &mut v8::HandleScope,
|
||||
args: v8::FunctionCallbackArguments,
|
||||
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);
|
||||
|
||||
let obj = templ.new_instance(scope).unwrap();
|
||||
|
||||
let member = v8::cppgc::make_garbage_collected(
|
||||
scope.get_cpp_heap(),
|
||||
Box::new(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 _);
|
||||
|
||||
rv.set(obj.into());
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let name = v8::String::new(scope, "make_wrap").unwrap();
|
||||
global.set(scope, name.into(), func.into()).unwrap();
|
||||
}
|
||||
|
||||
let source = v8::String::new(
|
||||
scope,
|
||||
r#"
|
||||
make_wrap('gc me pls'); // Inaccessible after scope.
|
||||
globalThis.wrap = make_wrap('dont gc me'); // Accessible after scope.
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
execute_script(scope, source);
|
||||
|
||||
scope
|
||||
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
|
||||
}
|
||||
|
||||
// Gracefully shutdown the process.
|
||||
unsafe {
|
||||
v8::cppgc::shutdown_process();
|
||||
v8::V8::dispose();
|
||||
}
|
||||
v8::V8::dispose_platform();
|
||||
}
|
||||
|
||||
fn execute_script(
|
||||
context_scope: &mut v8::ContextScope<v8::HandleScope>,
|
||||
script: v8::Local<v8::String>,
|
||||
) {
|
||||
let scope = &mut v8::HandleScope::new(context_scope);
|
||||
let try_catch = &mut v8::TryCatch::new(scope);
|
||||
|
||||
let script = v8::Script::compile(try_catch, script, None)
|
||||
.expect("failed to compile script");
|
||||
|
||||
if script.run(try_catch).is_none() {
|
||||
let exception_string = try_catch
|
||||
.stack_trace()
|
||||
.or_else(|| try_catch.exception())
|
||||
.map(|value| value.to_rust_string_lossy(try_catch))
|
||||
.unwrap_or_else(|| "no stack trace".into());
|
||||
|
||||
panic!("{}", exception_string);
|
||||
}
|
||||
}
|
91
examples/cppgc.rs
Normal file
91
examples/cppgc.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
// 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<v8::cppgc::Member<Rope>>,
|
||||
}
|
||||
|
||||
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())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
pub fn new(part: String, next: Option<v8::cppgc::Member<Rope>>) -> Box<Rope> {
|
||||
Box::new(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Rope {
|
||||
fn drop(&mut self) {
|
||||
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());
|
||||
v8::V8::initialize();
|
||||
v8::cppgc::initalize_process(platform.clone());
|
||||
|
||||
{
|
||||
// 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,
|
||||
)),
|
||||
);
|
||||
|
||||
// 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),
|
||||
)),
|
||||
),
|
||||
);
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// Gracefully shutdown the process.
|
||||
unsafe {
|
||||
v8::cppgc::shutdown_process();
|
||||
v8::V8::dispose();
|
||||
}
|
||||
v8::V8::dispose_platform();
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
#include "unicode/locid.h"
|
||||
#include "v8-callbacks.h"
|
||||
#include "v8/include/libplatform/libplatform.h"
|
||||
#include "v8/include/v8-cppgc.h"
|
||||
#include "v8/include/v8-fast-api-calls.h"
|
||||
#include "v8/include/v8-inspector.h"
|
||||
#include "v8/include/v8-internal.h"
|
||||
|
@ -29,6 +30,8 @@
|
|||
#include "v8/src/objects/objects.h"
|
||||
#include "v8/src/objects/smi.h"
|
||||
|
||||
#include "cppgc/platform.h"
|
||||
|
||||
using namespace support;
|
||||
|
||||
template <typename T>
|
||||
|
@ -3582,3 +3585,73 @@ void v8__PropertyDescriptor__set_configurable(v8::PropertyDescriptor* self,
|
|||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
// cppgc
|
||||
|
||||
extern "C" {
|
||||
|
||||
using RustTraceFn = void (*)(void* obj, cppgc::Visitor*);
|
||||
using RustDestroyFn = void (*)(void* obj);
|
||||
|
||||
class RustObj final: public cppgc::GarbageCollected<RustObj> {
|
||||
public:
|
||||
explicit RustObj(void* obj, RustTraceFn trace, RustDestroyFn destroy): trace_(trace), destroy_(destroy), obj_(obj) {}
|
||||
|
||||
~RustObj() {
|
||||
destroy_(obj_);
|
||||
}
|
||||
|
||||
void Trace(cppgc::Visitor* visitor) const {
|
||||
trace_(obj_, visitor);
|
||||
}
|
||||
|
||||
private:
|
||||
RustTraceFn trace_;
|
||||
RustDestroyFn destroy_;
|
||||
void* obj_;
|
||||
};
|
||||
|
||||
void cppgc__initialize_process(v8::Platform* platform) {
|
||||
cppgc::InitializeProcess(platform->GetPageAllocator());
|
||||
}
|
||||
|
||||
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<v8::CppHeap> heap = v8::CppHeap::Create(platform, v8::CppHeapCreateParams {
|
||||
{},
|
||||
v8::WrapperDescriptor(wrappable_type_index, wrappable_instance_index, embedder_id),
|
||||
});
|
||||
return heap.release();
|
||||
}
|
||||
|
||||
void v8__Isolate__AttachCppHeap(v8::Isolate* isolate, v8::CppHeap* cpp_heap) {
|
||||
isolate->AttachCppHeap(cpp_heap);
|
||||
}
|
||||
|
||||
v8::CppHeap* v8__Isolate__GetCppHeap(v8::Isolate* isolate) {
|
||||
return isolate->GetCppHeap();
|
||||
}
|
||||
|
||||
void cppgc__heap__DELETE(v8::CppHeap* self) { delete self; }
|
||||
|
||||
void cppgc__heap__enable_detached_garbage_collections_for_testing(v8::CppHeap* heap) {
|
||||
heap->EnableDetachedGarbageCollectionsForTesting();
|
||||
}
|
||||
|
||||
void cppgc__heap__collect_garbage_for_testing(v8::CppHeap* heap, cppgc::EmbedderStackState stack_state) {
|
||||
heap->CollectGarbageForTesting(stack_state);
|
||||
}
|
||||
|
||||
RustObj* cppgc__make_garbage_collectable(v8::CppHeap* heap, void* obj, RustTraceFn trace, RustDestroyFn destroy) {
|
||||
return cppgc::MakeGarbageCollected<RustObj>(heap->GetAllocationHandle(), obj, trace, destroy);
|
||||
}
|
||||
|
||||
void cppgc__visitor__trace(cppgc::Visitor* visitor, RustObj* member) {
|
||||
visitor->Trace(*member);
|
||||
}
|
||||
|
||||
} // extern "C"
|
294
src/cppgc.rs
Normal file
294
src/cppgc.rs
Normal file
|
@ -0,0 +1,294 @@
|
|||
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license
|
||||
|
||||
use crate::platform::Platform;
|
||||
use crate::support::int;
|
||||
use crate::support::Opaque;
|
||||
use crate::support::SharedRef;
|
||||
use crate::support::UniqueRef;
|
||||
|
||||
extern "C" {
|
||||
fn cppgc__initialize_process(platform: *mut Platform);
|
||||
fn cppgc__shutdown_process();
|
||||
|
||||
fn cppgc__heap__create(
|
||||
platform: *mut Platform,
|
||||
wrappable_type_index: int,
|
||||
wrappable_instance_index: int,
|
||||
embedder_id_for_garbage_collected: u16,
|
||||
) -> *mut Heap;
|
||||
fn cppgc__heap__DELETE(heap: *mut Heap);
|
||||
fn cppgc__make_garbage_collectable(
|
||||
heap: *mut Heap,
|
||||
obj: *mut (),
|
||||
trace: TraceFn,
|
||||
destroy: DestroyFn,
|
||||
) -> *mut ();
|
||||
|
||||
fn cppgc__heap__enable_detached_garbage_collections_for_testing(
|
||||
heap: *mut Heap,
|
||||
);
|
||||
fn cppgc__heap__collect_garbage_for_testing(
|
||||
heap: *mut Heap,
|
||||
stack_state: EmbedderStackState,
|
||||
);
|
||||
|
||||
fn cppgc__visitor__trace(visitor: *const Visitor, member: *const ());
|
||||
}
|
||||
|
||||
/// Process-global initialization of the garbage collector. Must be called before
|
||||
/// creating a Heap.
|
||||
///
|
||||
/// Can be called multiple times when paired with `ShutdownProcess()`.
|
||||
pub fn initalize_process(platform: SharedRef<Platform>) {
|
||||
unsafe {
|
||||
cppgc__initialize_process(&*platform as *const Platform as *mut _);
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Must be called after destroying the last used heap. Some process-global
|
||||
/// metadata may not be returned and reused upon a subsequent
|
||||
/// `initalize_process()` call.
|
||||
pub unsafe fn shutdown_process() {
|
||||
cppgc__shutdown_process();
|
||||
}
|
||||
|
||||
/// Visitor passed to trace methods. All managed pointers must have called the
|
||||
/// Visitor's trace method on them.
|
||||
///
|
||||
/// ```
|
||||
/// use v8::cppgc::{Member, Visitor, GarbageCollected};
|
||||
///
|
||||
/// struct Foo { foo: Member<Foo> }
|
||||
///
|
||||
/// impl GarbageCollected for Foo {
|
||||
/// fn trace(&self, visitor: &Visitor) {
|
||||
/// visitor.trace(&self.foo);
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct Visitor(Opaque);
|
||||
|
||||
impl Visitor {
|
||||
pub fn trace<T: GarbageCollected>(&self, member: &Member<T>) {
|
||||
unsafe { cppgc__visitor__trace(self, member.handle) }
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub enum EmbedderStackState {
|
||||
/// Stack may contain interesting heap pointers.
|
||||
MayContainHeapPointers,
|
||||
/// Stack does not contain any interesting heap pointers.
|
||||
NoHeapPointers,
|
||||
}
|
||||
|
||||
/// Specifies supported marking types.
|
||||
#[repr(u8)]
|
||||
pub enum MarkingType {
|
||||
/// Atomic stop-the-world marking. This option does not require any write barriers but is the most intrusive in terms of jank.
|
||||
Atomic,
|
||||
/// Incremental marking interleaves marking with the rest of the application workload on the same thread.
|
||||
Incremental,
|
||||
/// Incremental and concurrent marking.
|
||||
IncrementalAndConcurrent,
|
||||
}
|
||||
|
||||
/// Specifies supported sweeping types.
|
||||
#[repr(u8)]
|
||||
pub enum SweepingType {
|
||||
/// Atomic stop-the-world sweeping. All of sweeping is performed at once.
|
||||
Atomic,
|
||||
/// Incremental sweeping interleaves sweeping with the rest of the application workload on the same thread.
|
||||
Incremental,
|
||||
/// Incremental and concurrent sweeping. Sweeping is split and interleaved with the rest of the application.
|
||||
IncrementalAndConcurrent,
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
/// time.
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct Heap(Opaque);
|
||||
|
||||
impl Drop for Heap {
|
||||
fn drop(&mut self) {
|
||||
unsafe { cppgc__heap__DELETE(self as *mut Heap) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Heap {
|
||||
pub fn create(
|
||||
platform: SharedRef<Platform>,
|
||||
params: HeapCreateParams,
|
||||
) -> UniqueRef<Heap> {
|
||||
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,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_garbage_for_testing(&self, stack_state: EmbedderStackState) {
|
||||
unsafe {
|
||||
cppgc__heap__collect_garbage_for_testing(
|
||||
self as *const Heap as *mut _,
|
||||
stack_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enable_detached_garbage_collections_for_testing(&self) {
|
||||
unsafe {
|
||||
cppgc__heap__enable_detached_garbage_collections_for_testing(
|
||||
self as *const Heap as *mut _,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Base trait for managed objects.
|
||||
pub trait GarbageCollected {
|
||||
fn trace(&self, _visitor: &Visitor) {}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub struct Member<T: GarbageCollected> {
|
||||
pub handle: *mut (),
|
||||
ptr: *mut T,
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> Member<T> {
|
||||
/// 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.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> std::ops::Deref for Member<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
unsafe { &*self.ptr }
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<T: GarbageCollected>(
|
||||
heap: &Heap,
|
||||
obj: Box<T>,
|
||||
) -> Member<T> {
|
||||
unsafe extern "C" fn destroy<T>(obj: *mut ()) {
|
||||
let _ = Box::from_raw(obj as *mut T);
|
||||
}
|
||||
|
||||
unsafe { make_garbage_collected_raw(heap, Box::into_raw(obj), destroy::<T>) }
|
||||
}
|
||||
|
||||
/// # 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<T: GarbageCollected>(
|
||||
heap: &Heap,
|
||||
obj: *mut T,
|
||||
destroy: DestroyFn,
|
||||
) -> Member<T> {
|
||||
unsafe extern "C" fn trace<T: GarbageCollected>(
|
||||
obj: *mut (),
|
||||
visitor: *mut Visitor,
|
||||
) {
|
||||
let obj = unsafe { &*(obj as *const T) };
|
||||
obj.trace(unsafe { &*visitor });
|
||||
}
|
||||
|
||||
let handle = cppgc__make_garbage_collectable(
|
||||
heap as *const Heap as *mut _,
|
||||
obj as _,
|
||||
trace::<T>,
|
||||
destroy,
|
||||
);
|
||||
|
||||
Member { handle, ptr: obj }
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
|
||||
use crate::cppgc::Heap;
|
||||
use crate::function::FunctionCallbackInfo;
|
||||
use crate::gc::GCCallbackFlags;
|
||||
use crate::gc::GCType;
|
||||
|
@ -420,6 +421,8 @@ extern "C" {
|
|||
isolate: *mut Isolate,
|
||||
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,
|
||||
|
@ -1084,6 +1087,21 @@ 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) -> &Heap {
|
||||
unsafe { &*v8__Isolate__GetCppHeap(self) }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn set_oom_error_handler(&mut self, callback: OomErrorCallback) {
|
||||
unsafe { v8__Isolate__SetOOMErrorHandler(self, callback) };
|
||||
|
|
|
@ -31,6 +31,7 @@ mod array_buffer;
|
|||
mod array_buffer_view;
|
||||
mod bigint;
|
||||
mod context;
|
||||
pub mod cppgc;
|
||||
mod data;
|
||||
mod date;
|
||||
mod exception;
|
||||
|
|
137
tests/test_cppgc.rs
Normal file
137
tests/test_cppgc.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use v8::cppgc::{GarbageCollected, Visitor};
|
||||
|
||||
struct CppGCGuard {
|
||||
pub platform: v8::SharedRef<v8::Platform>,
|
||||
}
|
||||
|
||||
fn initalize_test() -> CppGCGuard {
|
||||
v8::V8::set_flags_from_string("--no_freeze_flags_after_init --expose-gc");
|
||||
let platform = v8::new_unprotected_default_platform(0, false).make_shared();
|
||||
v8::V8::initialize_platform(platform.clone());
|
||||
v8::V8::initialize();
|
||||
v8::cppgc::initalize_process(platform.clone());
|
||||
|
||||
CppGCGuard { platform }
|
||||
}
|
||||
|
||||
impl Drop for CppGCGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
v8::cppgc::shutdown_process();
|
||||
v8::V8::dispose();
|
||||
}
|
||||
v8::V8::dispose_platform();
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_CPP_GC_EMBEDDER_ID: u16 = 0xde90;
|
||||
|
||||
#[test]
|
||||
fn cppgc_object_wrap() {
|
||||
let guard = initalize_test();
|
||||
|
||||
static TRACE_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
struct Wrap;
|
||||
|
||||
impl GarbageCollected for Wrap {
|
||||
fn trace(&self, _: &Visitor) {
|
||||
TRACE_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Wrap {
|
||||
fn drop(&mut self) {
|
||||
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
fn op_make_wrap(
|
||||
scope: &mut v8::HandleScope,
|
||||
_: v8::FunctionCallbackArguments,
|
||||
mut rv: v8::ReturnValue,
|
||||
) {
|
||||
let templ = v8::ObjectTemplate::new(scope);
|
||||
templ.set_internal_field_count(2);
|
||||
|
||||
let obj = templ.new_instance(scope).unwrap();
|
||||
|
||||
let member =
|
||||
v8::cppgc::make_garbage_collected(scope.get_cpp_heap(), 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 _);
|
||||
|
||||
rv.set(obj.into());
|
||||
}
|
||||
|
||||
{
|
||||
let isolate = &mut v8::Isolate::new(Default::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,
|
||||
)),
|
||||
);
|
||||
|
||||
isolate.attach_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();
|
||||
global.set(scope, name.into(), func.into()).unwrap();
|
||||
}
|
||||
|
||||
let source = v8::String::new(
|
||||
scope,
|
||||
r#"
|
||||
make_wrap(); // Inaccessible after scope.
|
||||
globalThis.wrap = make_wrap(); // Accessible after scope.
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
execute_script(scope, source);
|
||||
|
||||
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 0);
|
||||
|
||||
scope
|
||||
.request_garbage_collection_for_testing(v8::GarbageCollectionType::Full);
|
||||
|
||||
assert!(TRACE_COUNT.load(Ordering::SeqCst) > 0);
|
||||
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1);
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_script(
|
||||
context_scope: &mut v8::ContextScope<v8::HandleScope>,
|
||||
script: v8::Local<v8::String>,
|
||||
) {
|
||||
let scope = &mut v8::HandleScope::new(context_scope);
|
||||
let try_catch = &mut v8::TryCatch::new(scope);
|
||||
|
||||
let script = v8::Script::compile(try_catch, script, None)
|
||||
.expect("failed to compile script");
|
||||
|
||||
if script.run(try_catch).is_none() {
|
||||
let exception_string = try_catch
|
||||
.stack_trace()
|
||||
.or_else(|| try_catch.exception())
|
||||
.map(|value| value.to_rust_string_lossy(try_catch))
|
||||
.unwrap_or_else(|| "no stack trace".into());
|
||||
|
||||
panic!("{}", exception_string);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue