Files
Nathan Whitaker 65fb5c1683 rework scopes to make them stack allocated and remove runtime bookkeeping of scopes (#1830)
Before this change, `HandleScope`s (and other scope types) were stored in the heap and we did bookkeeping at runtime of the current stack of scopes. The reasons were primarily:
- raw handle scopes need to be pinned in memory once initialized
- we couldn't enforce at compile that a `Local` didn't outlive the scope it was created from
The latter lead us to need a workaround where scopes actually were not exited when they were dropped, rather they were exited the next time we activated another scope.

This change refactors the scope implementation to:
- make scopes stack allocated, to reduce overhead of using them
- remove the runtime bookkeeping
- use lifetimes to prevent scopes from being dropped, so we can remove the `zombie` scope workaround

The practical effect is that creating a handle scope changes from:

```rust
let mut scope = v8::HandleScope::new(&mut isolate);
let local = v8::Integer::new(&mut scope, 123);
```

to:

```rust
let scope = std::pin::pin!(v8::HandleScope::new(&mut isolate));
let scope = scope.init();
let local = v8::Integer::new(&scope, 123);

// or shorter
v8::scope!(let scope, &mut isolate);
let local = v8::Integer::new(scope, 123);
```

If you accept a `HandleScope` as a parameter in a function, you would change it from:

```rust
fn takes_scope(s: &mut v8::HandleScope) {}
```

to

```rust
fn takes_scope(s: &mut v8::PinScope) {}
```

(`v8::PinScope` is a type alias for a pinned handle scope).
2025-09-22 21:24:22 +00:00

109 lines
2.6 KiB
Rust

// Copyright 2019-2021 the Deno authors. All rights reserved. MIT license.
//
// This sample program shows how to set up a stand-alone cppgc heap.
// 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: 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)?;
unsafe {
// SAFETY: `next` is visited in `trace()`.
if let Some(next) = self.next.get() {
write!(f, "{next}")?;
}
}
Ok(())
}
}
impl Rope {
pub fn new(part: String, next: Option<v8::cppgc::UnsafePtr<Rope>>) -> Rope {
let next = match next {
Some(p) => v8::cppgc::Member::new(&p),
None => v8::cppgc::Member::empty(),
};
Self { part, next }
}
}
unsafe impl v8::cppgc::GarbageCollected for Rope {
fn trace(&self, visitor: &mut v8::cppgc::Visitor) {
visitor.trace(&self.next);
}
fn get_name(&self) -> &'static std::ffi::CStr {
c"Rope"
}
}
impl Drop for Rope {
fn drop(&mut self) {
println!("Dropping: {}", self.part);
}
}
fn main() {
let platform = v8::new_default_platform(0, false).make_shared();
v8::V8::initialize_platform(platform.clone());
v8::cppgc::initialize_process(platform.clone());
v8::V8::initialize();
{
// Create a managed heap.
let heap =
v8::cppgc::Heap::create(platform, v8::cppgc::HeapCreateParams::default());
// Allocate a string rope on the managed heap.
let rope = unsafe {
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.as_ref() });
// Manually trigger garbage collection.
heap.enable_detached_garbage_collections_for_testing();
println!("Collect: MayContainHeapPointers");
unsafe {
heap.collect_garbage_for_testing(
v8::cppgc::EmbedderStackState::MayContainHeapPointers,
);
}
// Should still be live here:
println!("{}", unsafe { rope.as_ref() });
println!("Collect: NoHeapPointers");
unsafe {
heap.collect_garbage_for_testing(
v8::cppgc::EmbedderStackState::NoHeapPointers,
);
}
// Should be dead now.
}
// Gracefully shutdown the process.
unsafe {
v8::cppgc::shutdown_process();
v8::V8::dispose();
}
v8::V8::dispose_platform();
}