You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
476 lines
14 KiB
476 lines
14 KiB
use super::ref_types_module;
|
|
use super::skip_pooling_allocator_tests;
|
|
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::SeqCst};
|
|
use std::sync::Arc;
|
|
use wasmtime::*;
|
|
|
|
struct SetFlagOnDrop(Arc<AtomicBool>);
|
|
|
|
impl Drop for SetFlagOnDrop {
|
|
fn drop(&mut self) {
|
|
self.0.store(true, SeqCst);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn smoke_test_gc() -> anyhow::Result<()> {
|
|
let (mut 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(&mut store, |mut caller: Caller<'_, _>| {
|
|
// Do a GC with `externref`s on the stack in Wasm frames.
|
|
caller.gc();
|
|
});
|
|
let instance = Instance::new(&mut store, &module, &[do_gc.into()])?;
|
|
let func = instance.get_func(&mut store, "func").unwrap();
|
|
|
|
let inner_dropped = Arc::new(AtomicBool::new(false));
|
|
let r = ExternRef::new(SetFlagOnDrop(inner_dropped.clone()));
|
|
{
|
|
let args = [Val::I32(5), Val::ExternRef(Some(r.clone()))];
|
|
func.call(&mut store, &args, &mut [Val::I32(0)])?;
|
|
}
|
|
|
|
// 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.load(SeqCst));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn wasm_dropping_refs() -> anyhow::Result<()> {
|
|
let (mut store, module) = ref_types_module(
|
|
r#"
|
|
(module
|
|
(func (export "drop_ref") (param externref)
|
|
nop
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let drop_ref = instance.get_func(&mut store, "drop_ref").unwrap();
|
|
|
|
let num_refs_dropped = Arc::new(AtomicUsize::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(&mut store, &args, &mut [])?;
|
|
}
|
|
|
|
assert!(num_refs_dropped.load(SeqCst) > 0);
|
|
|
|
// And after doing a final GC, all the refs should have been dropped.
|
|
store.gc();
|
|
assert_eq!(num_refs_dropped.load(SeqCst), 4096);
|
|
|
|
return Ok(());
|
|
|
|
struct CountDrops(Arc<AtomicUsize>);
|
|
|
|
impl Drop for CountDrops {
|
|
fn drop(&mut self) {
|
|
self.0.fetch_add(1, SeqCst);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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 (mut store, module) = ref_types_module(&wat)?;
|
|
|
|
let live_refs = Arc::new(AtomicUsize::new(0));
|
|
|
|
let make_ref = Func::wrap(&mut store, {
|
|
let live_refs = live_refs.clone();
|
|
move || Some(ExternRef::new(CountLiveRefs::new(live_refs.clone())))
|
|
});
|
|
|
|
let observe_ref = Func::wrap(&mut store, |r: Option<ExternRef>| {
|
|
let r = r.unwrap();
|
|
let r = r.data().downcast_ref::<CountLiveRefs>().unwrap();
|
|
assert!(r.live_refs.load(SeqCst) > 0);
|
|
});
|
|
|
|
let instance = Instance::new(&mut store, &module, &[make_ref.into(), observe_ref.into()])?;
|
|
let many_live_refs = instance.get_func(&mut store, "many_live_refs").unwrap();
|
|
|
|
many_live_refs.call(&mut store, &[], &mut [])?;
|
|
|
|
store.gc();
|
|
assert_eq!(live_refs.load(SeqCst), 0);
|
|
|
|
return Ok(());
|
|
|
|
struct CountLiveRefs {
|
|
live_refs: Arc<AtomicUsize>,
|
|
}
|
|
|
|
impl CountLiveRefs {
|
|
fn new(live_refs: Arc<AtomicUsize>) -> Self {
|
|
live_refs.fetch_add(1, SeqCst);
|
|
Self { live_refs }
|
|
}
|
|
}
|
|
|
|
impl Drop for CountLiveRefs {
|
|
fn drop(&mut self) {
|
|
self.live_refs.fetch_sub(1, SeqCst);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn drop_externref_via_table_set() -> anyhow::Result<()> {
|
|
let (mut store, module) = ref_types_module(
|
|
r#"
|
|
(module
|
|
(table $t 1 externref)
|
|
|
|
(func (export "table-set") (param externref)
|
|
(table.set $t (i32.const 0) (local.get 0))
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let table_set = instance.get_func(&mut store, "table-set").unwrap();
|
|
|
|
let foo_is_dropped = Arc::new(AtomicBool::new(false));
|
|
let bar_is_dropped = Arc::new(AtomicBool::new(false));
|
|
|
|
let foo = ExternRef::new(SetFlagOnDrop(foo_is_dropped.clone()));
|
|
let bar = ExternRef::new(SetFlagOnDrop(bar_is_dropped.clone()));
|
|
|
|
{
|
|
let args = vec![Val::ExternRef(Some(foo))];
|
|
table_set.call(&mut store, &args, &mut [])?;
|
|
}
|
|
store.gc();
|
|
assert!(!foo_is_dropped.load(SeqCst));
|
|
assert!(!bar_is_dropped.load(SeqCst));
|
|
|
|
{
|
|
let args = vec![Val::ExternRef(Some(bar))];
|
|
table_set.call(&mut store, &args, &mut [])?;
|
|
}
|
|
store.gc();
|
|
assert!(foo_is_dropped.load(SeqCst));
|
|
assert!(!bar_is_dropped.load(SeqCst));
|
|
|
|
table_set.call(&mut store, &[Val::ExternRef(None)], &mut [])?;
|
|
assert!(foo_is_dropped.load(SeqCst));
|
|
assert!(bar_is_dropped.load(SeqCst));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn global_drops_externref() -> anyhow::Result<()> {
|
|
test_engine(&Engine::default())?;
|
|
|
|
if !skip_pooling_allocator_tests() {
|
|
test_engine(&Engine::new(
|
|
Config::new().allocation_strategy(InstanceAllocationStrategy::pooling()),
|
|
)?)?;
|
|
}
|
|
|
|
return Ok(());
|
|
|
|
fn test_engine(engine: &Engine) -> anyhow::Result<()> {
|
|
let mut store = Store::new(&engine, ());
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
|
|
Global::new(
|
|
&mut store,
|
|
GlobalType::new(ValType::ExternRef, Mutability::Const),
|
|
externref.into(),
|
|
)?;
|
|
drop(store);
|
|
assert!(flag.load(SeqCst));
|
|
|
|
let mut store = Store::new(&engine, ());
|
|
let module = Module::new(
|
|
&engine,
|
|
r#"
|
|
(module
|
|
(global (mut externref) (ref.null extern))
|
|
|
|
(func (export "run") (param externref)
|
|
local.get 0
|
|
global.set 0
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
|
|
run.call(&mut store, Some(externref))?;
|
|
drop(store);
|
|
assert!(flag.load(SeqCst));
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn table_drops_externref() -> anyhow::Result<()> {
|
|
test_engine(&Engine::default())?;
|
|
|
|
if !skip_pooling_allocator_tests() {
|
|
test_engine(&Engine::new(
|
|
Config::new().allocation_strategy(InstanceAllocationStrategy::pooling()),
|
|
)?)?;
|
|
}
|
|
|
|
return Ok(());
|
|
|
|
fn test_engine(engine: &Engine) -> anyhow::Result<()> {
|
|
let mut store = Store::new(&engine, ());
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
|
|
Table::new(
|
|
&mut store,
|
|
TableType::new(ValType::ExternRef, 1, None),
|
|
externref.into(),
|
|
)?;
|
|
drop(store);
|
|
assert!(flag.load(SeqCst));
|
|
|
|
let mut store = Store::new(&engine, ());
|
|
let module = Module::new(
|
|
&engine,
|
|
r#"
|
|
(module
|
|
(table 1 externref)
|
|
|
|
(func (export "run") (param externref)
|
|
i32.const 0
|
|
local.get 0
|
|
table.set 0
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
|
|
run.call(&mut store, Some(externref))?;
|
|
drop(store);
|
|
assert!(flag.load(SeqCst));
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn gee_i_sure_hope_refcounting_is_atomic() -> anyhow::Result<()> {
|
|
let mut config = Config::new();
|
|
config.wasm_reference_types(true);
|
|
config.interruptable(true);
|
|
let engine = Engine::new(&config)?;
|
|
let mut store = Store::new(&engine, ());
|
|
let module = Module::new(
|
|
&engine,
|
|
r#"
|
|
(module
|
|
(global (mut externref) (ref.null extern))
|
|
(table 1 externref)
|
|
|
|
(func (export "run") (param externref)
|
|
local.get 0
|
|
global.set 0
|
|
i32.const 0
|
|
local.get 0
|
|
table.set 0
|
|
loop
|
|
global.get 0
|
|
global.set 0
|
|
|
|
i32.const 0
|
|
i32.const 0
|
|
table.get
|
|
table.set
|
|
|
|
local.get 0
|
|
call $f
|
|
|
|
br 0
|
|
end
|
|
)
|
|
|
|
(func $f (param externref))
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let instance = Instance::new(&mut store, &module, &[])?;
|
|
let run = instance.get_typed_func::<Option<ExternRef>, (), _>(&mut store, "run")?;
|
|
|
|
let flag = Arc::new(AtomicBool::new(false));
|
|
let externref = ExternRef::new(SetFlagOnDrop(flag.clone()));
|
|
let externref2 = externref.clone();
|
|
let handle = store.interrupt_handle()?;
|
|
|
|
let child = std::thread::spawn(move || run.call(&mut store, Some(externref2)));
|
|
|
|
for _ in 0..10000 {
|
|
drop(externref.clone());
|
|
}
|
|
handle.interrupt();
|
|
|
|
assert!(child.join().unwrap().is_err());
|
|
assert!(!flag.load(SeqCst));
|
|
assert_eq!(externref.strong_count(), 1);
|
|
drop(externref);
|
|
assert!(flag.load(SeqCst));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn global_init_no_leak() -> anyhow::Result<()> {
|
|
let (mut store, module) = ref_types_module(
|
|
r#"
|
|
(module
|
|
(import "" "" (global externref))
|
|
(global externref (global.get 0))
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let externref = ExternRef::new(());
|
|
let global = Global::new(
|
|
&mut store,
|
|
GlobalType::new(ValType::ExternRef, Mutability::Const),
|
|
externref.clone().into(),
|
|
)?;
|
|
Instance::new(&mut store, &module, &[global.into()])?;
|
|
drop(store);
|
|
assert_eq!(externref.strong_count(), 1);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn no_gc_middle_of_args() -> anyhow::Result<()> {
|
|
let (mut store, module) = ref_types_module(
|
|
r#"
|
|
(module
|
|
(import "" "return_some" (func $return (result externref externref externref)))
|
|
(import "" "take_some" (func $take (param externref externref externref)))
|
|
(func (export "run")
|
|
(local i32)
|
|
i32.const 1000
|
|
local.set 0
|
|
loop
|
|
call $return
|
|
call $take
|
|
local.get 0
|
|
i32.const -1
|
|
i32.add
|
|
local.tee 0
|
|
br_if 0
|
|
end
|
|
)
|
|
)
|
|
"#,
|
|
)?;
|
|
|
|
let mut linker = Linker::new(store.engine());
|
|
linker.func_wrap("", "return_some", || {
|
|
(
|
|
Some(ExternRef::new("a".to_string())),
|
|
Some(ExternRef::new("b".to_string())),
|
|
Some(ExternRef::new("c".to_string())),
|
|
)
|
|
})?;
|
|
linker.func_wrap(
|
|
"",
|
|
"take_some",
|
|
|a: Option<ExternRef>, b: Option<ExternRef>, c: Option<ExternRef>| {
|
|
let a = a.unwrap();
|
|
let b = b.unwrap();
|
|
let c = c.unwrap();
|
|
assert_eq!(a.data().downcast_ref::<String>().unwrap(), "a");
|
|
assert_eq!(b.data().downcast_ref::<String>().unwrap(), "b");
|
|
assert_eq!(c.data().downcast_ref::<String>().unwrap(), "c");
|
|
},
|
|
)?;
|
|
|
|
let instance = linker.instantiate(&mut store, &module)?;
|
|
let func = instance.get_typed_func::<(), (), _>(&mut store, "run")?;
|
|
func.call(&mut store, ())?;
|
|
|
|
Ok(())
|
|
}
|
|
|