wasmtime: Add support for func.ref and table.grow with funcrefs
`funcref`s are implemented as `NonNull<VMCallerCheckedAnyfunc>`. This should be more efficient than using a `VMExternRef` that points at a `VMCallerCheckedAnyfunc` because it gets rid of an indirection, dynamic allocation, and some reference counting. Note that the null function reference is *NOT* a null pointer; it is a `VMCallerCheckedAnyfunc` that has a null `func_ptr` member. Part of #929
This commit is contained in:
@@ -28,27 +28,27 @@ fn bad_tables() {
|
||||
|
||||
// get out of bounds
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(0, Some(1)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap();
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
|
||||
assert!(t.get(0).is_none());
|
||||
assert!(t.get(u32::max_value()).is_none());
|
||||
|
||||
// set out of bounds or wrong type
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap();
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
|
||||
assert!(t.set(0, Val::I32(0)).is_err());
|
||||
assert!(t.set(0, Val::ExternRef(None)).is_ok());
|
||||
assert!(t.set(1, Val::ExternRef(None)).is_err());
|
||||
assert!(t.set(0, Val::FuncRef(None)).is_ok());
|
||||
assert!(t.set(1, Val::FuncRef(None)).is_err());
|
||||
|
||||
// grow beyond max
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(1)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap();
|
||||
assert!(t.grow(0, Val::ExternRef(None)).is_ok());
|
||||
assert!(t.grow(1, Val::ExternRef(None)).is_err());
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
|
||||
assert!(t.grow(0, Val::FuncRef(None)).is_ok());
|
||||
assert!(t.grow(1, Val::FuncRef(None)).is_err());
|
||||
assert_eq!(t.size(), 1);
|
||||
|
||||
// grow wrong type
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, Some(2)));
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::ExternRef(None)).unwrap();
|
||||
let t = Table::new(&Store::default(), ty.clone(), Val::FuncRef(None)).unwrap();
|
||||
assert!(t.grow(1, Val::I32(0)).is_err());
|
||||
assert_eq!(t.size(), 1);
|
||||
}
|
||||
@@ -69,7 +69,7 @@ fn cross_store() -> anyhow::Result<()> {
|
||||
let ty = MemoryType::new(Limits::new(1, None));
|
||||
let memory = Memory::new(&store2, ty);
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
|
||||
let table = Table::new(&store2, ty, Val::ExternRef(None))?;
|
||||
let table = Table::new(&store2, ty, Val::FuncRef(None))?;
|
||||
|
||||
let need_func = Module::new(&engine, r#"(module (import "" "" (func)))"#)?;
|
||||
assert!(Instance::new(&store1, &need_func, &[func.into()]).is_err());
|
||||
@@ -85,8 +85,8 @@ fn cross_store() -> anyhow::Result<()> {
|
||||
|
||||
// ============ Cross-store globals ==============
|
||||
|
||||
let store1val = Val::FuncRef(Func::wrap(&store1, || {}));
|
||||
let store2val = Val::FuncRef(Func::wrap(&store2, || {}));
|
||||
let store1val = Val::FuncRef(Some(Func::wrap(&store1, || {})));
|
||||
let store2val = Val::FuncRef(Some(Func::wrap(&store2, || {})));
|
||||
|
||||
let ty = GlobalType::new(ValType::FuncRef, Mutability::Var);
|
||||
assert!(Global::new(&store2, ty.clone(), store1val.clone()).is_err());
|
||||
|
||||
85
tests/all/funcref.rs
Normal file
85
tests/all/funcref.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
use super::ref_types_module;
|
||||
use wasmtime::*;
|
||||
|
||||
#[test]
|
||||
fn pass_funcref_in_and_out_of_wasm() -> anyhow::Result<()> {
|
||||
let (store, module) = ref_types_module(
|
||||
r#"
|
||||
(module
|
||||
(func (export "func") (param funcref) (result funcref)
|
||||
local.get 0
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let instance = Instance::new(&store, &module, &[])?;
|
||||
let func = instance.get_func("func").unwrap();
|
||||
|
||||
// Pass in a non-null funcref.
|
||||
{
|
||||
let results = func.call(&[Val::FuncRef(Some(func.clone()))])?;
|
||||
assert_eq!(results.len(), 1);
|
||||
|
||||
// Can't compare `Func` for equality, so this is the best we can do here.
|
||||
let result_func = results[0].unwrap_funcref().unwrap();
|
||||
assert_eq!(func.ty(), result_func.ty());
|
||||
}
|
||||
|
||||
// Pass in a null funcref.
|
||||
{
|
||||
let results = func.call(&[Val::FuncRef(None)])?;
|
||||
assert_eq!(results.len(), 1);
|
||||
|
||||
let result_func = results[0].unwrap_funcref();
|
||||
assert!(result_func.is_none());
|
||||
}
|
||||
|
||||
// Pass in a `funcref` from another instance.
|
||||
{
|
||||
let other_instance = Instance::new(&store, &module, &[])?;
|
||||
let other_instance_func = other_instance.get_func("func").unwrap();
|
||||
|
||||
let results = func.call(&[Val::FuncRef(Some(other_instance_func.clone()))])?;
|
||||
assert_eq!(results.len(), 1);
|
||||
|
||||
// Can't compare `Func` for equality, so this is the best we can do here.
|
||||
let result_func = results[0].unwrap_funcref().unwrap();
|
||||
assert_eq!(other_instance_func.ty(), result_func.ty());
|
||||
}
|
||||
|
||||
// Passing in a `funcref` from another store fails.
|
||||
{
|
||||
let (other_store, other_module) = ref_types_module(r#"(module (func (export "f")))"#)?;
|
||||
let other_store_instance = Instance::new(&other_store, &other_module, &[])?;
|
||||
let f = other_store_instance.get_func("f").unwrap();
|
||||
|
||||
assert!(func.call(&[Val::FuncRef(Some(f))]).is_err());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn receive_null_funcref_from_wasm() -> anyhow::Result<()> {
|
||||
let (store, module) = ref_types_module(
|
||||
r#"
|
||||
(module
|
||||
(func (export "get-null") (result funcref)
|
||||
ref.null func
|
||||
)
|
||||
)
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let instance = Instance::new(&store, &module, &[])?;
|
||||
let get_null = instance.get_func("get-null").unwrap();
|
||||
|
||||
let results = get_null.call(&[])?;
|
||||
assert_eq!(results.len(), 1);
|
||||
|
||||
let result_func = results[0].unwrap_funcref();
|
||||
assert!(result_func.is_none());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,21 +1,8 @@
|
||||
use super::ref_types_module;
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
use wasmtime::*;
|
||||
|
||||
fn ref_types_module(source: &str) -> anyhow::Result<(Store, Module)> {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut config = Config::new();
|
||||
config.wasm_reference_types(true);
|
||||
|
||||
let engine = Engine::new(&config);
|
||||
let store = Store::new(&engine);
|
||||
|
||||
let module = Module::new(&engine, source)?;
|
||||
|
||||
Ok((store, module))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smoke_test_gc() -> anyhow::Result<()> {
|
||||
let (store, module) = ref_types_module(
|
||||
|
||||
@@ -23,6 +23,7 @@ fn test_invoke_func_via_table() -> Result<()> {
|
||||
.unwrap()
|
||||
.funcref()
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.clone();
|
||||
let result = f.call(&[]).unwrap();
|
||||
assert_eq!(result[0].unwrap_i64(), 42);
|
||||
|
||||
@@ -59,11 +59,11 @@ fn link_twice_bad() -> Result<()> {
|
||||
|
||||
// tables
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
|
||||
let table = Table::new(&store, ty, Val::ExternRef(None))?;
|
||||
let table = Table::new(&store, ty, Val::FuncRef(None))?;
|
||||
linker.define("", "", table.clone())?;
|
||||
assert!(linker.define("", "", table.clone()).is_err());
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(2, None));
|
||||
let table = Table::new(&store, ty, Val::ExternRef(None))?;
|
||||
let table = Table::new(&store, ty, Val::FuncRef(None))?;
|
||||
assert!(linker.define("", "", table.clone()).is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,4 +21,26 @@ mod wast;
|
||||
|
||||
// TODO(#1886): Cranelift only supports reference types on x64.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod funcref;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
mod gc;
|
||||
|
||||
/// A helper to compile a module in a new store with reference types enabled.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn ref_types_module(
|
||||
source: &str,
|
||||
) -> anyhow::Result<(wasmtime::Store, wasmtime::Module)> {
|
||||
use wasmtime::*;
|
||||
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let mut config = Config::new();
|
||||
config.wasm_reference_types(true);
|
||||
|
||||
let engine = Engine::new(&config);
|
||||
let store = Store::new(&engine);
|
||||
|
||||
let module = Module::new(&engine, source)?;
|
||||
|
||||
Ok((store, module))
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ use wasmtime::*;
|
||||
fn get_none() {
|
||||
let store = Store::default();
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
|
||||
let table = Table::new(&store, ty, Val::ExternRef(None)).unwrap();
|
||||
let table = Table::new(&store, ty, Val::FuncRef(None)).unwrap();
|
||||
match table.get(0) {
|
||||
Some(Val::ExternRef(None)) => {}
|
||||
Some(Val::FuncRef(None)) => {}
|
||||
_ => panic!(),
|
||||
}
|
||||
assert!(table.get(1).is_none());
|
||||
|
||||
@@ -11,10 +11,10 @@ fn use_func_after_drop() -> Result<()> {
|
||||
assert_eq!(closed_over_data, "abcd");
|
||||
});
|
||||
let ty = TableType::new(ValType::FuncRef, Limits::new(1, None));
|
||||
table = Table::new(&store, ty, Val::ExternRef(None))?;
|
||||
table = Table::new(&store, ty, Val::FuncRef(None))?;
|
||||
table.set(0, func.into())?;
|
||||
}
|
||||
let func = table.get(0).unwrap().funcref().unwrap().clone();
|
||||
let func = table.get(0).unwrap().funcref().unwrap().unwrap().clone();
|
||||
let func = func.get0::<()>()?;
|
||||
func()?;
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user