Redo the statically typed Func API (#2719)

* Redo the statically typed `Func` API

This commit reimplements the `Func` API with respect to statically typed
dispatch. Previously `Func` had a `getN` and `getN_async` family of
methods which were implemented for 0 to 16 parameters. The return value
of these functions was an `impl Fn(..)` closure with the appropriate
parameters and return values.

There are a number of downsides with this approach that have become
apparent over time:

* The addition of `*_async` doubled the API surface area (which is quite
  large here due to one-method-per-number-of-parameters).
* The [documentation of `Func`][old-docs] are quite verbose and feel
  "polluted" with all these getters, making it harder to understand the
  other methods that can be used to interact with a `Func`.
* These methods unconditionally pay the cost of returning an owned `impl
  Fn` with a `'static` lifetime. While cheap, this is still paying the
  cost for cloning the `Store` effectively and moving data into the
  closed-over environment.
* Storage of the return value into a struct, for example, always
  requires `Box`-ing the returned closure since it otherwise cannot be
  named.
* Recently I had the desire to implement an "unchecked" path for
  invoking wasm where you unsafely assert the type signature of a wasm
  function. Doing this with today's scheme would require doubling
  (again) the API surface area for both async and synchronous calls,
  further polluting the documentation.

The main benefit of the previous scheme is that by returning a `impl Fn`
it was quite easy and ergonomic to actually invoke the function. In
practice, though, examples would often have something akin to
`.get0::<()>()?()?` which is a lot of things to interpret all at once.
Note that `get0` means "0 parameters" yet a type parameter is passed.
There's also a double function invocation which looks like a lot of
characters all lined up in a row.

Overall, I think that the previous design is starting to show too many
cracks and deserves a rewrite. This commit is that rewrite.

The new design in this commit is to delete the `getN{,_async}` family of
functions and instead have a new API:

    impl Func {
        fn typed<P, R>(&self) -> Result<&Typed<P, R>>;
    }

    impl Typed<P, R> {
        fn call(&self, params: P) -> Result<R, Trap>;
        async fn call_async(&self, params: P) -> Result<R, Trap>;
    }

This should entirely replace the current scheme, albeit by slightly
losing ergonomics use cases. The idea behind the API is that the
existence of `Typed<P, R>` is a "proof" that the underlying function
takes `P` and returns `R`. The `Func::typed` method peforms a runtime
type-check to ensure that types all match up, and if successful you get
a `Typed` value. Otherwise an error is returned.

Once you have a `Typed` then, like `Func`, you can either `call` or
`call_async`. The difference with a `Typed`, however, is that the
params/results are statically known and hence these calls can be much
more efficient.

This is a much smaller API surface area from before and should greatly
simplify the `Func` documentation. There's still a problem where
`Func::wrapN_async` produces a lot of functions to document, but that's
now the sole offender. It's a nice benefit that the
statically-typed-async verisons are now expressed with an `async`
function rather than a function-returning-a-future which makes it both
more efficient and easier to understand.

The type `P` and `R` are intended to either be bare types (e.g. `i32`)
or tuples of any length (including 0). At this time `R` is only allowed
to be `()` or a bare `i32`-style type because multi-value is not
supported with a native ABI (yet). The `P`, however, can be any size of
tuples of parameters. This is also where some ergonomics are lost
because instead of `f(1, 2)` you now have to write `f.call((1, 2))`
(note the double-parens). Similarly `f()` becomes `f.call(())`.

Overall I feel that this is a better tradeoff than before. While not
universally better due to the loss in ergonomics I feel that this design
is much more flexible in terms of what you can do with the return value
and also understanding the API surface area (just less to take in).

[old-docs]: https://docs.rs/wasmtime/0.24.0/wasmtime/struct.Func.html#method.get0

* Rename Typed to TypedFunc

* Implement multi-value returns through `Func::typed`

* Fix examples in docs

* Fix some more errors

* More test fixes

* Rebasing and adding `get_typed_func`

* Updating tests

* Fix typo

* More doc tweaks

* Tweak visibility on `Func::invoke`

* Fix tests again
This commit is contained in:
Alex Crichton
2021-03-11 14:43:34 -06:00
committed by GitHub
parent 918c012d00
commit 2697a18d2f
35 changed files with 1013 additions and 1012 deletions

View File

@@ -19,12 +19,12 @@ fn run_smoke_test(func: &Func) {
run(future1).unwrap();
}
fn run_smoke_get0_test(func: &Func) {
let func = func.get0_async::<()>().unwrap();
run(func()).unwrap();
run(func()).unwrap();
let future1 = func();
let future2 = func();
fn run_smoke_typed_test(func: &Func) {
let func = func.typed::<(), ()>().unwrap();
run(func.call_async(())).unwrap();
run(func.call_async(())).unwrap();
let future1 = func.call_async(());
let future2 = func.call_async(());
run(future2).unwrap();
run(future1).unwrap();
}
@@ -39,13 +39,13 @@ fn smoke() {
move |_caller, _state, _params, _results| Box::new(async { Ok(()) }),
);
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| {
Box::new(async { Ok(()) })
});
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
}
#[test]
@@ -68,13 +68,13 @@ fn smoke_host_func() {
.get_host_func("", "first")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
let func = store
.get_host_func("", "second")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
}
#[test]
@@ -92,7 +92,7 @@ fn smoke_with_suspension() {
},
);
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| {
Box::new(async {
@@ -101,7 +101,7 @@ fn smoke_with_suspension() {
})
});
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
}
#[test]
@@ -132,13 +132,13 @@ fn smoke_host_func_with_suspension() {
.get_host_func("", "first")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
let func = store
.get_host_func("", "second")
.expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
}
#[test]
@@ -461,7 +461,7 @@ fn async_with_pooling_stacks() {
);
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
}
#[test]
@@ -492,7 +492,7 @@ fn async_host_func_with_pooling_stacks() {
let func = store.get_host_func("", "").expect("expected host function");
run_smoke_test(&func);
run_smoke_get0_test(&func);
run_smoke_typed_test(&func);
}
fn execute_across_threads<F: Future + 'static>(future: F) {

View File

@@ -41,8 +41,8 @@ mod tests {
)
"#;
fn invoke_export(instance: &Instance, func_name: &str) -> Result<Box<[Val]>> {
let ret = instance.get_func(func_name).unwrap().call(&[])?;
fn invoke_export(instance: &Instance, func_name: &str) -> Result<i32> {
let ret = instance.get_typed_func::<(), i32>(func_name)?.call(())?;
Ok(ret)
}
@@ -126,7 +126,7 @@ mod tests {
}
println!("calling hostcall_read...");
let result = invoke_export(&instance, "hostcall_read").unwrap();
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(123, result);
Ok(())
}
@@ -149,7 +149,7 @@ mod tests {
{
println!("calling read...");
let result = invoke_export(&instance, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(123, result);
}
{
@@ -167,23 +167,17 @@ mod tests {
// these invoke wasmtime_call_trampoline from callable.rs
{
let read_func = instance
.get_func("read")
.expect("expected a 'read' func in the module");
let read_func = instance.get_typed_func::<(), i32>("read")?;
println!("calling read...");
let result = read_func.call(&[]).expect("expected function not to trap");
assert_eq!(123i32, result[0].clone().unwrap_i32());
let result = read_func.call(()).expect("expected function not to trap");
assert_eq!(123i32, result);
}
{
let read_out_of_bounds_func = instance
.get_func("read_out_of_bounds")
.expect("expected a 'read_out_of_bounds' func in the module");
let read_out_of_bounds_func =
instance.get_typed_func::<(), i32>("read_out_of_bounds")?;
println!("calling read_out_of_bounds...");
let trap = read_out_of_bounds_func
.call(&[])
.unwrap_err()
.downcast::<Trap>()?;
let trap = read_out_of_bounds_func.call(()).unwrap_err();
assert!(trap
.to_string()
.contains("wasm trap: out of bounds memory access"));
@@ -233,7 +227,7 @@ mod tests {
println!("calling instance1.read...");
let result = invoke_export(&instance1, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(123, result);
assert_eq!(
instance1_handler_triggered.load(Ordering::SeqCst),
true,
@@ -274,7 +268,7 @@ mod tests {
println!("calling instance2.read...");
let result = invoke_export(&instance2, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(123, result);
assert_eq!(
instance2_handler_triggered.load(Ordering::SeqCst),
true,
@@ -316,7 +310,7 @@ mod tests {
println!("calling instance2.run");
let result = invoke_export(&instance2, "run")?;
assert_eq!(123, result[0].unwrap_i32());
assert_eq!(123, result);
Ok(())
}
}

View File

@@ -120,6 +120,16 @@ fn cross_store() -> anyhow::Result<()> {
assert!(s2_f.call(&[Val::FuncRef(Some(s1_f.clone()))]).is_err());
assert!(s2_f.call(&[Val::FuncRef(Some(s2_f.clone()))]).is_ok());
let s1_f_t = s1_f.typed::<Option<Func>, ()>()?;
let s2_f_t = s2_f.typed::<Option<Func>, ()>()?;
assert!(s1_f_t.call(None).is_ok());
assert!(s2_f_t.call(None).is_ok());
assert!(s1_f_t.call(Some(s1_f.clone())).is_ok());
assert!(s1_f_t.call(Some(s2_f.clone())).is_err());
assert!(s2_f_t.call(Some(s1_f.clone())).is_err());
assert!(s2_f_t.call(Some(s2_f.clone())).is_ok());
Ok(())
}

View File

@@ -243,41 +243,40 @@ fn trap_import() -> Result<()> {
fn get_from_wrapper() {
let store = Store::default();
let f = Func::wrap(&store, || {});
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<(), ()>().is_ok());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, i32>().is_err());
assert!(f.get2::<(), (), ()>().is_ok());
assert!(f.get2::<i32, i32, ()>().is_err());
assert!(f.get2::<i32, i32, i32>().is_err());
assert!(f.typed::<(), ()>().is_ok());
assert!(f.typed::<(), i32>().is_err());
assert!(f.typed::<(), ()>().is_ok());
assert!(f.typed::<i32, ()>().is_err());
assert!(f.typed::<i32, i32>().is_err());
assert!(f.typed::<(i32, i32), ()>().is_err());
assert!(f.typed::<(i32, i32), i32>().is_err());
let f = Func::wrap(&store, || -> i32 { loop {} });
assert!(f.get0::<i32>().is_ok());
assert!(f.typed::<(), i32>().is_ok());
let f = Func::wrap(&store, || -> f32 { loop {} });
assert!(f.get0::<f32>().is_ok());
assert!(f.typed::<(), f32>().is_ok());
let f = Func::wrap(&store, || -> f64 { loop {} });
assert!(f.get0::<f64>().is_ok());
assert!(f.typed::<(), f64>().is_ok());
let f = Func::wrap(&store, || -> Option<ExternRef> { loop {} });
assert!(f.get0::<Option<ExternRef>>().is_ok());
assert!(f.typed::<(), Option<ExternRef>>().is_ok());
let f = Func::wrap(&store, || -> Option<Func> { loop {} });
assert!(f.get0::<Option<Func>>().is_ok());
assert!(f.typed::<(), Option<Func>>().is_ok());
let f = Func::wrap(&store, |_: i32| {});
assert!(f.get1::<i32, ()>().is_ok());
assert!(f.get1::<i64, ()>().is_err());
assert!(f.get1::<f32, ()>().is_err());
assert!(f.get1::<f64, ()>().is_err());
assert!(f.typed::<i32, ()>().is_ok());
assert!(f.typed::<i64, ()>().is_err());
assert!(f.typed::<f32, ()>().is_err());
assert!(f.typed::<f64, ()>().is_err());
let f = Func::wrap(&store, |_: i64| {});
assert!(f.get1::<i64, ()>().is_ok());
assert!(f.typed::<i64, ()>().is_ok());
let f = Func::wrap(&store, |_: f32| {});
assert!(f.get1::<f32, ()>().is_ok());
assert!(f.typed::<f32, ()>().is_ok());
let f = Func::wrap(&store, |_: f64| {});
assert!(f.get1::<f64, ()>().is_ok());
assert!(f.typed::<f64, ()>().is_ok());
let f = Func::wrap(&store, |_: Option<ExternRef>| {});
assert!(f.get1::<Option<ExternRef>, ()>().is_ok());
assert!(f.typed::<Option<ExternRef>, ()>().is_ok());
let f = Func::wrap(&store, |_: Option<Func>| {});
assert!(f.get1::<Option<Func>, ()>().is_ok());
assert!(f.typed::<Option<Func>, ()>().is_ok());
}
#[test]
@@ -285,16 +284,16 @@ fn get_from_signature() {
let store = Store::default();
let ty = FuncType::new(None, None);
let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.typed::<(), ()>().is_ok());
assert!(f.typed::<(), i32>().is_err());
assert!(f.typed::<i32, ()>().is_err());
let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64));
let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_err());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok());
assert!(f.typed::<(), ()>().is_err());
assert!(f.typed::<(), i32>().is_err());
assert!(f.typed::<i32, ()>().is_err());
assert!(f.typed::<i32, f64>().is_ok());
}
#[test]
@@ -314,17 +313,17 @@ fn get_from_module() -> anyhow::Result<()> {
)?;
let instance = Instance::new(&store, &module, &[])?;
let f0 = instance.get_func("f0").unwrap();
assert!(f0.get0::<()>().is_ok());
assert!(f0.get0::<i32>().is_err());
assert!(f0.typed::<(), ()>().is_ok());
assert!(f0.typed::<(), i32>().is_err());
let f1 = instance.get_func("f1").unwrap();
assert!(f1.get0::<()>().is_err());
assert!(f1.get1::<i32, ()>().is_ok());
assert!(f1.get1::<i32, f32>().is_err());
assert!(f1.typed::<(), ()>().is_err());
assert!(f1.typed::<i32, ()>().is_ok());
assert!(f1.typed::<i32, f32>().is_err());
let f2 = instance.get_func("f2").unwrap();
assert!(f2.get0::<()>().is_err());
assert!(f2.get0::<i32>().is_ok());
assert!(f2.get1::<i32, ()>().is_err());
assert!(f2.get1::<i32, f32>().is_err());
assert!(f2.typed::<(), ()>().is_err());
assert!(f2.typed::<(), i32>().is_ok());
assert!(f2.typed::<i32, ()>().is_err());
assert!(f2.typed::<i32, f32>().is_err());
Ok(())
}
@@ -338,31 +337,32 @@ fn call_wrapped_func() -> Result<()> {
assert_eq!(d, 4.0);
});
f.call(&[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()])?;
f.get4::<i32, i64, f32, f64, ()>()?(1, 2, 3.0, 4.0)?;
f.typed::<(i32, i64, f32, f64), ()>()?
.call((1, 2, 3.0, 4.0))?;
let f = Func::wrap(&store, || 1i32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 1);
assert_eq!(f.get0::<i32>()?()?, 1);
assert_eq!(f.typed::<(), i32>()?.call(())?, 1);
let f = Func::wrap(&store, || 2i64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2);
assert_eq!(f.get0::<i64>()?()?, 2);
assert_eq!(f.typed::<(), i64>()?.call(())?, 2);
let f = Func::wrap(&store, || 3.0f32);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0);
assert_eq!(f.get0::<f32>()?()?, 3.0);
assert_eq!(f.typed::<(), f32>()?.call(())?, 3.0);
let f = Func::wrap(&store, || 4.0f64);
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0);
assert_eq!(f.typed::<(), f64>()?.call(())?, 4.0);
Ok(())
}
@@ -500,8 +500,8 @@ fn pass_cross_store_arg() -> anyhow::Result<()> {
// And using `.get` followed by a function call also fails with cross-Store
// arguments.
let f = store1_func.get1::<Option<Func>, ()>()?;
let result = f(Some(store2_func));
let f = store1_func.typed::<Option<Func>, ()>()?;
let result = f.call(Some(store2_func));
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cross-`Store`"));
@@ -549,3 +549,35 @@ fn trampolines_always_valid() -> anyhow::Result<()> {
func.call(&[])?;
Ok(())
}
#[test]
fn typed_multiple_results() -> anyhow::Result<()> {
let store = Store::default();
let module = Module::new(
store.engine(),
r#"
(module
(func (export "f0") (result i32 i64)
i32.const 0
i64.const 1)
(func (export "f1") (param i32 i32 i32) (result f32 f64)
f32.const 2
f64.const 3)
)
"#,
)?;
let instance = Instance::new(&store, &module, &[])?;
let f0 = instance.get_func("f0").unwrap();
assert!(f0.typed::<(), ()>().is_err());
assert!(f0.typed::<(), (i32, f32)>().is_err());
assert!(f0.typed::<(), i32>().is_err());
assert_eq!(f0.typed::<(), (i32, i64)>()?.call(())?, (0, 1));
let f1 = instance.get_func("f1").unwrap();
assert_eq!(
f1.typed::<(i32, i32, i32), (f32, f64)>()?.call((1, 2, 3))?,
(2., 3.)
);
Ok(())
}

View File

@@ -390,15 +390,15 @@ fn new_from_signature() -> Result<()> {
let store = Store::new(&engine);
let f = store.get_host_func("", "f1").expect("func defined");
assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.typed::<(), ()>().is_ok());
assert!(f.typed::<(), i32>().is_err());
assert!(f.typed::<i32, ()>().is_err());
let f = store.get_host_func("", "f2").expect("func defined");
assert!(f.get0::<()>().is_err());
assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok());
assert!(f.typed::<(), ()>().is_err());
assert!(f.typed::<(), i32>().is_err());
assert!(f.typed::<i32, ()>().is_err());
assert!(f.typed::<i32, f64>().is_ok());
Ok(())
}
@@ -427,31 +427,32 @@ fn call_wrapped_func() -> Result<()> {
let f = store.get_host_func("", "f1").expect("func defined");
f.call(&[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()])?;
f.get4::<i32, i64, f32, f64, ()>()?(1, 2, 3.0, 4.0)?;
f.typed::<(i32, i64, f32, f64), ()>()?
.call((1, 2, 3.0, 4.0))?;
let f = store.get_host_func("", "f2").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 1);
assert_eq!(f.get0::<i32>()?()?, 1);
assert_eq!(f.typed::<(), i32>()?.call(())?, 1);
let f = store.get_host_func("", "f3").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2);
assert_eq!(f.get0::<i64>()?()?, 2);
assert_eq!(f.typed::<(), i64>()?.call(())?, 2);
let f = store.get_host_func("", "f4").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0);
assert_eq!(f.get0::<f32>()?()?, 3.0);
assert_eq!(f.typed::<(), f32>()?.call(())?, 3.0);
let f = store.get_host_func("", "f5").expect("func defined");
let results = f.call(&[])?;
assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0);
assert_eq!(f.typed::<(), f64>()?.call(())?, 4.0);
Ok(())
}
@@ -597,9 +598,9 @@ fn wasi_imports_missing_context() -> Result<()> {
let linker = Linker::new(&store);
let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap().get0::<()>()?;
let start = instance.get_typed_func::<(), ()>("_start")?;
let trap = start().unwrap_err();
let trap = start.call(()).unwrap_err();
assert!(trap.to_string().contains("context is missing in the store"));
assert!(trap.i32_exit_status().is_none());
@@ -629,9 +630,8 @@ fn wasi_imports() -> Result<()> {
let linker = Linker::new(&store);
let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap().get0::<()>()?;
let trap = start().unwrap_err();
let start = instance.get_typed_func::<(), ()>("_start")?;
let trap = start.call(()).unwrap_err();
assert_eq!(trap.i32_exit_status(), Some(123));
Ok(())

View File

@@ -27,9 +27,9 @@ fn loops_interruptable() -> anyhow::Result<()> {
let store = interruptable_store();
let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?;
let instance = Instance::new(&store, &module, &[])?;
let iloop = instance.get_func("loop").unwrap().get0::<()>()?;
let iloop = instance.get_typed_func::<(), ()>("loop")?;
store.interrupt_handle()?.interrupt();
let trap = iloop().unwrap_err();
let trap = iloop.call(()).unwrap_err();
assert!(trap.to_string().contains("wasm trap: interrupt"));
Ok(())
}
@@ -40,9 +40,9 @@ fn functions_interruptable() -> anyhow::Result<()> {
let module = hugely_recursive_module(&store)?;
let func = Func::wrap(&store, || {});
let instance = Instance::new(&store, &module, &[func.into()])?;
let iloop = instance.get_func("loop").unwrap().get0::<()>()?;
let iloop = instance.get_typed_func::<(), ()>("loop")?;
store.interrupt_handle()?.interrupt();
let trap = iloop().unwrap_err();
let trap = iloop.call(()).unwrap_err();
assert!(
trap.to_string().contains("wasm trap: interrupt"),
"{}",
@@ -88,8 +88,8 @@ fn loop_interrupt_from_afar() -> anyhow::Result<()> {
// Enter the infinitely looping function and assert that our interrupt
// handle does indeed actually interrupt the function.
let iloop = instance.get_func("loop").unwrap().get0::<()>()?;
let trap = iloop().unwrap_err();
let iloop = instance.get_typed_func::<(), ()>("loop")?;
let trap = iloop.call(()).unwrap_err();
thread.join().unwrap();
assert!(
trap.to_string().contains("wasm trap: interrupt"),
@@ -124,8 +124,8 @@ fn function_interrupt_from_afar() -> anyhow::Result<()> {
// Enter the infinitely looping function and assert that our interrupt
// handle does indeed actually interrupt the function.
let iloop = instance.get_func("loop").unwrap().get0::<()>()?;
let trap = iloop().unwrap_err();
let iloop = instance.get_typed_func::<(), ()>("loop")?;
let trap = iloop.call(()).unwrap_err();
thread.join().unwrap();
assert!(
trap.to_string().contains("wasm trap: interrupt"),

View File

@@ -43,12 +43,8 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
];
let instance = Instance::new(&store, &module, &imports)?;
let func = instance.get_func("foo").unwrap();
let results = func.call(&[])?;
assert_eq!(results.len(), 1);
match results[0] {
Val::I32(n) => assert_eq!(n, 3),
_ => panic!("unexpected type of return"),
}
let func = instance.get_typed_func::<(), i32>("foo")?;
let result = func.call(())?;
assert_eq!(result, 3);
Ok(())
}

View File

@@ -96,8 +96,8 @@ fn function_interposition() -> Result<()> {
}
let instance = linker.instantiate(&module)?;
let func = instance.get_export("green").unwrap().into_func().unwrap();
let func = func.get0::<i32>()?;
assert_eq!(func()?, 112);
let func = func.typed::<(), i32>()?;
assert_eq!(func.call(())?, 112);
Ok(())
}
@@ -129,8 +129,8 @@ fn function_interposition_renamed() -> Result<()> {
}
let instance = linker.instantiate(&module)?;
let func = instance.get_func("export").unwrap();
let func = func.get0::<i32>()?;
assert_eq!(func()?, 112);
let func = func.typed::<(), i32>()?;
assert_eq!(func.call(())?, 112);
Ok(())
}
@@ -158,8 +158,8 @@ fn module_interposition() -> Result<()> {
}
let instance = linker.instantiate(&module)?;
let func = instance.get_export("export").unwrap().into_func().unwrap();
let func = func.get0::<i32>()?;
assert_eq!(func()?, 112);
let func = func.typed::<(), i32>()?;
assert_eq!(func.call(())?, 112);
Ok(())
}

View File

@@ -20,11 +20,8 @@ fn test_module_serialize_simple() -> Result<()> {
let store = Store::default();
let instance = deserialize_and_instantiate(&store, &buffer)?;
let run = instance
.get_func("run")
.ok_or(anyhow::format_err!("failed to find `run` function export"))?
.get0::<i32>()?;
let result = run()?;
let run = instance.get_typed_func::<(), i32>("run")?;
let result = run.call(())?;
assert_eq!(42, result);
Ok(())

View File

@@ -63,13 +63,13 @@ fn memory_limit() -> Result<()> {
{
let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?;
let f = instance.get_func("f").unwrap().get0::<i32>().unwrap();
let f = instance.get_typed_func::<(), i32>("f")?;
assert_eq!(f().expect("function should not trap"), 0);
assert_eq!(f().expect("function should not trap"), 1);
assert_eq!(f().expect("function should not trap"), 2);
assert_eq!(f().expect("function should not trap"), -1);
assert_eq!(f().expect("function should not trap"), -1);
assert_eq!(f.call(()).expect("function should not trap"), 0);
assert_eq!(f.call(()).expect("function should not trap"), 1);
assert_eq!(f.call(()).expect("function should not trap"), 2);
assert_eq!(f.call(()).expect("function should not trap"), -1);
assert_eq!(f.call(()).expect("function should not trap"), -1);
}
// Instantiate the module and grow the memory via the Wasmtime API
@@ -155,25 +155,25 @@ fn memory_guard_page_trap() -> Result<()> {
let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?;
let m = instance.get_memory("m").unwrap();
let f = instance.get_func("f").unwrap().get1::<i32, ()>().unwrap();
let f = instance.get_typed_func::<i32, ()>("f")?;
let trap = f(0).expect_err("function should trap");
let trap = f.call(0).expect_err("function should trap");
assert!(trap.to_string().contains("out of bounds"));
let trap = f(1).expect_err("function should trap");
let trap = f.call(1).expect_err("function should trap");
assert!(trap.to_string().contains("out of bounds"));
m.grow(1).expect("memory should grow");
f(0).expect("function should not trap");
f.call(0).expect("function should not trap");
let trap = f(65536).expect_err("function should trap");
let trap = f.call(65536).expect_err("function should trap");
assert!(trap.to_string().contains("out of bounds"));
let trap = f(65537).expect_err("function should trap");
let trap = f.call(65537).expect_err("function should trap");
assert!(trap.to_string().contains("out of bounds"));
m.grow(1).expect("memory should grow");
f(65536).expect("function should not trap");
f.call(65536).expect("function should not trap");
m.grow(1).expect_err("memory should be at the limit");
}
@@ -261,14 +261,14 @@ fn table_limit() -> Result<()> {
{
let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?;
let f = instance.get_func("f").unwrap().get0::<i32>().unwrap();
let f = instance.get_typed_func::<(), i32>("f")?;
for i in 0..TABLE_ELEMENTS {
assert_eq!(f().expect("function should not trap"), i as i32);
assert_eq!(f.call(()).expect("function should not trap"), i as i32);
}
assert_eq!(f().expect("function should not trap"), -1);
assert_eq!(f().expect("function should not trap"), -1);
assert_eq!(f.call(()).expect("function should not trap"), -1);
assert_eq!(f.call(()).expect("function should not trap"), -1);
}
// Instantiate the module and grow the table via the Wasmtime API

View File

@@ -24,11 +24,11 @@ fn host_always_has_some_stack() -> anyhow::Result<()> {
)?;
let func = Func::wrap(&store, test_host_stack);
let instance = Instance::new(&store, &module, &[func.into()])?;
let foo = instance.get_func("foo").unwrap().get0::<()>()?;
let foo = instance.get_typed_func::<(), ()>("foo")?;
// Make sure that our function traps and the trap says that the call stack
// has been exhausted.
let trap = foo().unwrap_err();
let trap = foo.call(()).unwrap_err();
assert!(
trap.to_string().contains("call stack exhausted"),
"{}",

View File

@@ -18,14 +18,9 @@ fn test_trap_return() -> Result<()> {
let hello_func = Func::new(&store, hello_type, |_, _, _| Err(Trap::new("test 123")));
let instance = Instance::new(&store, &module, &[hello_func.into()])?;
let run_func = instance.get_func("run").expect("expected function export");
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let run_func = instance.get_typed_func::<(), ()>("run")?;
let e = run_func.call(()).err().expect("error calling function");
assert!(e.to_string().contains("test 123"));
Ok(())
@@ -45,13 +40,9 @@ fn test_trap_trace() -> Result<()> {
let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[])?;
let run_func = instance.get_func("run").expect("expected function export");
let run_func = instance.get_typed_func::<(), ()>("run")?;
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let e = run_func.call(()).err().expect("error calling function");
let trace = e.trace();
assert_eq!(trace.len(), 2);
@@ -92,13 +83,9 @@ fn test_trap_trace_cb() -> Result<()> {
let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[fn_func.into()])?;
let run_func = instance.get_func("run").expect("expected function export");
let run_func = instance.get_typed_func::<(), ()>("run")?;
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let e = run_func.call(()).err().expect("error calling function");
let trace = e.trace();
assert_eq!(trace.len(), 2);
@@ -124,13 +111,9 @@ fn test_trap_stack_overflow() -> Result<()> {
let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[])?;
let run_func = instance.get_func("run").expect("expected function export");
let run_func = instance.get_typed_func::<(), ()>("run")?;
let e = run_func
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let e = run_func.call(()).err().expect("error calling function");
let trace = e.trace();
assert!(trace.len() >= 32);
@@ -160,9 +143,9 @@ fn trap_display_pretty() -> Result<()> {
let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[])?;
let run_func = instance.get_func("bar").expect("expected function export");
let run_func = instance.get_typed_func::<(), ()>("bar")?;
let e = run_func.call(&[]).err().expect("error calling function");
let e = run_func.call(()).err().expect("error calling function");
assert_eq!(
e.to_string(),
"\
@@ -204,9 +187,9 @@ fn trap_display_multi_module() -> Result<()> {
"#;
let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[bar])?;
let bar2 = instance.get_func("bar2").expect("expected function export");
let bar2 = instance.get_typed_func::<(), ()>("bar2")?;
let e = bar2.call(&[]).err().expect("error calling function");
let e = bar2.call(()).err().expect("error calling function");
assert_eq!(
e.to_string(),
"\
@@ -278,16 +261,13 @@ fn rust_panic_import() -> Result<()> {
Func::wrap(&store, || panic!("this is another panic")).into(),
],
)?;
let func = instance.get_func("foo").unwrap();
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(func.call(&[]));
}))
.unwrap_err();
let func = instance.get_typed_func::<(), ()>("foo")?;
let err = panic::catch_unwind(AssertUnwindSafe(|| drop(func.call(())))).unwrap_err();
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic"));
let func = instance.get_func("bar").unwrap();
let func = instance.get_typed_func::<(), ()>("bar")?;
let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(func.call(&[]));
drop(func.call(()));
}))
.unwrap_err();
assert_eq!(
@@ -437,14 +417,14 @@ fn present_after_module_drop() -> Result<()> {
let store = Store::default();
let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?;
let instance = Instance::new(&store, &module, &[])?;
let func = instance.get_func("foo").unwrap();
let func = instance.get_typed_func::<(), ()>("foo")?;
println!("asserting before we drop modules");
assert_trap(func.call(&[]).unwrap_err().downcast()?);
assert_trap(func.call(()).unwrap_err());
drop((instance, module));
println!("asserting after drop");
assert_trap(func.call(&[]).unwrap_err().downcast()?);
assert_trap(func.call(()).unwrap_err());
return Ok(());
fn assert_trap(t: Trap) {

View File

@@ -15,7 +15,7 @@ fn use_func_after_drop() -> Result<()> {
table.set(0, func.into())?;
}
let func = table.get(0).unwrap().funcref().unwrap().unwrap().clone();
let func = func.get0::<()>()?;
func()?;
let func = func.typed::<(), ()>()?;
func.call(())?;
Ok(())
}