mirror of
https://github.com/denoland/rusty_v8.git
synced 2024-11-24 15:19:31 -05:00
fix: remove use of deprecated apis (#1488)
This commit is contained in:
parent
0b440db772
commit
e747f405a4
16 changed files with 842 additions and 300 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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::<TAG, Wrappable>(scope, obj, &member);
|
||||
}
|
||||
|
||||
rv.set(obj.into());
|
||||
},
|
||||
|
|
|
@ -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<v8::cppgc::Member<Rope>>,
|
||||
next: 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())?;
|
||||
if let Some(next) = self.next.borrow() {
|
||||
write!(f, "{}", next)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Rope {
|
||||
pub fn new(part: String, next: Option<v8::cppgc::Member<Rope>>) -> Box<Rope> {
|
||||
Box::new(Self { part, next })
|
||||
pub fn new(part: String, next: v8::cppgc::Member<Rope>) -> 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.
|
||||
|
|
168
src/binding.cc
168
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 <typename T>
|
||||
|
@ -405,6 +404,21 @@ void v8__Global__Reset(const v8::Data* data) {
|
|||
global.Reset();
|
||||
}
|
||||
|
||||
void v8__TracedReference__CONSTRUCT(
|
||||
uninit_t<v8::TracedReference<v8::Data>>* buf) {
|
||||
construct_in_place<v8::TracedReference<v8::Data>>(buf);
|
||||
}
|
||||
|
||||
void v8__TracedReference__Reset(v8::TracedReference<v8::Data>* self,
|
||||
v8::Isolate* isolate, const v8::Data* other) {
|
||||
self->Reset(isolate, ptr_to_local(other));
|
||||
}
|
||||
|
||||
const v8::Data* v8__TracedReference__Get(v8::TracedReference<v8::Data>* self,
|
||||
v8::Isolate* isolate) {
|
||||
return local_to_ptr(self->Get(isolate));
|
||||
}
|
||||
|
||||
v8::Isolate* v8__WeakCallbackInfo__GetIsolate(
|
||||
const v8::WeakCallbackInfo<void>* 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<RustObj> {
|
||||
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<RustObj*>(
|
||||
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<void*>(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<v8::CppHeap> 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<v8::CppHeap> 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<RustObj>(heap->GetAllocationHandle(), obj,
|
||||
return cppgc::MakeGarbageCollected<RustObj>(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<RustObj>* member) {
|
||||
visitor->Trace(*member);
|
||||
}
|
||||
|
||||
void cppgc__Visitor__Trace__WeakMember(cppgc::Visitor* visitor,
|
||||
cppgc::WeakMember<RustObj>* member) {
|
||||
visitor->Trace(*member);
|
||||
}
|
||||
|
||||
void cppgc__Visitor__Trace__TracedReference(
|
||||
cppgc::Visitor* visitor, v8::TracedReference<v8::Data>* ref) {
|
||||
visitor->Trace(*ref);
|
||||
}
|
||||
|
||||
void cppgc__Member__CONSTRUCT(uninit_t<cppgc::Member<RustObj>>* buf,
|
||||
RustObj* other) {
|
||||
construct_in_place<cppgc::Member<RustObj>>(buf, other);
|
||||
}
|
||||
|
||||
void cppgc__Member__DESTRUCT(cppgc::Member<RustObj>* self) {
|
||||
self->~BasicMember();
|
||||
}
|
||||
|
||||
RustObj* cppgc__Member__Get(cppgc::Member<RustObj>* member) {
|
||||
return member->Get();
|
||||
}
|
||||
|
||||
void cppgc__Member__Assign(cppgc::Member<RustObj>* member, RustObj* other) {
|
||||
member->operator=(other);
|
||||
}
|
||||
|
||||
void cppgc__WeakMember__CONSTRUCT(uninit_t<cppgc::WeakMember<RustObj>>* buf,
|
||||
RustObj* other) {
|
||||
construct_in_place<cppgc::WeakMember<RustObj>>(buf, other);
|
||||
}
|
||||
|
||||
void cppgc__WeakMember__DESTRUCT(cppgc::WeakMember<RustObj>* self) {
|
||||
self->~BasicMember();
|
||||
}
|
||||
|
||||
RustObj* cppgc__WeakMember__Get(cppgc::WeakMember<RustObj>* member) {
|
||||
return member->Get();
|
||||
}
|
||||
|
||||
void cppgc__WeakMember__Assign(cppgc::WeakMember<RustObj>* member,
|
||||
RustObj* other) {
|
||||
member->operator=(other);
|
||||
}
|
||||
|
||||
cppgc::Persistent<RustObj>* cppgc__Persistent__CONSTRUCT() {
|
||||
return new cppgc::Persistent<RustObj>(nullptr);
|
||||
}
|
||||
|
||||
void cppgc__Persistent__DESTRUCT(cppgc::Persistent<RustObj>* self) {
|
||||
delete self;
|
||||
}
|
||||
|
||||
void cppgc__Persistent__Assign(cppgc::Persistent<RustObj>* self, RustObj* ptr) {
|
||||
self->operator=(ptr);
|
||||
}
|
||||
|
||||
RustObj* cppgc__Persistent__Get(cppgc::Persistent<RustObj>* self) {
|
||||
return self->Get();
|
||||
}
|
||||
|
||||
cppgc::WeakPersistent<RustObj>* cppgc__WeakPersistent__CONSTRUCT() {
|
||||
return new cppgc::WeakPersistent<RustObj>(nullptr);
|
||||
}
|
||||
|
||||
void cppgc__WeakPersistent__DESTRUCT(cppgc::WeakPersistent<RustObj>* self) {
|
||||
delete self;
|
||||
}
|
||||
|
||||
void cppgc__WeakPersistent__Assign(cppgc::WeakPersistent<RustObj>* self,
|
||||
RustObj* ptr) {
|
||||
self->operator=(ptr);
|
||||
}
|
||||
|
||||
RustObj* cppgc__WeakPersistent__Get(cppgc::WeakPersistent<RustObj>* self) {
|
||||
return self->Get();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include <v8-cppgc.h>
|
||||
#include <v8-message.h>
|
||||
|
||||
/**
|
||||
|
@ -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<RustObj>);
|
||||
static size_t RUST_cppgc__WeakMember_SIZE = sizeof(cppgc::WeakMember<RustObj>);
|
||||
|
||||
static size_t RUST_v8__TracedReference_SIZE =
|
||||
sizeof(v8::TracedReference<v8::Data>);
|
||||
|
|
499
src/cppgc.rs
499
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<Data>,
|
||||
);
|
||||
|
||||
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<T: GarbageCollected>() -> usize {
|
||||
#[repr(C)]
|
||||
struct Calc<T> {
|
||||
header: RustObj,
|
||||
data: T,
|
||||
}
|
||||
|
||||
std::mem::offset_of!(Calc<T>, data)
|
||||
}
|
||||
|
||||
fn get_object_from_rust_obj<T: GarbageCollected>(
|
||||
rust_obj: *const RustObj,
|
||||
) -> *mut T {
|
||||
unsafe { rust_obj.byte_add(object_offset_for_rust_obj::<T>()) 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<T: GarbageCollected>(&self, member: &Member<T>) {
|
||||
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<T: GarbageCollected> Traced for Member<T> {
|
||||
fn trace(&self, visitor: &Visitor) {
|
||||
unsafe { cppgc__Visitor__Trace__Member(visitor, &self.inner) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> Traced for WeakMember<T> {
|
||||
fn trace(&self, visitor: &Visitor) {
|
||||
unsafe { cppgc__Visitor__Trace__WeakMember(visitor, &self.inner) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Traced for TracedReference<T> {
|
||||
fn trace(&self, visitor: &Visitor) {
|
||||
unsafe {
|
||||
cppgc__Visitor__Trace__TracedReference(
|
||||
visitor,
|
||||
self as *const TracedReference<T> as *const TracedReference<Data>,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<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,
|
||||
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<T: GarbageCollected>(&self) -> &T {
|
||||
unsafe { self.ptr.cast::<T>().as_ref().unwrap() }
|
||||
}
|
||||
|
||||
pub unsafe fn get_mut<T: GarbageCollected>(&mut self) -> &mut T {
|
||||
unsafe { self.ptr.cast::<T>().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<T: GarbageCollected> {
|
||||
pub handle: *mut InnerMember,
|
||||
_phantom: std::marker::PhantomData<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.handle).get() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> std::ops::Deref for Member<T> {
|
||||
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<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>) }
|
||||
}
|
||||
|
||||
/// 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<T: GarbageCollected>(
|
||||
/// 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<T: GarbageCollected>(
|
||||
heap: &Heap,
|
||||
obj: *mut T,
|
||||
destroy: DestroyFn,
|
||||
obj: T,
|
||||
) -> Member<T> {
|
||||
unsafe extern "C" fn trace<T: GarbageCollected>(
|
||||
obj: *mut (),
|
||||
obj: *const RustObj,
|
||||
visitor: *mut Visitor,
|
||||
) {
|
||||
let obj = unsafe { &*(obj as *const T) };
|
||||
let obj = unsafe { &*get_object_from_rust_obj::<T>(obj) };
|
||||
obj.trace(unsafe { &*visitor });
|
||||
}
|
||||
|
||||
let handle = cppgc__make_garbage_collectable(
|
||||
heap as *const Heap as *mut _,
|
||||
obj as _,
|
||||
trace::<T>,
|
||||
destroy,
|
||||
);
|
||||
unsafe extern "C" fn destroy<T: GarbageCollected>(obj: *const RustObj) {
|
||||
let obj = get_object_from_rust_obj::<T>(obj);
|
||||
std::ptr::drop_in_place(obj);
|
||||
}
|
||||
|
||||
Member {
|
||||
handle,
|
||||
_phantom: std::marker::PhantomData,
|
||||
let additional_bytes = (object_offset_for_rust_obj::<T>()
|
||||
- std::mem::size_of::<RustObj>())
|
||||
+ std::mem::size_of::<T>();
|
||||
|
||||
let handle = unsafe {
|
||||
cppgc__make_garbage_collectable(
|
||||
heap as *const Heap as *mut _,
|
||||
additional_bytes,
|
||||
trace::<T>,
|
||||
destroy::<T>,
|
||||
)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
get_object_from_rust_obj::<T>(handle).write(obj);
|
||||
}
|
||||
|
||||
Member::new(handle)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait GetRustObj<T: GarbageCollected> {
|
||||
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<T: GarbageCollected> {
|
||||
inner: [< $name Inner >],
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> $name<T> {
|
||||
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<T>) {
|
||||
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<T: GarbageCollected> GetRustObj<T> for $name<T> {
|
||||
fn get_rust_obj(&self) -> *mut RustObj {
|
||||
self.inner.get()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> std::fmt::Debug for $name<T> {
|
||||
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<T: GarbageCollected> {
|
||||
inner: *mut [< $name Inner >],
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> $name<T> {
|
||||
#[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<T>) {
|
||||
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<T: GarbageCollected> Drop for $name<T> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
[< cppgc__ $name __DESTRUCT >](self.inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> std::fmt::Debug for $name<T> {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
fmt.debug_struct(stringify!($name)).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: GarbageCollected> GetRustObj<T> for $name<T> {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -35,6 +35,17 @@ extern "C" {
|
|||
this: *const WeakCallbackInfo,
|
||||
callback: extern "C" fn(*const WeakCallbackInfo),
|
||||
);
|
||||
|
||||
fn v8__TracedReference__CONSTRUCT(this: *mut TracedReference<Data>);
|
||||
fn v8__TracedReference__Reset(
|
||||
this: *mut TracedReference<Data>,
|
||||
isolate: *mut Isolate,
|
||||
data: *mut Data,
|
||||
);
|
||||
fn v8__TracedReference__Get(
|
||||
this: *const TracedReference<Data>,
|
||||
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<T> {
|
||||
data: [u8; crate::binding::RUST_v8__TracedReference_SIZE],
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TracedReference<T> {
|
||||
/// 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<T>) -> Self {
|
||||
let mut this = Self::empty();
|
||||
this.reset(scope, Some(data));
|
||||
this
|
||||
}
|
||||
|
||||
pub fn get<'s>(
|
||||
&self,
|
||||
scope: &mut HandleScope<'s, ()>,
|
||||
) -> Option<Local<'s, T>> {
|
||||
unsafe {
|
||||
scope.cast_local(|sd| {
|
||||
v8__TracedReference__Get(
|
||||
self as *const Self as *const TracedReference<Data>,
|
||||
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<Local<T>>) {
|
||||
unsafe {
|
||||
v8__TracedReference__Reset(
|
||||
self as *mut Self as *mut TracedReference<Data>,
|
||||
scope.get_isolate_ptr(),
|
||||
data
|
||||
.map(|h| h.as_non_null().as_ptr())
|
||||
.unwrap_or(std::ptr::null_mut())
|
||||
.cast(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<CreateParams>,
|
||||
) -> 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<CreateParams>,
|
||||
) -> 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() }
|
||||
}
|
||||
|
|
|
@ -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<Heap>) -> Self {
|
||||
self.raw.cpp_heap = heap.into_raw();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn finalize(mut self) -> (raw::CreateParams, Box<dyn Any>) {
|
||||
if self.raw.array_buffer_allocator_shared.is_null() {
|
||||
self = self.array_buffer_allocator(array_buffer::new_default_allocator());
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<PropertyAttribute>,
|
||||
);
|
||||
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<const TAG: u16, T: GarbageCollected>(
|
||||
scope: &mut HandleScope,
|
||||
wrapper: Local<Object>,
|
||||
value: &impl GetRustObj<T>,
|
||||
) {
|
||||
// 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<const TAG: u16, T: GarbageCollected>(
|
||||
scope: &mut HandleScope,
|
||||
wrapper: Local<Object>,
|
||||
) -> Member<T> {
|
||||
// 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(
|
||||
|
|
|
@ -102,8 +102,9 @@ impl SnapshotCreator {
|
|||
#[allow(clippy::new_ret_no_self)]
|
||||
pub(crate) fn new(
|
||||
external_references: Option<&'static ExternalReferences>,
|
||||
params: Option<crate::CreateParams>,
|
||||
) -> 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<crate::CreateParams>,
|
||||
) -> 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<impl Allocated<[u8]>>,
|
||||
params: Option<crate::CreateParams>,
|
||||
) -> OwnedIsolate {
|
||||
let mut snapshot_creator: MaybeUninit<Self> = 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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<v8::Value>,
|
||||
}
|
||||
|
||||
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::<TAG, 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::<TAG, Wrap>(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<v8::HandleScope>,
|
||||
script: v8::Local<v8::String>,
|
||||
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);
|
||||
|
|
Loading…
Reference in a new issue