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:
Nick Fitzgerald
2020-06-18 11:04:40 -07:00
parent ddc2ce8080
commit 58bb5dd953
37 changed files with 603 additions and 305 deletions

View File

@@ -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
View 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(())
}

View File

@@ -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(

View File

@@ -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);

View File

@@ -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(())
}

View File

@@ -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))
}

View File

@@ -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());

View File

@@ -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(())