Files
Nick Fitzgerald bd2ea901d3 Define garbage collection rooting APIs (#8011)
* Define garbage collection rooting APIs

Rooting prevents GC objects from being collected while they are actively being
used.

We have a few sometimes-conflicting goals with our GC rooting APIs:

1. Safety: It should never be possible to get a use-after-free bug because the
   user misused the rooting APIs, the collector "mistakenly" determined an
   object was unreachable and collected it, and then the user tried to access
   the object. This is our highest priority.

2. Moving GC: Our rooting APIs should moving collectors (such as generational
   and compacting collectors) where an object might get relocated after a
   collection and we need to update the GC root's pointer to the moved
   object. This means we either need cooperation and internal mutability from
   individual GC roots as well as the ability to enumerate all GC roots on the
   native Rust stack, or we need a level of indirection.

3. Performance: Our rooting APIs should generally be as low-overhead as
   possible. They definitely shouldn't require synchronization and locking to
   create, access, and drop GC roots.

4. Ergonomics: Our rooting APIs should be, if not a pleasure, then at least not
   a burden for users. Additionally, the API's types should be `Sync` and `Send`
   so that they work well with async Rust.

For example, goals (3) and (4) are in conflict when we think about how to
support (2). Ideally, for ergonomics, a root would automatically unroot itself
when dropped. But in the general case that requires holding a reference to the
store's root set, and that root set needs to be held simultaneously by all GC
roots, and they each need to mutate the set to unroot themselves. That implies
`Rc<RefCell<...>>` or `Arc<Mutex<...>>`! The former makes the store and GC root
types not `Send` and not `Sync`. The latter imposes synchronization and locking
overhead. So we instead make GC roots indirect and require passing in a store
context explicitly to unroot in the general case. This trades worse ergonomics
for better performance and support for moving GC and async Rust.

Okay, with that out of the way, this module provides two flavors of rooting
API. One for the common, scoped lifetime case, and another for the rare case
where we really need a GC root with an arbitrary, non-LIFO/non-scoped lifetime:

1. `RootScope` and `Rooted<T>`: These are used for temporarily rooting GC
   objects for the duration of a scope. Upon exiting the scope, they are
   automatically unrooted. The internal implementation takes advantage of the
   LIFO property inherent in scopes, making creating and dropping `Rooted<T>`s
   and `RootScope`s super fast and roughly equivalent to bump allocation.

   This type is vaguely similar to V8's [`HandleScope`].

   [`HandleScope`]: https://v8.github.io/api/head/classv8_1_1HandleScope.html

   Note that `Rooted<T>` can't be statically tied to its context scope via a
   lifetime parameter, unfortunately, as that would allow the creation and use
   of only one `Rooted<T>` at a time, since the `Rooted<T>` would take a borrow
   of the whole context.

   This supports the common use case for rooting and provides good ergonomics.

2. `ManuallyRooted<T>`: This is the fully general rooting API used for holding
   onto non-LIFO GC roots with arbitrary lifetimes. However, users must manually
   unroot them. Failure to manually unroot a `ManuallyRooted<T>` before it is
   dropped will result in the GC object (and everything it transitively
   references) leaking for the duration of the `Store`'s lifetime.

   This type is roughly similar to SpiderMonkey's [`PersistentRooted<T>`],
   although they avoid the manual-unrooting with internal mutation and shared
   references. (Our constraints mean we can't do those things, as mentioned
   explained above.)

   [`PersistentRooted<T>`]: http://devdoc.net/web/developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference/JS::PersistentRooted.html

At the end of the day, both `Rooted<T>` and `ManuallyRooted<T>` are just tagged
indices into the store's `RootSet`. This indirection allows working with Rust's
borrowing discipline (we use `&mut Store` to represent mutable access to the GC
heap) while still allowing rooted references to be moved around without tying up
the whole store in borrows. Additionally, and crucially, this indirection allows
us to update the *actual* GC pointers in the `RootSet` and support moving GCs
(again, as mentioned above).

* Reorganize GC-related submodules in `wasmtime-runtime`

* Reorganize GC-related submodules in `wasmtime`

* Use `Into<StoreContext[Mut]<'a, T>` for `Externref::data[_mut]` methods

* Run rooting tests under MIRI

* Make `into_abi` take an `AutoAssertNoGc`

* Don't use atomics to update externref ref counts anymore

* Try to make lifetimes/safety more-obviously correct

Remove some transmute methods, assert that `VMExternRef`s are the only valid
`VMGcRef`, etc.

* Update extenref constructor examples

* Make `GcRefImpl::transmute_ref` a non-default trait method

* Make inline fast paths for GC LIFO scopes

* Make `RootSet::unroot_gc_ref` an `unsafe` function

* Move Hash and Eq for Rooted, move to impl methods

* Remove type parameter from `AutoAssertNoGc`

Just wrap a `&mut StoreOpaque` directly.

* Make a bunch of internal `ExternRef` methods that deal with raw `VMGcRef`s take `AutoAssertNoGc` instead of `StoreOpaque`

* Fix compile after rebase

* rustfmt

* revert unrelated egraph changes

* Fix non-gc build

* Mark `AutoAssertNoGc` methods inline

* review feedback

* Temporarily remove externref support from the C API

Until we can add proper GC rooting.

* Remove doxygen reference to temp deleted function

* Remove need to `allow(private_interfaces)`

* Fix call benchmark compilation
2024-03-06 00:40:02 +00:00

62 lines
2.0 KiB
Rust

//! Small example of how to use `externref`s.
// You can execute this example with `cargo run --example externref`
use wasmtime::*;
fn main() -> Result<()> {
println!("Initializing...");
let mut config = Config::new();
config.wasm_reference_types(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, ());
println!("Compiling module...");
let module = Module::from_file(&engine, "examples/externref.wat")?;
println!("Instantiating module...");
let instance = Instance::new(&mut store, &module, &[])?;
println!("Creating new `externref`...");
let externref = ExternRef::new(&mut store, "Hello, World!");
assert!(externref.data(&store)?.is::<&'static str>());
assert_eq!(
*externref
.data(&store)?
.downcast_ref::<&'static str>()
.unwrap(),
"Hello, World!"
);
println!("Touching `externref` table...");
let table = instance.get_table(&mut store, "table").unwrap();
table.set(&mut store, 3, Some(externref.clone()).into())?;
let elem = table
.get(&mut store, 3)
.unwrap() // assert in bounds
.unwrap_extern() // assert it's an externref table
.copied()
.unwrap(); // assert the externref isn't null
assert!(Rooted::ref_eq(&store, &elem, &externref)?);
println!("Touching `externref` global...");
let global = instance.get_global(&mut store, "global").unwrap();
global.set(&mut store, Some(externref.clone()).into())?;
let global_val = global.get(&mut store).unwrap_externref().copied().unwrap();
assert!(Rooted::ref_eq(&store, &global_val, &externref)?);
println!("Calling `externref` func...");
let func = instance.get_typed_func::<Option<Rooted<ExternRef>>, Option<Rooted<ExternRef>>>(
&mut store, "func",
)?;
let ret = func.call(&mut store, Some(externref))?;
assert!(ret.is_some());
assert!(Rooted::ref_eq(&store, &ret.unwrap(), &externref)?);
println!("GCing within the store...");
store.gc();
println!("Done.");
Ok(())
}