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

@@ -301,9 +301,8 @@ impl BenchState {
.as_ref() .as_ref()
.expect("instantiate the module before executing it"); .expect("instantiate the module before executing it");
let start_func = instance.get_func("_start").expect("a _start function"); let start_func = instance.get_typed_func::<(), ()>("_start")?;
let runnable_func = start_func.get0::<()>()?; match start_func.call(()) {
match runnable_func() {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(trap) => { Err(trap) => {
// Since _start will likely return by using the system `exit` call, we must // Since _start will likely return by using the system `exit` call, we must

View File

@@ -57,9 +57,8 @@ pub fn instantiate(data: &[u8], bin_name: &str, workspace: Option<&Path>) -> any
let module = Module::new(store.engine(), &data).context("failed to create wasm module")?; let module = Module::new(store.engine(), &data).context("failed to create wasm module")?;
let instance = linker.instantiate(&module)?; let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap(); let start = instance.get_typed_func::<(), ()>("_start")?;
let with_type = start.get0::<()>()?; start.call(()).map_err(anyhow::Error::from)
with_type().map_err(anyhow::Error::from)
}; };
match r { match r {
@@ -112,9 +111,8 @@ pub fn instantiate_inherit_stdio(
let module = Module::new(store.engine(), &data).context("failed to create wasm module")?; let module = Module::new(store.engine(), &data).context("failed to create wasm module")?;
let instance = linker.instantiate(&module)?; let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap(); let start = instance.get_typed_func::<(), ()>("_start")?;
let with_type = start.get0::<()>()?; start.call(()).map_err(anyhow::Error::from)
with_type().map_err(anyhow::Error::from)
}; };
match r { match r {

View File

@@ -350,8 +350,8 @@ macro_rules! generate_wrap_async_host_func {
debug_assert!(store.async_support()); debug_assert!(store.async_support());
let mut future = Pin::from(func(caller, $($args),*)); let mut future = Pin::from(func(caller, $($args),*));
match store.block_on(future.as_mut()) { match store.block_on(future.as_mut()) {
Ok(ret) => ret.into_result(), Ok(ret) => ret.into_fallible(),
Err(e) => Err(e), Err(e) => R::fallible_from_trap(e),
} }
}) })
); );

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,488 @@
use super::invoke_wasm_and_catch_traps;
use crate::{ExternRef, Func, Store, Trap, ValType};
use anyhow::{bail, Result};
use std::marker;
use std::mem::{self, MaybeUninit};
use std::ptr;
use wasmtime_runtime::{VMContext, VMFunctionBody, VMTrampoline};
/// A statically typed WebAssembly function.
///
/// Values of this type represent statically type-checked WebAssembly functions.
/// The function within a [`TypedFunc`] is statically known to have `Params` as its
/// parameters and `Results` as its results.
///
/// This structure is created via [`Func::typed`] or [`Func::typed_unchecked`].
/// For more documentation about this see those methods.
#[repr(transparent)]
pub struct TypedFunc<Params, Results> {
_a: marker::PhantomData<fn(Params) -> Results>,
func: Func,
}
impl<Params, Results> Clone for TypedFunc<Params, Results> {
fn clone(&self) -> TypedFunc<Params, Results> {
TypedFunc {
_a: marker::PhantomData,
func: self.func.clone(),
}
}
}
impl<Params, Results> TypedFunc<Params, Results>
where
Params: WasmParams,
Results: WasmResults,
{
/// Returns the underlying [`Func`] that this is wrapping, losing the static
/// type information in the process.
pub fn func(&self) -> &Func {
&self.func
}
/// Invokes this WebAssembly function with the specified parameters.
///
/// Returns either the results of the call, or a [`Trap`] if one happened.
///
/// For more information, see the [`Func::typed`] and [`Func::call`]
/// documentation.
///
/// # Panics
///
/// This function will panic if it is called when the underlying [`Func`] is
/// connected to an asynchronous store.
pub fn call(&self, params: Params) -> Result<Results, Trap> {
assert!(
!self.func.store().async_support(),
"must use `call_async` with async stores"
);
unsafe { self._call(params) }
}
/// Invokes this WebAssembly function with the specified parameters.
///
/// Returns either the results of the call, or a [`Trap`] if one happened.
///
/// For more information, see the [`Func::typed`] and [`Func::call_async`]
/// documentation.
///
/// # Panics
///
/// This function will panic if it is called when the underlying [`Func`] is
/// connected to a synchronous store.
#[cfg(feature = "async")]
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
pub async fn call_async(&self, params: Params) -> Result<Results, Trap> {
assert!(
self.func.store().async_support(),
"must use `call` with non-async stores"
);
self.func
.store()
.on_fiber(|| unsafe { self._call(params) })
.await?
}
unsafe fn _call(&self, params: Params) -> Result<Results, Trap> {
// Validate that all runtime values flowing into this store indeed
// belong within this store, otherwise it would be unsafe for store
// values to cross each other.
if !params.compatible_with_store(&self.func.instance.store) {
return Err(Trap::new(
"attempt to pass cross-`Store` value to Wasm as function argument",
));
}
let anyfunc = self.func.export.anyfunc.as_ref();
let trampoline = self.func.trampoline;
let params = MaybeUninit::new(params);
let mut ret = MaybeUninit::uninit();
let mut called = false;
let mut returned = false;
let result = invoke_wasm_and_catch_traps(&self.func.instance.store, || {
called = true;
let params = ptr::read(params.as_ptr());
let result = params.invoke::<Results>(
&self.func.instance.store,
trampoline,
anyfunc.func_ptr.as_ptr(),
anyfunc.vmctx,
ptr::null_mut(),
);
ptr::write(ret.as_mut_ptr(), result);
returned = true
});
// This can happen if we early-trap due to interrupts or other
// pre-flight checks, so we need to be sure the parameters are at least
// dropped at some point.
if !called {
drop(params.assume_init());
}
debug_assert_eq!(result.is_ok(), returned);
result?;
Ok(ret.assume_init())
}
}
/// A trait implemented for types which can be arguments and results for
/// closures passed to [`Func::wrap`] as well as parameters to [`Func::typed`].
///
/// This trait should not be implemented by user types. This trait may change at
/// any time internally. The types which implement this trait, however, are
/// stable over time.
///
/// For more information see [`Func::wrap`] and [`Func::typed`]
pub unsafe trait WasmTy {
#[doc(hidden)]
type Abi: Copy;
#[doc(hidden)]
#[inline]
fn typecheck(ty: crate::ValType) -> Result<()> {
if ty == Self::valtype() {
Ok(())
} else {
bail!("expected {} found {}", Self::valtype(), ty)
}
}
#[doc(hidden)]
fn valtype() -> ValType;
#[doc(hidden)]
fn compatible_with_store(&self, store: &Store) -> bool;
#[doc(hidden)]
fn into_abi(self, store: &Store) -> Self::Abi;
#[doc(hidden)]
unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self;
}
macro_rules! primitives {
($($primitive:ident => $ty:ident)*) => ($(
unsafe impl WasmTy for $primitive {
type Abi = $primitive;
#[inline]
fn valtype() -> ValType {
ValType::$ty
}
#[inline]
fn compatible_with_store(&self, _: &Store) -> bool {
true
}
#[inline]
fn into_abi(self, _store: &Store) -> Self::Abi {
self
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self {
abi
}
}
)*)
}
primitives! {
i32 => I32
u32 => I32
i64 => I64
u64 => I64
f32 => F32
f64 => F64
}
unsafe impl WasmTy for Option<ExternRef> {
type Abi = *mut u8;
#[inline]
fn valtype() -> ValType {
ValType::ExternRef
}
#[inline]
fn compatible_with_store(&self, _store: &Store) -> bool {
true
}
#[inline]
fn into_abi(self, store: &Store) -> Self::Abi {
if let Some(x) = self {
let abi = x.inner.as_raw();
unsafe {
store
.externref_activations_table()
.insert_with_gc(x.inner, store.stack_map_registry());
}
abi
} else {
ptr::null_mut()
}
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self {
if abi.is_null() {
None
} else {
Some(ExternRef {
inner: wasmtime_runtime::VMExternRef::clone_from_raw(abi),
})
}
}
}
unsafe impl WasmTy for Option<Func> {
type Abi = *mut wasmtime_runtime::VMCallerCheckedAnyfunc;
#[inline]
fn valtype() -> ValType {
ValType::FuncRef
}
#[inline]
fn compatible_with_store<'a>(&self, store: &Store) -> bool {
if let Some(f) = self {
Store::same(&store, f.store())
} else {
true
}
}
#[inline]
fn into_abi(self, _store: &Store) -> Self::Abi {
if let Some(f) = self {
f.caller_checked_anyfunc().as_ptr()
} else {
ptr::null_mut()
}
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self {
Func::from_caller_checked_anyfunc(&store, abi)
}
}
/// A trait used for [`Func::typed`] and with [`TypedFunc`] to represent the set of
/// parameters for wasm functions.
///
/// This is implemented for bare types that can be passed to wasm as well as
/// tuples of those types.
pub unsafe trait WasmParams {
#[doc(hidden)]
fn typecheck(params: impl ExactSizeIterator<Item = crate::ValType>) -> Result<()>;
#[doc(hidden)]
fn compatible_with_store(&self, store: &Store) -> bool;
#[doc(hidden)]
unsafe fn invoke<R: WasmResults>(
self,
store: &Store,
trampoline: VMTrampoline,
func: *const VMFunctionBody,
vmctx1: *mut VMContext,
vmctx2: *mut VMContext,
) -> R;
}
// Forward an impl from `T` to `(T,)` for convenience if there's only one
// parameter.
unsafe impl<T> WasmParams for T
where
T: WasmTy,
{
fn typecheck(params: impl ExactSizeIterator<Item = crate::ValType>) -> Result<()> {
<(T,)>::typecheck(params)
}
fn compatible_with_store(&self, store: &Store) -> bool {
<T as WasmTy>::compatible_with_store(self, store)
}
unsafe fn invoke<R: WasmResults>(
self,
store: &Store,
trampoline: VMTrampoline,
func: *const VMFunctionBody,
vmctx1: *mut VMContext,
vmctx2: *mut VMContext,
) -> R {
<(T,)>::invoke((self,), store, trampoline, func, vmctx1, vmctx2)
}
}
macro_rules! impl_wasm_params {
($n:tt $($t:ident)*) => {
#[allow(non_snake_case)]
unsafe impl<$($t: WasmTy,)*> WasmParams for ($($t,)*) {
fn typecheck(mut params: impl ExactSizeIterator<Item = crate::ValType>) -> Result<()> {
let mut _n = 0;
$(
match params.next() {
Some(t) => $t::typecheck(t)?,
None => bail!("expected {} types, found {}", $n, _n),
}
_n += 1;
)*
match params.next() {
None => Ok(()),
Some(_) => bail!("expected {} types, found {}", $n, params.len() + _n),
}
}
fn compatible_with_store(&self, _store: &Store) -> bool {
let ($($t,)*) = self;
$($t.compatible_with_store(_store)&&)* true
}
unsafe fn invoke<R: WasmResults>(
self,
store: &Store,
trampoline: VMTrampoline,
func: *const VMFunctionBody,
vmctx1: *mut VMContext,
vmctx2: *mut VMContext,
) -> R {
// Some signatures can go directly into JIT code which uses the
// default platform ABI, but basically only those without
// multiple return values. With multiple return values we can't
// natively in Rust call such a function because there's no way
// to model it (yet).
//
// To work around that we use the trampoline which passes
// arguments/values via the stack which allows us to match the
// expected ABI. Note that this branch, using the trampoline,
// is slower as a result and has an extra indirect function
// call as well. In the future if this is a problem we should
// consider updating JIT code to use an ABI we can call from
// Rust itself.
if R::uses_trampoline() {
R::with_space(|space1| {
// Figure out whether the parameters or the results
// require more space, and use the bigger one as where
// to store arguments and load return values from.
let mut space2 = [0; $n];
let space = if space1.len() < space2.len() {
space2.as_mut_ptr()
} else {
space1.as_mut_ptr()
};
// ... store the ABI for all values into our storage
// area...
let ($($t,)*) = self;
let mut _n = 0;
$(
*space.add(_n).cast::<$t::Abi>() = $t.into_abi(store);
_n += 1;
)*
// ... make the indirect call through the trampoline
// which will read from `space` and also write all the
// results to `space`...
trampoline(vmctx1, vmctx2, func, space);
// ... and then we can decode all the return values
// from `space`.
R::from_storage(space, store)
})
} else {
let fnptr = mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(
*mut VMContext,
*mut VMContext,
$($t::Abi,)*
) -> R::Abi,
>(func);
let ($($t,)*) = self;
R::from_abi(fnptr(vmctx1, vmctx2, $($t.into_abi(store),)*), store)
}
}
}
};
}
for_each_function_signature!(impl_wasm_params);
/// A trait used for [`Func::typed`] and with [`TypedFunc`] to represent the set of
/// results for wasm functions.
///
/// This is currently only implemented for `()` and for bare types that can be
/// returned. This is not yet implemented for tuples because a multi-value
/// `TypedFunc` is not currently supported.
pub unsafe trait WasmResults: WasmParams {
#[doc(hidden)]
type Abi;
#[doc(hidden)]
unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self;
#[doc(hidden)]
fn uses_trampoline() -> bool;
// Provides a stack-allocated array with enough space to store all these
// result values.
//
// It'd be nice if we didn't have to have this API and could do something
// with const-generics (or something like that), but I couldn't figure it
// out. If a future Rust explorer is able to get something like `const LEN:
// usize` working that'd be great!
#[doc(hidden)]
fn with_space<R>(_: impl FnOnce(&mut [u128]) -> R) -> R;
#[doc(hidden)]
unsafe fn from_storage(ptr: *const u128, store: &Store) -> Self;
}
unsafe impl<T: WasmTy> WasmResults for T {
type Abi = <(T,) as WasmResults>::Abi;
unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self {
<(T,) as WasmResults>::from_abi(abi, store).0
}
fn uses_trampoline() -> bool {
<(T,) as WasmResults>::uses_trampoline()
}
fn with_space<R>(f: impl FnOnce(&mut [u128]) -> R) -> R {
<(T,) as WasmResults>::with_space(f)
}
unsafe fn from_storage(ptr: *const u128, store: &Store) -> Self {
<(T,) as WasmResults>::from_storage(ptr, store).0
}
}
#[doc(hidden)]
pub enum Void {}
macro_rules! impl_wasm_results {
($n:tt $($t:ident)*) => {
#[allow(non_snake_case, unused_variables)]
unsafe impl<$($t: WasmTy,)*> WasmResults for ($($t,)*) {
type Abi = impl_wasm_results!(@abi $n $($t)*);
unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self {
impl_wasm_results!(@from_abi abi store $n $($t)*)
}
fn uses_trampoline() -> bool {
$n > 1
}
fn with_space<R>(f: impl FnOnce(&mut [u128]) -> R) -> R {
f(&mut [0; $n])
}
unsafe fn from_storage(ptr: *const u128, store: &Store) -> Self {
let mut _n = 0;
$(
let $t = $t::from_abi(*ptr.add(_n).cast::<$t::Abi>(), store);
_n += 1;
)*
($($t,)*)
}
}
};
// 0/1 return values we can use natively, everything else isn't expressible
// and won't be used so define the abi type to Void.
(@abi 0) => (());
(@abi 1 $t:ident) => ($t::Abi);
(@abi $($t:tt)*) => (Void);
(@from_abi $abi:ident $store:ident 0) => (());
(@from_abi $abi:ident $store:ident 1 $t:ident) => (($t::from_abi($abi, $store),));
(@from_abi $abi:ident $store:ident $($t:tt)*) => ({
debug_assert!(false);
match $abi {}
});
}
for_each_function_signature!(impl_wasm_results);

View File

@@ -2,8 +2,9 @@ use crate::trampoline::StoreInstanceHandle;
use crate::types::matching; use crate::types::matching;
use crate::{ use crate::{
Engine, Export, Extern, Func, Global, InstanceType, Memory, Module, Store, Table, Trap, Engine, Export, Extern, Func, Global, InstanceType, Memory, Module, Store, Table, Trap,
TypedFunc,
}; };
use anyhow::{bail, Context, Error, Result}; use anyhow::{anyhow, bail, Context, Error, Result};
use std::mem; use std::mem;
use std::rc::Rc; use std::rc::Rc;
use wasmtime_environ::entity::PrimaryMap; use wasmtime_environ::entity::PrimaryMap;
@@ -216,6 +217,25 @@ impl Instance {
self.get_export(name)?.into_func() self.get_export(name)?.into_func()
} }
/// Looks up an exported [`Func`] value by name and with its type.
///
/// This function is a convenience wrapper over [`Instance::get_func`] and
/// [`Func::typed`]. For more information see the linked documentation.
///
/// Returns an error if `name` isn't a function export or if the export's
/// type did not match `Params` or `Results`
pub fn get_typed_func<Params, Results>(&self, name: &str) -> Result<TypedFunc<Params, Results>>
where
Params: crate::WasmParams,
Results: crate::WasmResults,
{
let f = self
.get_export(name)
.and_then(|f| f.into_func())
.ok_or_else(|| anyhow!("failed to find function export `{}`", name))?;
Ok(f.typed::<Params, Results>()?.clone())
}
/// Looks up an exported [`Table`] value by name. /// Looks up an exported [`Table`] value by name.
/// ///
/// Returns `None` if there was no export named `name`, or if there was but /// Returns `None` if there was no export named `name`, or if there was but

View File

@@ -47,15 +47,12 @@
//! //!
//! // Instantiation of a module requires specifying its imports and then //! // Instantiation of a module requires specifying its imports and then
//! // afterwards we can fetch exports by name, as well as asserting the //! // afterwards we can fetch exports by name, as well as asserting the
//! // type signature of the function with `get0`. //! // type signature of the function with `get_typed_func`.
//! let instance = Instance::new(&store, &module, &[host_hello.into()])?; //! let instance = Instance::new(&store, &module, &[host_hello.into()])?;
//! let hello = instance //! let hello = instance.get_typed_func::<(), ()>("hello")?;
//! .get_func("hello")
//! .ok_or(anyhow::format_err!("failed to find `hello` function export"))?
//! .get0::<()>()?;
//! //!
//! // And finally we can call the wasm as if it were a Rust function! //! // And finally we can call the wasm as if it were a Rust function!
//! hello()?; //! hello.call(())?;
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@@ -262,8 +259,8 @@
//! "#, //! "#,
//! )?; //! )?;
//! let instance = Instance::new(&store, &module, &[log_str.into()])?; //! let instance = Instance::new(&store, &module, &[log_str.into()])?;
//! let foo = instance.get_func("foo").unwrap().get0::<()>()?; //! let foo = instance.get_typed_func::<(), ()>("foo")?;
//! foo()?; //! foo.call(())?;
//! # Ok(()) //! # Ok(())
//! # } //! # }
//! ``` //! ```

View File

@@ -355,10 +355,10 @@ impl Linker {
/// "#; /// "#;
/// let module = Module::new(store.engine(), wat)?; /// let module = Module::new(store.engine(), wat)?;
/// linker.module("commander", &module)?; /// linker.module("commander", &module)?;
/// let run = linker.get_default("")?.get0::<()>()?; /// let run = linker.get_default("")?.typed::<(), ()>()?.clone();
/// run()?; /// run.call(())?;
/// run()?; /// run.call(())?;
/// run()?; /// run.call(())?;
/// ///
/// let wat = r#" /// let wat = r#"
/// (module /// (module
@@ -374,7 +374,8 @@ impl Linker {
/// "#; /// "#;
/// let module = Module::new(store.engine(), wat)?; /// let module = Module::new(store.engine(), wat)?;
/// linker.module("", &module)?; /// linker.module("", &module)?;
/// let count = linker.get_one_by_name("", Some("run"))?.into_func().unwrap().get0::<i32>()?()?; /// let run = linker.get_one_by_name("", Some("run"))?.into_func().unwrap();
/// let count = run.typed::<(), i32>()?.call(())?;
/// assert_eq!(count, 0, "a Command should get a fresh instance on each invocation"); /// assert_eq!(count, 0, "a Command should get a fresh instance on each invocation");
/// ///
/// # Ok(()) /// # Ok(())
@@ -388,8 +389,8 @@ impl Linker {
if let Some(export) = instance.get_export("_initialize") { if let Some(export) = instance.get_export("_initialize") {
if let Extern::Func(func) = export { if let Extern::Func(func) = export {
func.get0::<()>() func.typed::<(), ()>()
.and_then(|f| f().map_err(Into::into)) .and_then(|f| f.call(()).map_err(Into::into))
.context("calling the Reactor initialization function")?; .context("calling the Reactor initialization function")?;
} }
} }

View File

@@ -479,10 +479,7 @@ impl Store {
/// (func (export "run") (loop br 0)) /// (func (export "run") (loop br 0))
/// "#)?; /// "#)?;
/// let instance = Instance::new(&store, &module, &[])?; /// let instance = Instance::new(&store, &module, &[])?;
/// let run = instance /// let run = instance.get_typed_func::<(), ()>("run")?;
/// .get_func("run")
/// .ok_or(anyhow::format_err!("failed to find `run` function export"))?
/// .get0::<()>()?;
/// ///
/// // Spin up a thread to send us an interrupt in a second /// // Spin up a thread to send us an interrupt in a second
/// std::thread::spawn(move || { /// std::thread::spawn(move || {
@@ -490,7 +487,7 @@ impl Store {
/// interrupt_handle.interrupt(); /// interrupt_handle.interrupt();
/// }); /// });
/// ///
/// let trap = run().unwrap_err(); /// let trap = run.call(()).unwrap_err();
/// assert!(trap.to_string().contains("wasm trap: interrupt")); /// assert!(trap.to_string().contains("wasm trap: interrupt"));
/// # Ok(()) /// # Ok(())
/// # } /// # }

View File

@@ -79,13 +79,13 @@ fn main() -> Result<(), Box<dyn Error>> {
.expect("`answer` was not an exported function"); .expect("`answer` was not an exported function");
// There's a few ways we can call the `answer` `Func` value. The easiest // There's a few ways we can call the `answer` `Func` value. The easiest
// is to statically assert its signature with `get0` (in this case asserting // is to statically assert its signature with `typed` (in this case
// it takes no arguments and returns one i32) and then call it. // asserting it takes no arguments and returns one i32) and then call it.
let answer = answer.get0::<i32>()?; let answer = answer.typed::<(), i32>()?;
// And finally we can call our function! Note that the error propagation // And finally we can call our function! Note that the error propagation
// with `?` is done to handle the case where the wasm function traps. // with `?` is done to handle the case where the wasm function traps.
let result = answer()?; let result = answer.call(())?;
println!("Answer: {:?}", result); println!("Answer: {:?}", result);
Ok(()) Ok(())
} }
@@ -164,13 +164,8 @@ fn main() -> Result<(), Box<dyn Error>> {
// entry in the slice must line up with the imports in the module. // entry in the slice must line up with the imports in the module.
let instance = Instance::new(&store, &module, &[log.into(), double.into()])?; let instance = Instance::new(&store, &module, &[log.into(), double.into()])?;
let run = instance let run = instance.get_typed_func::<(), ()>("run")?;
.get_func("run") Ok(run.call(())?)
.expect("`run` was not an exported function");
let run = run.get0::<()>()?;
Ok(run()?)
} }
``` ```

View File

@@ -48,9 +48,8 @@ let wat = r#"
"#; "#;
let module = Module::new(&engine, wat)?; let module = Module::new(&engine, wat)?;
let instance = Instance::new(&store, &module, &[])?; let instance = Instance::new(&store, &module, &[])?;
let add = instance.get_func("add").unwrap(); let add = instance.get_typed_func::<(i32, i32), i32>("add")?;
let add = add.get2::<i32, i32, i32>()?; println!("1 + 2 = {}", add.call((1, 2))?);
println!("1 + 2 = {}", add(1, 2)?);
# Ok(()) # Ok(())
# } # }
``` ```

View File

@@ -40,9 +40,8 @@ fn main() -> Result<()> {
assert!(global_val.ptr_eq(&externref)); assert!(global_val.ptr_eq(&externref));
println!("Calling `externref` func..."); println!("Calling `externref` func...");
let func = instance.get_func("func").unwrap(); let func = instance.get_typed_func::<Option<ExternRef>, Option<ExternRef>>("func")?;
let func = func.get1::<Option<ExternRef>, Option<ExternRef>>()?; let ret = func.call(Some(externref.clone()))?;
let ret = func(Some(externref.clone()))?;
assert!(ret.is_some()); assert!(ret.is_some());
assert!(ret.unwrap().ptr_eq(&externref)); assert!(ret.unwrap().ptr_eq(&externref));

View File

@@ -21,10 +21,7 @@ fn main() -> Result<()> {
let instance = Instance::new(&store, &module, &[])?; let instance = Instance::new(&store, &module, &[])?;
// Invoke `fib` export // Invoke `fib` export
let fib = instance let fib = instance.get_typed_func::<i32, i32>("fib")?;
.get_func("fib") println!("fib(6) = {}", fib.call(6)?);
.ok_or(anyhow::format_err!("failed to find `fib` function export"))?
.get1::<i32, i32>()?;
println!("fib(6) = {}", fib(6)?);
Ok(()) Ok(())
} }

View File

@@ -15,11 +15,8 @@ fn main() -> Result<()> {
let instance = Instance::new(&store, &module, &[])?; let instance = Instance::new(&store, &module, &[])?;
// Invoke `gcd` export // Invoke `gcd` export
let gcd = instance let gcd = instance.get_typed_func::<(i32, i32), i32>("gcd")?;
.get_func("gcd")
.ok_or(anyhow::format_err!("failed to find `gcd` function export"))?
.get2::<i32, i32, i32>()?;
println!("gcd(6, 27) = {}", gcd(6, 27)?); println!("gcd(6, 27) = {}", gcd.call((6, 27))?);
Ok(()) Ok(())
} }

View File

@@ -34,14 +34,11 @@ fn main() -> Result<()> {
// Next we poke around a bit to extract the `run` function from the module. // Next we poke around a bit to extract the `run` function from the module.
println!("Extracting export..."); println!("Extracting export...");
let run = instance let run = instance.get_typed_func::<(), ()>("run")?;
.get_func("run")
.ok_or(anyhow::format_err!("failed to find `run` function export"))?
.get0::<()>()?;
// And last but not least we can call it! // And last but not least we can call it!
println!("Calling export..."); println!("Calling export...");
run()?; run.call(())?;
println!("Done."); println!("Done.");
Ok(()) Ok(())

View File

@@ -16,10 +16,7 @@ fn main() -> Result<()> {
// Compile and instantiate a small example with an infinite loop. // Compile and instantiate a small example with an infinite loop.
let module = Module::from_file(&engine, "examples/interrupt.wat")?; let module = Module::from_file(&engine, "examples/interrupt.wat")?;
let instance = Instance::new(&store, &module, &[])?; let instance = Instance::new(&store, &module, &[])?;
let run = instance let run = instance.get_typed_func::<(), ()>("run")?;
.get_func("run")
.ok_or(anyhow::format_err!("failed to find `run` function export"))?
.get0::<()>()?;
// Spin up a thread to send us an interrupt in a second // Spin up a thread to send us an interrupt in a second
std::thread::spawn(move || { std::thread::spawn(move || {
@@ -29,7 +26,7 @@ fn main() -> Result<()> {
}); });
println!("Entering infinite loop ..."); println!("Entering infinite loop ...");
let trap = run().unwrap_err(); let trap = run.call(()).unwrap_err();
println!("trap received..."); println!("trap received...");
assert!(trap.to_string().contains("wasm trap: interrupt")); assert!(trap.to_string().contains("wasm trap: interrupt"));

View File

@@ -34,8 +34,7 @@ fn main() -> Result<()> {
// And with that we can perform the final link and the execute the module. // And with that we can perform the final link and the execute the module.
let linking1 = linker.instantiate(&linking1)?; let linking1 = linker.instantiate(&linking1)?;
let run = linking1.get_func("run").unwrap(); let run = linking1.get_typed_func::<(), ()>("run")?;
let run = run.get0::<()>()?; run.call(())?;
run()?;
Ok(()) Ok(())
} }

View File

@@ -20,18 +20,9 @@ fn main() -> Result<()> {
let memory = instance let memory = instance
.get_memory("memory") .get_memory("memory")
.ok_or(anyhow::format_err!("failed to find `memory` export"))?; .ok_or(anyhow::format_err!("failed to find `memory` export"))?;
let size = instance let size = instance.get_typed_func::<(), i32>("size")?;
.get_func("size") let load = instance.get_typed_func::<i32, i32>("load")?;
.ok_or(anyhow::format_err!("failed to find `size` export"))? let store = instance.get_typed_func::<(i32, i32), ()>("store")?;
.get0::<i32>()?;
let load = instance
.get_func("load")
.ok_or(anyhow::format_err!("failed to find `load` export"))?
.get1::<i32, i32>()?;
let store = instance
.get_func("store")
.ok_or(anyhow::format_err!("failed to find `store` export"))?
.get2::<i32, i32, ()>()?;
// Note that these memory reads are *unsafe* due to unknown knowledge about // Note that these memory reads are *unsafe* due to unknown knowledge about
// aliasing with wasm memory. For more information about the safety // aliasing with wasm memory. For more information about the safety
@@ -46,27 +37,27 @@ fn main() -> Result<()> {
assert_eq!(memory.data_unchecked_mut()[0x1003], 4); assert_eq!(memory.data_unchecked_mut()[0x1003], 4);
} }
assert_eq!(size()?, 2); assert_eq!(size.call(())?, 2);
assert_eq!(load(0)?, 0); assert_eq!(load.call(0)?, 0);
assert_eq!(load(0x1000)?, 1); assert_eq!(load.call(0x1000)?, 1);
assert_eq!(load(0x1003)?, 4); assert_eq!(load.call(0x1003)?, 4);
assert_eq!(load(0x1ffff)?, 0); assert_eq!(load.call(0x1ffff)?, 0);
assert!(load(0x20000).is_err()); // out of bounds trap assert!(load.call(0x20000).is_err()); // out of bounds trap
println!("Mutating memory..."); println!("Mutating memory...");
unsafe { unsafe {
memory.data_unchecked_mut()[0x1003] = 5; memory.data_unchecked_mut()[0x1003] = 5;
} }
store(0x1002, 6)?; store.call((0x1002, 6))?;
assert!(store(0x20000, 0).is_err()); // out of bounds trap assert!(store.call((0x20000, 0)).is_err()); // out of bounds trap
unsafe { unsafe {
assert_eq!(memory.data_unchecked_mut()[0x1002], 6); assert_eq!(memory.data_unchecked_mut()[0x1002], 6);
assert_eq!(memory.data_unchecked_mut()[0x1003], 5); assert_eq!(memory.data_unchecked_mut()[0x1003], 5);
} }
assert_eq!(load(0x1002)?, 6); assert_eq!(load.call(0x1002)?, 6);
assert_eq!(load(0x1003)?, 5); assert_eq!(load.call(0x1003)?, 5);
// Grow memory. // Grow memory.
println!("Growing memory..."); println!("Growing memory...");
@@ -74,10 +65,10 @@ fn main() -> Result<()> {
assert_eq!(memory.size(), 3); assert_eq!(memory.size(), 3);
assert_eq!(memory.data_size(), 0x30000); assert_eq!(memory.data_size(), 0x30000);
assert_eq!(load(0x20000)?, 0); assert_eq!(load.call(0x20000)?, 0);
store(0x20000, 0)?; store.call((0x20000, 0))?;
assert!(load(0x30000).is_err()); assert!(load.call(0x30000).is_err());
assert!(store(0x30000, 0).is_err()); assert!(store.call((0x30000, 0)).is_err());
assert!(memory.grow(1).is_err()); assert!(memory.grow(1).is_err());
assert!(memory.grow(0).is_ok()); assert!(memory.grow(0).is_ok());

View File

@@ -7,7 +7,7 @@
// You can execute this example with `cargo run --example multi` // You can execute this example with `cargo run --example multi`
use anyhow::{format_err, Result}; use anyhow::Result;
use wasmtime::*; use wasmtime::*;
fn main() -> Result<()> { fn main() -> Result<()> {
@@ -40,51 +40,31 @@ fn main() -> Result<()> {
// Extract exports. // Extract exports.
println!("Extracting export..."); println!("Extracting export...");
let g = instance let g = instance.get_typed_func::<(i32, i64), (i64, i32)>("g")?;
.get_func("g")
.ok_or(format_err!("failed to find export `g`"))?;
// Call `$g`. // Call `$g`.
println!("Calling export \"g\"..."); println!("Calling export \"g\"...");
let results = g.call(&[Val::I32(1), Val::I64(3)])?; let (a, b) = g.call((1, 3))?;
println!("Printing result..."); println!("Printing result...");
println!("> {} {}", results[0].unwrap_i64(), results[1].unwrap_i32()); println!("> {} {}", a, b);
assert_eq!(results[0].unwrap_i64(), 4); assert_eq!(a, 4);
assert_eq!(results[1].unwrap_i32(), 2); assert_eq!(b, 2);
// Call `$round_trip_many`. // Call `$round_trip_many`.
println!("Calling export \"round_trip_many\"..."); println!("Calling export \"round_trip_many\"...");
let round_trip_many = instance let round_trip_many = instance
.get_func("round_trip_many") .get_typed_func::<
.ok_or(format_err!("failed to find export `round_trip_many`"))?; (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
let args = vec![ (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64),
Val::I64(0), >
Val::I64(1), ("round_trip_many")?;
Val::I64(2), let results = round_trip_many.call((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))?;
Val::I64(3),
Val::I64(4),
Val::I64(5),
Val::I64(6),
Val::I64(7),
Val::I64(8),
Val::I64(9),
];
let results = round_trip_many.call(&args)?;
println!("Printing result..."); println!("Printing result...");
print!(">"); println!("> {:?}", results);
for r in results.iter() { assert_eq!(results, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
print!(" {}", r.unwrap_i64());
}
println!();
assert_eq!(results.len(), 10);
assert!(args
.iter()
.zip(results.iter())
.all(|(a, r)| a.i64() == r.i64()));
Ok(()) Ok(())
} }

View File

@@ -50,14 +50,11 @@ fn deserialize(buffer: &[u8]) -> Result<()> {
// Next we poke around a bit to extract the `run` function from the module. // Next we poke around a bit to extract the `run` function from the module.
println!("Extracting export..."); println!("Extracting export...");
let run = instance let run = instance.get_typed_func::<(), ()>("run")?;
.get_func("run")
.ok_or(anyhow::format_err!("failed to find `run` function export"))?
.get0::<()>()?;
// And last but not least we can call it! // And last but not least we can call it!
println!("Calling export..."); println!("Calling export...");
run()?; run.call(())?;
println!("Done."); println!("Done.");
Ok(()) Ok(())

View File

@@ -1,6 +1,6 @@
// You can execute this example with `cargo run --example threads` // You can execute this example with `cargo run --example threads`
use anyhow::{format_err, Result}; use anyhow::Result;
use std::thread; use std::thread;
use std::time; use std::time;
use wasmtime::*; use wasmtime::*;
@@ -26,14 +26,12 @@ fn run(engine: &Engine, module: Module, id: i32) -> Result<()> {
// Extract exports. // Extract exports.
println!("Extracting export..."); println!("Extracting export...");
let g = instance let g = instance.get_typed_func::<(), ()>("run")?;
.get_func("run")
.ok_or(format_err!("failed to find export `run`"))?;
for _ in 0..N_REPS { for _ in 0..N_REPS {
thread::sleep(time::Duration::from_millis(100)); thread::sleep(time::Duration::from_millis(100));
// Call `$run`. // Call `$run`.
drop(g.call(&[])?); drop(g.call(())?);
} }
Ok(()) Ok(())

View File

@@ -37,7 +37,7 @@ fn main() -> Result<()> {
// Instantiate our module with the imports we've created, and run it. // Instantiate our module with the imports we've created, and run it.
let module = Module::from_file(store.engine(), "target/wasm32-wasi/debug/wasi.wasm")?; let module = Module::from_file(store.engine(), "target/wasm32-wasi/debug/wasi.wasm")?;
linker.module("", &module)?; linker.module("", &module)?;
linker.get_default("")?.get0::<()>()?()?; linker.get_default("")?.typed::<(), ()>()?.call(())?;
Ok(()) Ok(())
} }

View File

@@ -19,12 +19,12 @@ fn run_smoke_test(func: &Func) {
run(future1).unwrap(); run(future1).unwrap();
} }
fn run_smoke_get0_test(func: &Func) { fn run_smoke_typed_test(func: &Func) {
let func = func.get0_async::<()>().unwrap(); let func = func.typed::<(), ()>().unwrap();
run(func()).unwrap(); run(func.call_async(())).unwrap();
run(func()).unwrap(); run(func.call_async(())).unwrap();
let future1 = func(); let future1 = func.call_async(());
let future2 = func(); let future2 = func.call_async(());
run(future2).unwrap(); run(future2).unwrap();
run(future1).unwrap(); run(future1).unwrap();
} }
@@ -39,13 +39,13 @@ fn smoke() {
move |_caller, _state, _params, _results| Box::new(async { Ok(()) }), move |_caller, _state, _params, _results| Box::new(async { Ok(()) }),
); );
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| { let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| {
Box::new(async { Ok(()) }) Box::new(async { Ok(()) })
}); });
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
} }
#[test] #[test]
@@ -68,13 +68,13 @@ fn smoke_host_func() {
.get_host_func("", "first") .get_host_func("", "first")
.expect("expected host function"); .expect("expected host function");
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
let func = store let func = store
.get_host_func("", "second") .get_host_func("", "second")
.expect("expected host function"); .expect("expected host function");
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
} }
#[test] #[test]
@@ -92,7 +92,7 @@ fn smoke_with_suspension() {
}, },
); );
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| { let func = Func::wrap0_async(&store, (), move |_caller: Caller<'_>, _state| {
Box::new(async { Box::new(async {
@@ -101,7 +101,7 @@ fn smoke_with_suspension() {
}) })
}); });
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
} }
#[test] #[test]
@@ -132,13 +132,13 @@ fn smoke_host_func_with_suspension() {
.get_host_func("", "first") .get_host_func("", "first")
.expect("expected host function"); .expect("expected host function");
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
let func = store let func = store
.get_host_func("", "second") .get_host_func("", "second")
.expect("expected host function"); .expect("expected host function");
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
} }
#[test] #[test]
@@ -461,7 +461,7 @@ fn async_with_pooling_stacks() {
); );
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
} }
#[test] #[test]
@@ -492,7 +492,7 @@ fn async_host_func_with_pooling_stacks() {
let func = store.get_host_func("", "").expect("expected host function"); let func = store.get_host_func("", "").expect("expected host function");
run_smoke_test(&func); run_smoke_test(&func);
run_smoke_get0_test(&func); run_smoke_typed_test(&func);
} }
fn execute_across_threads<F: Future + 'static>(future: F) { 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]>> { fn invoke_export(instance: &Instance, func_name: &str) -> Result<i32> {
let ret = instance.get_func(func_name).unwrap().call(&[])?; let ret = instance.get_typed_func::<(), i32>(func_name)?.call(())?;
Ok(ret) Ok(ret)
} }
@@ -126,7 +126,7 @@ mod tests {
} }
println!("calling hostcall_read..."); println!("calling hostcall_read...");
let result = invoke_export(&instance, "hostcall_read").unwrap(); let result = invoke_export(&instance, "hostcall_read").unwrap();
assert_eq!(123, result[0].unwrap_i32()); assert_eq!(123, result);
Ok(()) Ok(())
} }
@@ -149,7 +149,7 @@ mod tests {
{ {
println!("calling read..."); println!("calling read...");
let result = invoke_export(&instance, "read").expect("read succeeded"); 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 // these invoke wasmtime_call_trampoline from callable.rs
{ {
let read_func = instance let read_func = instance.get_typed_func::<(), i32>("read")?;
.get_func("read")
.expect("expected a 'read' func in the module");
println!("calling read..."); println!("calling read...");
let result = read_func.call(&[]).expect("expected function not to trap"); let result = read_func.call(()).expect("expected function not to trap");
assert_eq!(123i32, result[0].clone().unwrap_i32()); assert_eq!(123i32, result);
} }
{ {
let read_out_of_bounds_func = instance let read_out_of_bounds_func =
.get_func("read_out_of_bounds") instance.get_typed_func::<(), i32>("read_out_of_bounds")?;
.expect("expected a 'read_out_of_bounds' func in the module");
println!("calling read_out_of_bounds..."); println!("calling read_out_of_bounds...");
let trap = read_out_of_bounds_func let trap = read_out_of_bounds_func.call(()).unwrap_err();
.call(&[])
.unwrap_err()
.downcast::<Trap>()?;
assert!(trap assert!(trap
.to_string() .to_string()
.contains("wasm trap: out of bounds memory access")); .contains("wasm trap: out of bounds memory access"));
@@ -233,7 +227,7 @@ mod tests {
println!("calling instance1.read..."); println!("calling instance1.read...");
let result = invoke_export(&instance1, "read").expect("read succeeded"); let result = invoke_export(&instance1, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32()); assert_eq!(123, result);
assert_eq!( assert_eq!(
instance1_handler_triggered.load(Ordering::SeqCst), instance1_handler_triggered.load(Ordering::SeqCst),
true, true,
@@ -274,7 +268,7 @@ mod tests {
println!("calling instance2.read..."); println!("calling instance2.read...");
let result = invoke_export(&instance2, "read").expect("read succeeded"); let result = invoke_export(&instance2, "read").expect("read succeeded");
assert_eq!(123, result[0].unwrap_i32()); assert_eq!(123, result);
assert_eq!( assert_eq!(
instance2_handler_triggered.load(Ordering::SeqCst), instance2_handler_triggered.load(Ordering::SeqCst),
true, true,
@@ -316,7 +310,7 @@ mod tests {
println!("calling instance2.run"); println!("calling instance2.run");
let result = invoke_export(&instance2, "run")?; let result = invoke_export(&instance2, "run")?;
assert_eq!(123, result[0].unwrap_i32()); assert_eq!(123, result);
Ok(()) 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(s1_f.clone()))]).is_err());
assert!(s2_f.call(&[Val::FuncRef(Some(s2_f.clone()))]).is_ok()); 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(()) Ok(())
} }

View File

@@ -243,41 +243,40 @@ fn trap_import() -> Result<()> {
fn get_from_wrapper() { fn get_from_wrapper() {
let store = Store::default(); let store = Store::default();
let f = Func::wrap(&store, || {}); let f = Func::wrap(&store, || {});
assert!(f.get0::<()>().is_ok()); assert!(f.typed::<(), ()>().is_ok());
assert!(f.get0::<i32>().is_err()); assert!(f.typed::<(), i32>().is_err());
assert!(f.get1::<(), ()>().is_ok()); assert!(f.typed::<(), ()>().is_ok());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.typed::<i32, ()>().is_err());
assert!(f.get1::<i32, i32>().is_err()); assert!(f.typed::<i32, i32>().is_err());
assert!(f.get2::<(), (), ()>().is_ok()); assert!(f.typed::<(i32, i32), ()>().is_err());
assert!(f.get2::<i32, i32, ()>().is_err()); assert!(f.typed::<(i32, i32), i32>().is_err());
assert!(f.get2::<i32, i32, i32>().is_err());
let f = Func::wrap(&store, || -> i32 { loop {} }); 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 {} }); 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 {} }); 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 {} }); 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 {} }); 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| {}); let f = Func::wrap(&store, |_: i32| {});
assert!(f.get1::<i32, ()>().is_ok()); assert!(f.typed::<i32, ()>().is_ok());
assert!(f.get1::<i64, ()>().is_err()); assert!(f.typed::<i64, ()>().is_err());
assert!(f.get1::<f32, ()>().is_err()); assert!(f.typed::<f32, ()>().is_err());
assert!(f.get1::<f64, ()>().is_err()); assert!(f.typed::<f64, ()>().is_err());
let f = Func::wrap(&store, |_: i64| {}); let f = Func::wrap(&store, |_: i64| {});
assert!(f.get1::<i64, ()>().is_ok()); assert!(f.typed::<i64, ()>().is_ok());
let f = Func::wrap(&store, |_: f32| {}); let f = Func::wrap(&store, |_: f32| {});
assert!(f.get1::<f32, ()>().is_ok()); assert!(f.typed::<f32, ()>().is_ok());
let f = Func::wrap(&store, |_: f64| {}); 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>| {}); 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>| {}); let f = Func::wrap(&store, |_: Option<Func>| {});
assert!(f.get1::<Option<Func>, ()>().is_ok()); assert!(f.typed::<Option<Func>, ()>().is_ok());
} }
#[test] #[test]
@@ -285,16 +284,16 @@ fn get_from_signature() {
let store = Store::default(); let store = Store::default();
let ty = FuncType::new(None, None); let ty = FuncType::new(None, None);
let f = Func::new(&store, ty, |_, _, _| panic!()); let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_ok()); assert!(f.typed::<(), ()>().is_ok());
assert!(f.get0::<i32>().is_err()); assert!(f.typed::<(), i32>().is_err());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.typed::<i32, ()>().is_err());
let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64)); let ty = FuncType::new(Some(ValType::I32), Some(ValType::F64));
let f = Func::new(&store, ty, |_, _, _| panic!()); let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_err()); assert!(f.typed::<(), ()>().is_err());
assert!(f.get0::<i32>().is_err()); assert!(f.typed::<(), i32>().is_err());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.typed::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok()); assert!(f.typed::<i32, f64>().is_ok());
} }
#[test] #[test]
@@ -314,17 +313,17 @@ fn get_from_module() -> anyhow::Result<()> {
)?; )?;
let instance = Instance::new(&store, &module, &[])?; let instance = Instance::new(&store, &module, &[])?;
let f0 = instance.get_func("f0").unwrap(); let f0 = instance.get_func("f0").unwrap();
assert!(f0.get0::<()>().is_ok()); assert!(f0.typed::<(), ()>().is_ok());
assert!(f0.get0::<i32>().is_err()); assert!(f0.typed::<(), i32>().is_err());
let f1 = instance.get_func("f1").unwrap(); let f1 = instance.get_func("f1").unwrap();
assert!(f1.get0::<()>().is_err()); assert!(f1.typed::<(), ()>().is_err());
assert!(f1.get1::<i32, ()>().is_ok()); assert!(f1.typed::<i32, ()>().is_ok());
assert!(f1.get1::<i32, f32>().is_err()); assert!(f1.typed::<i32, f32>().is_err());
let f2 = instance.get_func("f2").unwrap(); let f2 = instance.get_func("f2").unwrap();
assert!(f2.get0::<()>().is_err()); assert!(f2.typed::<(), ()>().is_err());
assert!(f2.get0::<i32>().is_ok()); assert!(f2.typed::<(), i32>().is_ok());
assert!(f2.get1::<i32, ()>().is_err()); assert!(f2.typed::<i32, ()>().is_err());
assert!(f2.get1::<i32, f32>().is_err()); assert!(f2.typed::<i32, f32>().is_err());
Ok(()) Ok(())
} }
@@ -338,31 +337,32 @@ fn call_wrapped_func() -> Result<()> {
assert_eq!(d, 4.0); assert_eq!(d, 4.0);
}); });
f.call(&[Val::I32(1), Val::I64(2), 3.0f32.into(), 4.0f64.into()])?; 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 f = Func::wrap(&store, || 1i32);
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 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 f = Func::wrap(&store, || 2i64);
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2); 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 f = Func::wrap(&store, || 3.0f32);
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0); 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 f = Func::wrap(&store, || 4.0f64);
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0); assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0); assert_eq!(f.typed::<(), f64>()?.call(())?, 4.0);
Ok(()) 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 // And using `.get` followed by a function call also fails with cross-Store
// arguments. // arguments.
let f = store1_func.get1::<Option<Func>, ()>()?; let f = store1_func.typed::<Option<Func>, ()>()?;
let result = f(Some(store2_func)); let result = f.call(Some(store2_func));
assert!(result.is_err()); assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("cross-`Store`")); assert!(result.unwrap_err().to_string().contains("cross-`Store`"));
@@ -549,3 +549,35 @@ fn trampolines_always_valid() -> anyhow::Result<()> {
func.call(&[])?; func.call(&[])?;
Ok(()) 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 store = Store::new(&engine);
let f = store.get_host_func("", "f1").expect("func defined"); let f = store.get_host_func("", "f1").expect("func defined");
assert!(f.get0::<()>().is_ok()); assert!(f.typed::<(), ()>().is_ok());
assert!(f.get0::<i32>().is_err()); assert!(f.typed::<(), i32>().is_err());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.typed::<i32, ()>().is_err());
let f = store.get_host_func("", "f2").expect("func defined"); let f = store.get_host_func("", "f2").expect("func defined");
assert!(f.get0::<()>().is_err()); assert!(f.typed::<(), ()>().is_err());
assert!(f.get0::<i32>().is_err()); assert!(f.typed::<(), i32>().is_err());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.typed::<i32, ()>().is_err());
assert!(f.get1::<i32, f64>().is_ok()); assert!(f.typed::<i32, f64>().is_ok());
Ok(()) Ok(())
} }
@@ -427,31 +427,32 @@ fn call_wrapped_func() -> Result<()> {
let f = store.get_host_func("", "f1").expect("func defined"); 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.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 f = store.get_host_func("", "f2").expect("func defined");
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i32(), 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 f = store.get_host_func("", "f3").expect("func defined");
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_i64(), 2); 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 f = store.get_host_func("", "f4").expect("func defined");
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f32(), 3.0); 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 f = store.get_host_func("", "f5").expect("func defined");
let results = f.call(&[])?; let results = f.call(&[])?;
assert_eq!(results.len(), 1); assert_eq!(results.len(), 1);
assert_eq!(results[0].unwrap_f64(), 4.0); assert_eq!(results[0].unwrap_f64(), 4.0);
assert_eq!(f.get0::<f64>()?()?, 4.0); assert_eq!(f.typed::<(), f64>()?.call(())?, 4.0);
Ok(()) Ok(())
} }
@@ -597,9 +598,9 @@ fn wasi_imports_missing_context() -> Result<()> {
let linker = Linker::new(&store); let linker = Linker::new(&store);
let instance = linker.instantiate(&module)?; 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.to_string().contains("context is missing in the store"));
assert!(trap.i32_exit_status().is_none()); assert!(trap.i32_exit_status().is_none());
@@ -629,9 +630,8 @@ fn wasi_imports() -> Result<()> {
let linker = Linker::new(&store); let linker = Linker::new(&store);
let instance = linker.instantiate(&module)?; let instance = linker.instantiate(&module)?;
let start = instance.get_func("_start").unwrap().get0::<()>()?; let start = instance.get_typed_func::<(), ()>("_start")?;
let trap = start.call(()).unwrap_err();
let trap = start().unwrap_err();
assert_eq!(trap.i32_exit_status(), Some(123)); assert_eq!(trap.i32_exit_status(), Some(123));
Ok(()) Ok(())

View File

@@ -27,9 +27,9 @@ fn loops_interruptable() -> anyhow::Result<()> {
let store = interruptable_store(); let store = interruptable_store();
let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?; let module = Module::new(store.engine(), r#"(func (export "loop") (loop br 0))"#)?;
let instance = Instance::new(&store, &module, &[])?; 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(); store.interrupt_handle()?.interrupt();
let trap = iloop().unwrap_err(); let trap = iloop.call(()).unwrap_err();
assert!(trap.to_string().contains("wasm trap: interrupt")); assert!(trap.to_string().contains("wasm trap: interrupt"));
Ok(()) Ok(())
} }
@@ -40,9 +40,9 @@ fn functions_interruptable() -> anyhow::Result<()> {
let module = hugely_recursive_module(&store)?; let module = hugely_recursive_module(&store)?;
let func = Func::wrap(&store, || {}); let func = Func::wrap(&store, || {});
let instance = Instance::new(&store, &module, &[func.into()])?; 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(); store.interrupt_handle()?.interrupt();
let trap = iloop().unwrap_err(); let trap = iloop.call(()).unwrap_err();
assert!( assert!(
trap.to_string().contains("wasm trap: interrupt"), 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 // Enter the infinitely looping function and assert that our interrupt
// handle does indeed actually interrupt the function. // handle does indeed actually interrupt the function.
let iloop = instance.get_func("loop").unwrap().get0::<()>()?; let iloop = instance.get_typed_func::<(), ()>("loop")?;
let trap = iloop().unwrap_err(); let trap = iloop.call(()).unwrap_err();
thread.join().unwrap(); thread.join().unwrap();
assert!( assert!(
trap.to_string().contains("wasm trap: interrupt"), 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 // Enter the infinitely looping function and assert that our interrupt
// handle does indeed actually interrupt the function. // handle does indeed actually interrupt the function.
let iloop = instance.get_func("loop").unwrap().get0::<()>()?; let iloop = instance.get_typed_func::<(), ()>("loop")?;
let trap = iloop().unwrap_err(); let trap = iloop.call(()).unwrap_err();
thread.join().unwrap(); thread.join().unwrap();
assert!( assert!(
trap.to_string().contains("wasm trap: interrupt"), 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 instance = Instance::new(&store, &module, &imports)?;
let func = instance.get_func("foo").unwrap(); let func = instance.get_typed_func::<(), i32>("foo")?;
let results = func.call(&[])?; let result = func.call(())?;
assert_eq!(results.len(), 1); assert_eq!(result, 3);
match results[0] {
Val::I32(n) => assert_eq!(n, 3),
_ => panic!("unexpected type of return"),
}
Ok(()) Ok(())
} }

View File

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

View File

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

View File

@@ -63,13 +63,13 @@ fn memory_limit() -> Result<()> {
{ {
let store = Store::new(&engine); let store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?; 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.call(()).expect("function should not trap"), 0);
assert_eq!(f().expect("function should not trap"), 1); assert_eq!(f.call(()).expect("function should not trap"), 1);
assert_eq!(f().expect("function should not trap"), 2); assert_eq!(f.call(()).expect("function should not trap"), 2);
assert_eq!(f().expect("function should not trap"), -1); assert_eq!(f.call(()).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);
} }
// Instantiate the module and grow the memory via the Wasmtime API // 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 store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?; let instance = Instance::new(&store, &module, &[])?;
let m = instance.get_memory("m").unwrap(); 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")); 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")); assert!(trap.to_string().contains("out of bounds"));
m.grow(1).expect("memory should grow"); 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")); 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")); assert!(trap.to_string().contains("out of bounds"));
m.grow(1).expect("memory should grow"); 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"); 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 store = Store::new(&engine);
let instance = Instance::new(&store, &module, &[])?; 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 { 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.call(()).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);
} }
// Instantiate the module and grow the table via the Wasmtime API // 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 func = Func::wrap(&store, test_host_stack);
let instance = Instance::new(&store, &module, &[func.into()])?; 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 // Make sure that our function traps and the trap says that the call stack
// has been exhausted. // has been exhausted.
let trap = foo().unwrap_err(); let trap = foo.call(()).unwrap_err();
assert!( assert!(
trap.to_string().contains("call stack exhausted"), 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 hello_func = Func::new(&store, hello_type, |_, _, _| Err(Trap::new("test 123")));
let instance = Instance::new(&store, &module, &[hello_func.into()])?; let instance = Instance::new(&store, &module, &[hello_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");
assert!(e.to_string().contains("test 123")); assert!(e.to_string().contains("test 123"));
Ok(()) Ok(())
@@ -45,13 +40,9 @@ fn test_trap_trace() -> Result<()> {
let module = Module::new(store.engine(), wat)?; let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[])?; 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 let e = run_func.call(()).err().expect("error calling function");
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let trace = e.trace(); let trace = e.trace();
assert_eq!(trace.len(), 2); assert_eq!(trace.len(), 2);
@@ -92,13 +83,9 @@ fn test_trap_trace_cb() -> Result<()> {
let module = Module::new(store.engine(), wat)?; let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[fn_func.into()])?; 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 let e = run_func.call(()).err().expect("error calling function");
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let trace = e.trace(); let trace = e.trace();
assert_eq!(trace.len(), 2); assert_eq!(trace.len(), 2);
@@ -124,13 +111,9 @@ fn test_trap_stack_overflow() -> Result<()> {
let module = Module::new(store.engine(), wat)?; let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[])?; 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 let e = run_func.call(()).err().expect("error calling function");
.call(&[])
.err()
.expect("error calling function")
.downcast::<Trap>()?;
let trace = e.trace(); let trace = e.trace();
assert!(trace.len() >= 32); assert!(trace.len() >= 32);
@@ -160,9 +143,9 @@ fn trap_display_pretty() -> Result<()> {
let module = Module::new(store.engine(), wat)?; let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[])?; 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!( assert_eq!(
e.to_string(), e.to_string(),
"\ "\
@@ -204,9 +187,9 @@ fn trap_display_multi_module() -> Result<()> {
"#; "#;
let module = Module::new(store.engine(), wat)?; let module = Module::new(store.engine(), wat)?;
let instance = Instance::new(&store, &module, &[bar])?; 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!( assert_eq!(
e.to_string(), e.to_string(),
"\ "\
@@ -278,16 +261,13 @@ fn rust_panic_import() -> Result<()> {
Func::wrap(&store, || panic!("this is another panic")).into(), Func::wrap(&store, || panic!("this is another panic")).into(),
], ],
)?; )?;
let func = instance.get_func("foo").unwrap(); let func = instance.get_typed_func::<(), ()>("foo")?;
let err = panic::catch_unwind(AssertUnwindSafe(|| { let err = panic::catch_unwind(AssertUnwindSafe(|| drop(func.call(())))).unwrap_err();
drop(func.call(&[]));
}))
.unwrap_err();
assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic")); 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(|| { let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(func.call(&[])); drop(func.call(()));
})) }))
.unwrap_err(); .unwrap_err();
assert_eq!( assert_eq!(
@@ -437,14 +417,14 @@ fn present_after_module_drop() -> Result<()> {
let store = Store::default(); let store = Store::default();
let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?; let module = Module::new(store.engine(), r#"(func (export "foo") unreachable)"#)?;
let instance = Instance::new(&store, &module, &[])?; 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"); println!("asserting before we drop modules");
assert_trap(func.call(&[]).unwrap_err().downcast()?); assert_trap(func.call(()).unwrap_err());
drop((instance, module)); drop((instance, module));
println!("asserting after drop"); println!("asserting after drop");
assert_trap(func.call(&[]).unwrap_err().downcast()?); assert_trap(func.call(()).unwrap_err());
return Ok(()); return Ok(());
fn assert_trap(t: Trap) { fn assert_trap(t: Trap) {

View File

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