wasmtime: Implement global.{get,set} for externref globals (#1969)
* wasmtime: Implement `global.{get,set}` for externref globals
We use libcalls to implement these -- unlike `table.{get,set}`, for which we
create inline JIT fast paths -- because no known toolchain actually uses
externref globals.
Part of #929
* wasmtime: Enable `{extern,func}ref` globals in the API
This commit is contained in:
@@ -111,3 +111,73 @@ fn cross_store() -> anyhow::Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set_externref_globals_via_api() -> anyhow::Result<()> {
|
||||
let mut cfg = Config::new();
|
||||
cfg.wasm_reference_types(true);
|
||||
let engine = Engine::new(&cfg);
|
||||
let store = Store::new(&engine);
|
||||
|
||||
// Initialize with a null externref.
|
||||
|
||||
let global = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::ExternRef, Mutability::Var),
|
||||
Val::ExternRef(None),
|
||||
)?;
|
||||
assert!(global.get().unwrap_externref().is_none());
|
||||
|
||||
global.set(Val::ExternRef(Some(ExternRef::new("hello".to_string()))))?;
|
||||
let r = global.get().unwrap_externref().unwrap();
|
||||
assert!(r.data().is::<String>());
|
||||
assert_eq!(r.data().downcast_ref::<String>().unwrap(), "hello");
|
||||
|
||||
// Initialize with a non-null externref.
|
||||
|
||||
let global = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::ExternRef, Mutability::Const),
|
||||
Val::ExternRef(Some(ExternRef::new(42_i32))),
|
||||
)?;
|
||||
let r = global.get().unwrap_externref().unwrap();
|
||||
assert!(r.data().is::<i32>());
|
||||
assert_eq!(r.data().downcast_ref::<i32>().copied().unwrap(), 42);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_set_funcref_globals_via_api() -> anyhow::Result<()> {
|
||||
let mut cfg = Config::new();
|
||||
cfg.wasm_reference_types(true);
|
||||
let engine = Engine::new(&cfg);
|
||||
let store = Store::new(&engine);
|
||||
|
||||
let f = Func::wrap(&store, || {});
|
||||
|
||||
// Initialize with a null funcref.
|
||||
|
||||
let global = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::FuncRef, Mutability::Var),
|
||||
Val::FuncRef(None),
|
||||
)?;
|
||||
assert!(global.get().unwrap_funcref().is_none());
|
||||
|
||||
global.set(Val::FuncRef(Some(f.clone())))?;
|
||||
let f2 = global.get().unwrap_funcref().cloned().unwrap();
|
||||
assert_eq!(f.ty(), f2.ty());
|
||||
|
||||
// Initialize with a non-null funcref.
|
||||
|
||||
let global = Global::new(
|
||||
&store,
|
||||
GlobalType::new(ValType::FuncRef, Mutability::Var),
|
||||
Val::FuncRef(Some(f.clone())),
|
||||
)?;
|
||||
let f2 = global.get().unwrap_funcref().cloned().unwrap();
|
||||
assert_eq!(f.ty(), f2.ty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -517,3 +517,97 @@ fn pass_externref_into_wasm_during_destructor_in_gc() -> anyhow::Result<()> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn gc_on_drop_in_mutable_externref_global() -> anyhow::Result<()> {
|
||||
let (store, module) = ref_types_module(
|
||||
r#"
|
||||
(module
|
||||
(global $g (mut externref) (ref.null extern))
|
||||
|
||||
(func (export "set-g") (param externref)
|
||||
(global.set $g (local.get 0))
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let instance = Instance::new(&store, &module, &[])?;
|
||||
let set_g = instance.get_func("set-g").unwrap();
|
||||
|
||||
let gc_count = Rc::new(Cell::new(0));
|
||||
|
||||
// Put a `GcOnDrop` into the global.
|
||||
{
|
||||
let args = vec![Val::ExternRef(Some(ExternRef::new(GcOnDrop {
|
||||
store: store.clone(),
|
||||
gc_count: gc_count.clone(),
|
||||
})))];
|
||||
set_g.call(&args)?;
|
||||
}
|
||||
|
||||
// Remove the `GcOnDrop` from the `VMExternRefActivationsTable`.
|
||||
store.gc();
|
||||
|
||||
// Overwrite the `GcOnDrop` global value, causing it to be dropped, and
|
||||
// triggering a GC.
|
||||
assert_eq!(gc_count.get(), 0);
|
||||
set_g.call(&[Val::ExternRef(None)])?;
|
||||
assert_eq!(gc_count.get(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn touch_own_externref_global_on_drop() -> anyhow::Result<()> {
|
||||
let (store, module) = ref_types_module(
|
||||
r#"
|
||||
(module
|
||||
(global $g (export "g") (mut externref) (ref.null extern))
|
||||
|
||||
(func (export "set-g") (param externref)
|
||||
(global.set $g (local.get 0))
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let instance = Instance::new(&store, &module, &[])?;
|
||||
let g = instance.get_global("g").unwrap();
|
||||
let set_g = instance.get_func("set-g").unwrap();
|
||||
|
||||
let touched = Rc::new(Cell::new(false));
|
||||
|
||||
{
|
||||
let args = vec![Val::ExternRef(Some(ExternRef::new(TouchGlobalOnDrop {
|
||||
g,
|
||||
touched: touched.clone(),
|
||||
})))];
|
||||
set_g.call(&args)?;
|
||||
}
|
||||
|
||||
// Remove the `TouchGlobalOnDrop` from the `VMExternRefActivationsTable`.
|
||||
store.gc();
|
||||
|
||||
assert!(!touched.get());
|
||||
set_g.call(&[Val::ExternRef(Some(ExternRef::new("hello".to_string())))])?;
|
||||
assert!(touched.get());
|
||||
|
||||
return Ok(());
|
||||
|
||||
struct TouchGlobalOnDrop {
|
||||
g: Global,
|
||||
touched: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl Drop for TouchGlobalOnDrop {
|
||||
fn drop(&mut self) {
|
||||
// From the `Drop` implementation, we see the new global value, not
|
||||
// `self`.
|
||||
let r = self.g.get().unwrap_externref().unwrap();
|
||||
assert!(r.data().is::<String>());
|
||||
assert_eq!(r.data().downcast_ref::<String>().unwrap(), "hello");
|
||||
self.touched.set(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user