mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-11-24 15:19:31 -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 "unicode/locid.h"
|
||||||
#include "v8-callbacks.h"
|
#include "v8-callbacks.h"
|
||||||
#include "v8/include/libplatform/libplatform.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-fast-api-calls.h"
|
||||||
#include "v8/include/v8-inspector.h"
|
#include "v8/include/v8-inspector.h"
|
||||||
#include "v8/include/v8-internal.h"
|
#include "v8/include/v8-internal.h"
|
||||||
|
@ -29,6 +30,8 @@
|
||||||
#include "v8/src/objects/objects.h"
|
#include "v8/src/objects/objects.h"
|
||||||
#include "v8/src/objects/smi.h"
|
#include "v8/src/objects/smi.h"
|
||||||
|
|
||||||
|
#include "cppgc/platform.h"
|
||||||
|
|
||||||
using namespace support;
|
using namespace support;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
@ -3582,3 +3585,73 @@ void v8__PropertyDescriptor__set_configurable(v8::PropertyDescriptor* self,
|
||||||
}
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // 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.
|
// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
use crate::cppgc::Heap;
|
||||||
use crate::function::FunctionCallbackInfo;
|
use crate::function::FunctionCallbackInfo;
|
||||||
use crate::gc::GCCallbackFlags;
|
use crate::gc::GCCallbackFlags;
|
||||||
use crate::gc::GCType;
|
use crate::gc::GCType;
|
||||||
|
@ -420,6 +421,8 @@ extern "C" {
|
||||||
isolate: *mut Isolate,
|
isolate: *mut Isolate,
|
||||||
change_in_bytes: i64,
|
change_in_bytes: i64,
|
||||||
) -> 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(
|
fn v8__Isolate__SetPrepareStackTraceCallback(
|
||||||
isolate: *mut Isolate,
|
isolate: *mut Isolate,
|
||||||
callback: PrepareStackTraceCallback,
|
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)]
|
#[inline(always)]
|
||||||
pub fn set_oom_error_handler(&mut self, callback: OomErrorCallback) {
|
pub fn set_oom_error_handler(&mut self, callback: OomErrorCallback) {
|
||||||
unsafe { v8__Isolate__SetOOMErrorHandler(self, callback) };
|
unsafe { v8__Isolate__SetOOMErrorHandler(self, callback) };
|
||||||
|
|
|
@ -31,6 +31,7 @@ mod array_buffer;
|
||||||
mod array_buffer_view;
|
mod array_buffer_view;
|
||||||
mod bigint;
|
mod bigint;
|
||||||
mod context;
|
mod context;
|
||||||
|
pub mod cppgc;
|
||||||
mod data;
|
mod data;
|
||||||
mod date;
|
mod date;
|
||||||
mod exception;
|
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