Better to be loud that we don't support attaching arbitrary host info to `externref`s than to limp along and pretend we do support it. Supporting it properly won't reuse any of this code anyways.
213 lines
5.7 KiB
Rust
213 lines
5.7 KiB
Rust
use super::ref_types_module;
|
|
use std::cell::Cell;
|
|
use std::rc::Rc;
|
|
use wasmtime::*;
|
|
|
|
#[test]
|
|
fn smoke_test_gc() -> anyhow::Result<()> {
|
|
let (store, module) = ref_types_module(
|
|
r#"
|
|
(module
|
|
(import "" "" (func $do_gc))
|
|
(func $recursive (export "func") (param i32 externref) (result externref)
|
|
local.get 0
|
|
i32.eqz
|
|
if (result externref)
|
|
call $do_gc
|
|
local.get 1
|
|
else
|
|
local.get 0
|
|
i32.const 1
|
|
i32.sub
|
|
local.get 1
|
|
call $recursive
|
|
end
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let do_gc = Func::wrap(&store, {
|
|
let store = store.clone();
|
|
move || {
|
|
// Do a GC with `externref`s on the stack in Wasm frames.
|
|
store.gc();
|
|
}
|
|
});
|
|
let instance = Instance::new(&store, &module, &[do_gc.into()])?;
|
|
let func = instance.get_func("func").unwrap();
|
|
|
|
let inner_dropped = Rc::new(Cell::new(false));
|
|
let r = ExternRef::new(SetFlagOnDrop(inner_dropped.clone()));
|
|
{
|
|
let args = [Val::I32(5), Val::ExternRef(Some(r.clone()))];
|
|
func.call(&args)?;
|
|
}
|
|
|
|
// Still held alive by the `VMExternRefActivationsTable` (potentially in
|
|
// multiple slots within the table) and by this `r` local.
|
|
assert!(r.strong_count() >= 2);
|
|
|
|
// Doing a GC should see that there aren't any `externref`s on the stack in
|
|
// Wasm frames anymore.
|
|
store.gc();
|
|
assert_eq!(r.strong_count(), 1);
|
|
|
|
// Dropping `r` should drop the inner `SetFlagOnDrop` value.
|
|
drop(r);
|
|
assert!(inner_dropped.get());
|
|
|
|
return Ok(());
|
|
|
|
struct SetFlagOnDrop(Rc<Cell<bool>>);
|
|
|
|
impl Drop for SetFlagOnDrop {
|
|
fn drop(&mut self) {
|
|
self.0.set(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn wasm_dropping_refs() -> anyhow::Result<()> {
|
|
let (store, module) = ref_types_module(
|
|
r#"
|
|
(module
|
|
(func (export "drop_ref") (param externref)
|
|
nop
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let instance = Instance::new(&store, &module, &[])?;
|
|
let drop_ref = instance.get_func("drop_ref").unwrap();
|
|
|
|
let num_refs_dropped = Rc::new(Cell::new(0));
|
|
|
|
// NB: 4096 is greater than the initial `VMExternRefActivationsTable`
|
|
// capacity, so this will trigger at least one GC.
|
|
for _ in 0..4096 {
|
|
let r = ExternRef::new(CountDrops(num_refs_dropped.clone()));
|
|
let args = [Val::ExternRef(Some(r))];
|
|
drop_ref.call(&args)?;
|
|
}
|
|
|
|
assert!(num_refs_dropped.get() > 0);
|
|
|
|
// And after doing a final GC, all the refs should have been dropped.
|
|
store.gc();
|
|
assert_eq!(num_refs_dropped.get(), 4096);
|
|
|
|
return Ok(());
|
|
|
|
struct CountDrops(Rc<Cell<usize>>);
|
|
|
|
impl Drop for CountDrops {
|
|
fn drop(&mut self) {
|
|
self.0.set(self.0.get() + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn many_live_refs() -> anyhow::Result<()> {
|
|
let mut wat = r#"
|
|
(module
|
|
;; Make new `externref`s.
|
|
(import "" "make_ref" (func $make_ref (result externref)))
|
|
|
|
;; Observe an `externref` so it is kept live.
|
|
(import "" "observe_ref" (func $observe_ref (param externref)))
|
|
|
|
(func (export "many_live_refs")
|
|
"#
|
|
.to_string();
|
|
|
|
// This is more than the initial `VMExternRefActivationsTable` capacity, so
|
|
// it will need to allocate additional bump chunks.
|
|
const NUM_LIVE_REFS: usize = 1024;
|
|
|
|
// Push `externref`s onto the stack.
|
|
for _ in 0..NUM_LIVE_REFS {
|
|
wat.push_str("(call $make_ref)\n");
|
|
}
|
|
|
|
// Pop `externref`s from the stack. Because we pass each of them to a
|
|
// function call here, they are all live references for the duration of
|
|
// their lifetimes.
|
|
for _ in 0..NUM_LIVE_REFS {
|
|
wat.push_str("(call $observe_ref)\n");
|
|
}
|
|
|
|
wat.push_str(
|
|
"
|
|
) ;; func
|
|
) ;; module
|
|
",
|
|
);
|
|
|
|
let (store, module) = ref_types_module(&wat)?;
|
|
|
|
let live_refs = Rc::new(Cell::new(0));
|
|
|
|
let make_ref = Func::new(
|
|
&store,
|
|
FuncType::new(
|
|
vec![].into_boxed_slice(),
|
|
vec![ValType::ExternRef].into_boxed_slice(),
|
|
),
|
|
{
|
|
let live_refs = live_refs.clone();
|
|
move |_caller, _params, results| {
|
|
results[0] =
|
|
Val::ExternRef(Some(ExternRef::new(CountLiveRefs::new(live_refs.clone()))));
|
|
Ok(())
|
|
}
|
|
},
|
|
);
|
|
|
|
let observe_ref = Func::new(
|
|
&store,
|
|
FuncType::new(
|
|
vec![ValType::ExternRef].into_boxed_slice(),
|
|
vec![].into_boxed_slice(),
|
|
),
|
|
|_caller, params, _results| {
|
|
let r = params[0].externref().unwrap().unwrap();
|
|
let r = r.data().downcast_ref::<CountLiveRefs>().unwrap();
|
|
assert!(r.live_refs.get() > 0);
|
|
Ok(())
|
|
},
|
|
);
|
|
|
|
let instance = Instance::new(&store, &module, &[make_ref.into(), observe_ref.into()])?;
|
|
let many_live_refs = instance.get_func("many_live_refs").unwrap();
|
|
|
|
many_live_refs.call(&[])?;
|
|
|
|
store.gc();
|
|
assert_eq!(live_refs.get(), 0);
|
|
|
|
return Ok(());
|
|
|
|
struct CountLiveRefs {
|
|
live_refs: Rc<Cell<usize>>,
|
|
}
|
|
|
|
impl CountLiveRefs {
|
|
fn new(live_refs: Rc<Cell<usize>>) -> Self {
|
|
let live = live_refs.get();
|
|
live_refs.set(live + 1);
|
|
Self { live_refs }
|
|
}
|
|
}
|
|
|
|
impl Drop for CountLiveRefs {
|
|
fn drop(&mut self) {
|
|
let live = self.live_refs.get();
|
|
self.live_refs.set(live - 1);
|
|
}
|
|
}
|
|
}
|