diff --git a/crates/bench-api/src/lib.rs b/crates/bench-api/src/lib.rs index a7b939a084..b2884d7738 100644 --- a/crates/bench-api/src/lib.rs +++ b/crates/bench-api/src/lib.rs @@ -301,9 +301,8 @@ impl BenchState { .as_ref() .expect("instantiate the module before executing it"); - let start_func = instance.get_func("_start").expect("a _start function"); - let runnable_func = start_func.get0::<()>()?; - match runnable_func() { + let start_func = instance.get_typed_func::<(), ()>("_start")?; + match start_func.call(()) { Ok(_) => Ok(()), Err(trap) => { // Since _start will likely return by using the system `exit` call, we must diff --git a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs index 5900e6a19c..1610880115 100644 --- a/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs +++ b/crates/test-programs/tests/wasm_tests/runtime/cap_std_sync.rs @@ -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 instance = linker.instantiate(&module)?; - let start = instance.get_func("_start").unwrap(); - let with_type = start.get0::<()>()?; - with_type().map_err(anyhow::Error::from) + let start = instance.get_typed_func::<(), ()>("_start")?; + start.call(()).map_err(anyhow::Error::from) }; 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 instance = linker.instantiate(&module)?; - let start = instance.get_func("_start").unwrap(); - let with_type = start.get0::<()>()?; - with_type().map_err(anyhow::Error::from) + let start = instance.get_typed_func::<(), ()>("_start")?; + start.call(()).map_err(anyhow::Error::from) }; match r { diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 55a49c0918..da85f832b8 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -350,8 +350,8 @@ macro_rules! generate_wrap_async_host_func { debug_assert!(store.async_support()); let mut future = Pin::from(func(caller, $($args),*)); match store.block_on(future.as_mut()) { - Ok(ret) => ret.into_result(), - Err(e) => Err(e), + Ok(ret) => ret.into_fallible(), + Err(e) => R::fallible_from_trap(e), } }) ); diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index 9884c33bb3..f99548fee5 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -1,6 +1,6 @@ use crate::{sig_registry::SignatureRegistry, trampoline::StoreInstanceHandle}; -use crate::{Config, Extern, ExternRef, FuncType, Store, Trap, Val, ValType}; -use anyhow::{bail, ensure, Context as _, Result}; +use crate::{Config, Extern, FuncType, Store, Trap, Val, ValType}; +use anyhow::{bail, Context as _, Result}; use smallvec::{smallvec, SmallVec}; use std::any::Any; use std::cmp::max; @@ -10,12 +10,11 @@ use std::mem; use std::panic::{self, AssertUnwindSafe}; use std::pin::Pin; use std::ptr::{self, NonNull}; -use std::task::{Context, Poll}; use wasmtime_environ::wasm::{EntityIndex, FuncIndex}; use wasmtime_runtime::{ raise_user_trap, ExportFunction, InstanceAllocator, InstanceHandle, OnDemandInstanceAllocator, - VMCallerCheckedAnyfunc, VMContext, VMExternRef, VMFunctionBody, VMFunctionImport, - VMSharedSignatureIndex, VMTrampoline, + VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMSharedSignatureIndex, + VMTrampoline, }; /// Represents a host function. @@ -150,8 +149,8 @@ unsafe impl Sync for HostFunc {} /// information about [asynchronous configs](Config::async_support), but from the /// perspective of `Func` it's important to know that whether or not your /// [`Store`] is asynchronous will dictate whether you call functions through -/// [`Func::call`] or [`Func::call_async`] (or the wrappers such as -/// [`Func::get0`] vs [`Func::get0_async`]). +/// [`Func::call`] or [`Func::call_async`] (or the typed wrappers such as +/// [`TypedFunc::call`] vs [`TypedFunc::call_async`]). /// /// Note that asynchronous function APIs here are a bit trickier than their /// synchronous brethren. For example [`Func::new_async`] and @@ -166,11 +165,13 @@ unsafe impl Sync for HostFunc {} /// etc). Be sure to consult the documentation for [`Func::wrap`] for how the /// wasm type signature is inferred from the Rust type signature. /// -/// # To `Func::call` or to `Func::getN` +/// # To `Func::call` or to `Func::typed().call()` /// -/// There are four ways to call a `Func`. Half are asynchronous and half are -/// synchronous, corresponding to the type of store you're using. Within each -/// half you've got two choices: +/// There's a 2x2 matrix of methods to call `Func`. Invocations can either be +/// asynchronous or synchronous. They can also be statically typed or not. +/// Whether or not an invocation is asynchronous is indicated via the method +/// being `async` and `call_async` being the entry point. Otherwise for +/// statically typed or not your options are: /// /// * Dynamically typed - if you don't statically know the signature of the /// function that you're calling you'll be using [`Func::call`] or @@ -183,18 +184,14 @@ unsafe impl Sync for HostFunc {} /// looking for a speedier alternative you can also use... /// /// * Statically typed - if you statically know the type signature of the wasm -/// function you're calling then you can use the [`Func::getN`](Func::get1) -/// family of functions where `N` is the number of wasm arguments the function -/// takes. Asynchronous users can use [`Func::getN_async`](Func::get1_async). -/// These functions will perform type validation up-front and then return a -/// specialized closure which can be used to invoke the wasm function. This -/// route involves no dynamic allocation and does not type-checks during -/// runtime, so it's recommended to use this where possible for maximal speed. -/// -/// Unfortunately a limitation of the code generation backend right now means -/// that the statically typed `getN` methods only work with wasm functions that -/// return 0 or 1 value. If 2 or more values are returned you'll need to use the -/// dynamic `call` API. We hope to fix this in the future, though! +/// function you're calling, then you'll want to use the [`Func::typed`] +/// method to acquire an instance of [`TypedFunc`]. This structure is static proof +/// that the underlying wasm function has the ascripted type, and type +/// validation is only done once up-front. The [`TypedFunc::call`] and +/// [`TypedFunc::call_async`] methods are much more efficient than [`Func::call`] +/// and [`Func::call_async`] because the type signature is statically known. +/// This eschews runtime checks as much as possible to get into wasm as fast +/// as possible. /// /// # Examples /// @@ -223,8 +220,8 @@ unsafe impl Sync for HostFunc {} /// // ... or we can make a static assertion about its signature and call it. /// // Our first call here can fail if the signatures don't match, and then the /// // second call can fail if the function traps (like the `match` above). -/// let foo = foo.get0::<()>()?; -/// foo()?; +/// let foo = foo.typed::<(), ()>()?; +/// foo.call(())?; /// # Ok(()) /// # } /// ``` @@ -258,10 +255,9 @@ unsafe impl Sync for HostFunc {} /// "#, /// )?; /// let instance = Instance::new(&store, &module, &[add.into()])?; -/// let call_add_twice = instance.get_func("call_add_twice").expect("export wasn't a function"); -/// let call_add_twice = call_add_twice.get0::()?; +/// let call_add_twice = instance.get_typed_func::<(), i32>("call_add_twice")?; /// -/// assert_eq!(call_add_twice()?, 10); +/// assert_eq!(call_add_twice.call(())?, 10); /// # Ok(()) /// # } /// ``` @@ -332,152 +328,8 @@ macro_rules! for_each_function_signature { }; } -macro_rules! generate_get_methods { - ($num:tt $($args:ident)*) => (paste::paste! { - /// Extracts a natively-callable object from this `Func`, if the - /// signature matches. - /// - /// See the [`Func`] structure for more documentation. Returns an error - /// if the type parameters and return parameter provided don't match the - /// actual function's type signature. - /// - /// # Panics - /// - /// Panics if this is called on a function in an asynchronous store. - #[allow(non_snake_case)] - pub fn []<$($args,)* R>(&self) - -> Result Result> - where - $($args: WasmTy,)* - R: WasmTy, - { - assert!(!self.store().async_support(), concat!("cannot use `get", $num, "` when async support is enabled on the config")); - self.[<_get $num>]::<$($args,)* R>() - } - - /// Extracts a natively-callable object from this `Func`, if the - /// signature matches. - /// - /// See the [`Func`] structure for more documentation. Returns an error - /// if the type parameters and return parameter provided don't match the - /// actual function's type signature. - /// - /// # Panics - /// - /// Panics if this is called on a function in a synchronous store. - #[allow(non_snake_case, warnings)] - #[cfg(feature = "async")] - #[cfg_attr(nightlydoc, doc(cfg(feature = "async")))] - pub fn []<$($args,)* R>(&self) - -> Result WasmCall> - where - $($args: WasmTy + 'static,)* - R: WasmTy + 'static, - { - assert!(self.store().async_support(), concat!("cannot use `get", $num, "_async` without enabling async support on the config")); - - // TODO: ideally we wouldn't box up the future here but could - // instead name the future returned by `on_fiber`. Unfortunately - // that would require a bunch of inter-future borrows which are safe - // but gnarly to implement by hand. - // - // Otherwise the implementation here is pretty similar to - // `call_async` where we're just calling the `on_fiber` method to - // run the blocking work. Most of the goop here is juggling - // lifetimes and making sure everything lives long enough and - // closures have the right signatures. - // - // This is... less readable than ideal. - let func = self.[<_get $num>]::<$($args,)* R>()?; - let store = self.store().clone(); - Ok(move |$($args),*| { - let func = func.clone(); - let store = store.clone(); - WasmCall { - inner: Pin::from(Box::new(async move { - match store.on_fiber(|| func($($args,)*)).await { - Ok(result) => result, - Err(trap) => Err(trap), - } - })) - } - }) - } - - // Internal helper to share between `getN` and `getN_async` - #[allow(non_snake_case)] - fn [<_get $num>]<$($args,)* R>(&self) - -> Result Result + Clone> - where - $($args: WasmTy,)* - R: WasmTy, - { - // Verify all the paramers match the expected parameters, and that - // there are no extra parameters... - let ty = self.ty(); - let mut params = ty.params(); - let n = 0; - $( - let n = n + 1; - $args::matches(&mut params) - .with_context(|| format!("Type mismatch in argument {}", n))?; - )* - ensure!(params.next().is_none(), "Type mismatch: too many arguments (expected {})", n); - - // ... then do the same for the results... - let mut results = ty.results(); - R::matches(&mut results) - .context("Type mismatch in return type")?; - ensure!(results.next().is_none(), "Type mismatch: too many return values (expected 1)"); - - // Pass the instance into the closure so that we keep it live for - // the lifetime of the closure. Pass the `anyfunc` in so that we can - // call it. - let instance = self.instance.clone(); - let anyfunc = self.export.anyfunc; - - // ... and then once we've passed the typechecks we can hand out our - // object since our `transmute` below should be safe! - Ok(move |$($args: $args),*| -> Result { - unsafe { - let fnptr = mem::transmute::< - *const VMFunctionBody, - unsafe extern "C" fn( - *mut VMContext, - *mut VMContext, - $( $args::Abi, )* - ) -> R::Abi, - >(anyfunc.as_ref().func_ptr.as_ptr()); - - let mut ret = None; - - $( - // Because this returned closure is not marked `unsafe`, - // we have to check that incoming values are compatible - // with our store. - if !$args.compatible_with_store(&instance.store) { - return Err(Trap::new( - "attempt to pass cross-`Store` value to Wasm as function argument" - )); - } - - let $args = $args.into_abi_for_arg(&instance.store); - )* - - invoke_wasm_and_catch_traps(&instance.store, || { - ret = Some(fnptr( - anyfunc.as_ref().vmctx, - ptr::null_mut(), - $( $args, )* - )); - })?; - - Ok(R::from_abi(ret.unwrap(), &instance.store)) - } - }) - } - }) -} +mod typed; +pub use typed::*; macro_rules! generate_wrap_async_func { ($num:tt $($args:ident)*) => (paste::paste!{ @@ -505,8 +357,8 @@ macro_rules! generate_wrap_async_func { let store = caller.store().clone(); let mut future = Pin::from(func(caller, &state, $($args),*)); match store.block_on(future.as_mut()) { - Ok(ret) => ret.into_result(), - Err(e) => Err(e), + Ok(ret) => ret.into_fallible(), + Err(e) => R::fallible_from_trap(e), } }) } @@ -751,8 +603,8 @@ impl Func { /// "#, /// )?; /// let instance = Instance::new(&store, &module, &[add.into()])?; - /// let foo = instance.get_func("foo").unwrap().get2::()?; - /// assert_eq!(foo(1, 2)?, 3); + /// let foo = instance.get_typed_func::<(i32, i32), i32>("foo")?; + /// assert_eq!(foo.call((1, 2))?, 3); /// # Ok(()) /// # } /// ``` @@ -782,9 +634,9 @@ impl Func { /// "#, /// )?; /// let instance = Instance::new(&store, &module, &[add.into()])?; - /// let foo = instance.get_func("foo").unwrap().get2::()?; - /// assert_eq!(foo(1, 2)?, 3); - /// assert!(foo(i32::max_value(), 1).is_err()); + /// let foo = instance.get_typed_func::<(i32, i32), i32>("foo")?; + /// assert_eq!(foo.call((1, 2))?, 3); + /// assert!(foo.call((i32::max_value(), 1)).is_err()); /// # Ok(()) /// # } /// ``` @@ -820,8 +672,8 @@ impl Func { /// "#, /// )?; /// let instance = Instance::new(&store, &module, &[debug.into()])?; - /// let foo = instance.get_func("foo").unwrap().get0::<()>()?; - /// foo()?; + /// let foo = instance.get_typed_func::<(), ()>("foo")?; + /// foo.call(())?; /// # Ok(()) /// # } /// ``` @@ -876,8 +728,8 @@ impl Func { /// "#, /// )?; /// let instance = Instance::new(&store, &module, &[log_str.into()])?; - /// let foo = instance.get_func("foo").unwrap().get0::<()>()?; - /// foo()?; + /// let foo = instance.get_typed_func::<(), ()>("foo")?; + /// foo.call(())?; /// # Ok(()) /// # } /// ``` @@ -1073,8 +925,6 @@ impl Func { } } - for_each_function_signature!(generate_get_methods); - /// Get a reference to this function's store. pub fn store(&self) -> &Store { &self.instance.store @@ -1094,7 +944,7 @@ impl Func { &self.export } - pub(crate) fn invoke( + fn invoke( store: &Store, ty: &FuncType, caller_vmctx: *mut VMContext, @@ -1148,6 +998,148 @@ impl Func { Ok(()) } + + /// Attempts to extract a typed object from this `Func` through which the + /// function can be called. + /// + /// This function serves as an alternative to [`Func::call`] and + /// [`Func::call_async`]. This method performs a static type check (using + /// the `Params` and `Results` type parameters on the underlying wasm + /// function. If the type check passes then a `TypedFunc` object is returned, + /// otherwise an error is returned describing the typecheck failure. + /// + /// The purpose of this relative to [`Func::call`] is that it's much more + /// efficient when used to invoke WebAssembly functions. With the types + /// statically known far less setup/teardown is required when invoking + /// WebAssembly. If speed is desired then this function is recommended to be + /// used instead of [`Func::call`] (which is more general, hence its + /// slowdown). + /// + /// The `Params` type parameter is used to describe the parameters of the + /// WebAssembly function. This can either be a single type (like `i32`), or + /// a tuple of types representing the list of parameters (like `(i32, f32, + /// f64)`). Additionally you can use `()` to represent that the function has + /// no parameters. + /// + /// The `Results` type parameter is used to describe the results of the + /// function. This behaves the same way as `Params`, but just for the + /// results of the function. + /// + /// Translation between Rust types and WebAssembly types looks like: + /// + /// | WebAssembly | Rust | + /// |-------------|---------------------| + /// | `i32` | `i32` or `u32` | + /// | `i64` | `i64` or `u64` | + /// | `f32` | `f32` | + /// | `f64` | `f64` | + /// | `externref` | `Option` | + /// | `funcref` | `Option` | + /// | `v128` | not supported | + /// + /// (note that this mapping is the same as that of [`Func::wrap`]). + /// + /// Note that once the [`TypedFunc`] return value is acquired you'll use either + /// [`TypedFunc::call`] or [`TypedFunc::call_async`] as necessary to actually invoke + /// the function. This method does not invoke any WebAssembly code, it + /// simply performs a typecheck before returning the [`TypedFunc`] value. + /// + /// This method also has a convenience wrapper as + /// [`Instance::get_typed_func`](crate::Instance::get_typed_func) to + /// directly get a typed function value from an + /// [`Instance`](crate::Instance). + /// + /// # Errors + /// + /// This function will return an error if `Params` or `Results` does not + /// match the native type of this WebAssembly function. + /// + /// # Examples + /// + /// An end-to-end example of calling a function which takes no parameters + /// and has no results: + /// + /// ``` + /// # use wasmtime::*; + /// # fn main() -> anyhow::Result<()> { + /// let engine = Engine::default(); + /// let store = Store::new(&engine); + /// let module = Module::new(&engine, r#"(module (func (export "foo")))"#)?; + /// let instance = Instance::new(&store, &module, &[])?; + /// let foo = instance.get_func("foo").expect("export wasn't a function"); + /// + /// // Note that this call can fail due to the typecheck not passing, but + /// // in our case we statically know the module so we know this should + /// // pass. + /// let typed = foo.typed::<(), ()>()?; + /// + /// // Note that this can fail if the wasm traps at runtime. + /// typed.call(())?; + /// # Ok(()) + /// # } + /// ``` + /// + /// You can also pass in multiple parameters and get a result back + /// + /// ``` + /// # use wasmtime::*; + /// # fn foo(add: &Func) -> anyhow::Result<()> { + /// let typed = add.typed::<(i32, i64), f32>()?; + /// assert_eq!(typed.call((1, 2))?, 3.0); + /// # Ok(()) + /// # } + /// ``` + /// + /// and similarly if a function has multiple results you can bind that too + /// + /// ``` + /// # use wasmtime::*; + /// # fn foo(add_with_overflow: &Func) -> anyhow::Result<()> { + /// let typed = add_with_overflow.typed::<(u32, u32), (u32, i32)>()?; + /// let (result, overflow) = typed.call((u32::max_value(), 2))?; + /// assert_eq!(result, 1); + /// assert_eq!(overflow, 1); + /// # Ok(()) + /// # } + /// ``` + pub fn typed(&self) -> Result<&TypedFunc> + where + Params: WasmParams, + Results: WasmResults, + { + // First type-check that the params/results are all valid... + let ty = self.ty(); + Params::typecheck(ty.params()).context("type mismatch with parameters")?; + Results::typecheck(ty.results()).context("type mismatch with results")?; + + // ... then we can construct the typed version of this function + // (unsafely), which should be safe since we just did the type check above. + unsafe { Ok(self.typed_unchecked::()) } + } + + /// An unchecked version of [`Func::typed`] which does not perform a + /// typecheck and simply assumes that the type declared here matches the + /// type of this function. + /// + /// The semantics of this function are the same as [`Func::typed`] except + /// that no error is returned because no typechecking is done. + /// + /// # Unsafety + /// + /// This function only safe to call if `typed` would otherwise return `Ok` + /// for the same `Params` and `Results` specified. If `typed` would return + /// an error then the returned `TypedFunc` is memory unsafe to invoke. + pub unsafe fn typed_unchecked(&self) -> &TypedFunc + where + Params: WasmParams, + Results: WasmResults, + { + assert_eq!( + mem::size_of::>(), + mem::size_of_val(self) + ); + &*(self as *const Func as *const TypedFunc) + } } impl fmt::Debug for Func { @@ -1170,54 +1162,6 @@ pub(crate) fn invoke_wasm_and_catch_traps( } } -/// A trait implemented for types which can be arguments to closures passed to -/// [`Func::wrap`] and friends. -/// -/// 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`] -pub unsafe trait WasmTy { - // The raw ABI representation of this type inside Wasm. - #[doc(hidden)] - type Abi: Copy; - - // Is this value compatible with the given store? - #[doc(hidden)] - fn compatible_with_store(&self, store: &Store) -> bool; - - // Convert this value into its ABI representation, when passing a value into - // Wasm as an argument. - #[doc(hidden)] - fn into_abi_for_arg(self, store: &Store) -> Self::Abi; - - // Convert from the raw ABI representation back into `Self`, when receiving - // a value from Wasm. - // - // Safety: The abi value *must* have be valid for this type (e.g. for - // `externref`, it must be a valid raw `VMExternRef` pointer, not some - // random, dangling pointer). - #[doc(hidden)] - unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self; - - // Add this type to the given vec of expected valtypes. - #[doc(hidden)] - fn valtype() -> Option; - - // Does the next valtype(s) match this type? - #[doc(hidden)] - fn matches(tys: impl Iterator) -> anyhow::Result<()>; - - // Load this type's raw ABI representation from an args array. - #[doc(hidden)] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi; - - // Store this type's raw ABI representation into an args array. - #[doc(hidden)] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128); -} - /// A trait implemented for types which can be returned from closures passed to /// [`Func::wrap`] and friends. /// @@ -1231,11 +1175,6 @@ pub unsafe trait WasmRet { #[doc(hidden)] type Abi: Copy; - // The "ok" version of this, meaning that which is returned if there is no - // error. - #[doc(hidden)] - type Ok: WasmTy; - // Same as `WasmTy::compatible_with_store`. #[doc(hidden)] fn compatible_with_store(&self, store: &Store) -> bool; @@ -1249,33 +1188,24 @@ pub unsafe trait WasmRet { #[doc(hidden)] unsafe fn into_abi_for_ret(self, store: &Store) -> Self::Abi; - // Same as `WasmTy::from_abi`. - #[doc(hidden)] - unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self; - // Same as `WasmTy::push`. #[doc(hidden)] fn valtype() -> Option; - // Same as `WasmTy::matches`. + // Utilities used to convert an instance of this type to a `Result` + // explicitly, used when wrapping async functions which always bottom-out + // in a function that returns a trap because futures can be cancelled. #[doc(hidden)] - fn matches(tys: impl Iterator) -> anyhow::Result<()>; - - // Same as `WasmTy::load_from_args`. + type Fallible: WasmRet; #[doc(hidden)] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi; - - // Same as `WasmTy::store_to_args`. + fn into_fallible(self) -> Self::Fallible; #[doc(hidden)] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128); - - // Converts this result into an explicit `Result` to match on. - #[doc(hidden)] - fn into_result(self) -> Result; + fn fallible_from_trap(trap: Trap) -> Self::Fallible; } -unsafe impl WasmTy for () { - type Abi = Self; +unsafe impl WasmRet for () { + type Abi = (); + type Fallible = Result<(), Trap>; #[inline] fn compatible_with_store(&self, _store: &Store) -> bool { @@ -1283,28 +1213,27 @@ unsafe impl WasmTy for () { } #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi {} + unsafe fn into_abi_for_ret(self, _store: &Store) {} #[inline] - unsafe fn from_abi(_abi: Self::Abi, _store: &Store) -> Self {} - fn valtype() -> Option { None } - fn matches(_tys: impl Iterator) -> anyhow::Result<()> { + #[inline] + fn into_fallible(self) -> Result<(), Trap> { Ok(()) } #[inline] - unsafe fn load_from_args(_ptr: &mut *const u128) -> Self::Abi {} - - #[inline] - unsafe fn store_to_args(_abi: Self::Abi, _ptr: *mut u128) {} + fn fallible_from_trap(trap: Trap) -> Result<(), Trap> { + Err(trap) + } } -unsafe impl WasmTy for i32 { - type Abi = Self; +unsafe impl WasmRet for Result<(), Trap> { + type Abi = (); + type Fallible = Self; #[inline] fn compatible_with_store(&self, _store: &Store) -> bool { @@ -1312,358 +1241,26 @@ unsafe impl WasmTy for i32 { } #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - self - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - abi - } - - fn valtype() -> Option { - Some(ValType::I32) - } - - fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { - let next = tys.next(); - ensure!( - next == Some(ValType::I32), - "Type mismatch, expected i32, got {:?}", - next - ); - Ok(()) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - let ret = *(*ptr).cast::(); - *ptr = (*ptr).add(1); - return ret; - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - *ptr.cast::() = abi; - } -} - -unsafe impl WasmTy for u32 { - type Abi = ::Abi; - - #[inline] - fn compatible_with_store(&self, _store: &Store) -> bool { - true - } - - #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - self as i32 - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - abi as Self - } - - fn valtype() -> Option { - ::valtype() - } - - fn matches(tys: impl Iterator) -> anyhow::Result<()> { - ::matches(tys) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - ::load_from_args(ptr) - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - ::store_to_args(abi, ptr) - } -} - -unsafe impl WasmTy for i64 { - type Abi = Self; - - #[inline] - fn compatible_with_store(&self, _store: &Store) -> bool { - true - } - - #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - self - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - abi - } - - fn valtype() -> Option { - Some(ValType::I64) - } - - fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { - let next = tys.next(); - ensure!( - next == Some(ValType::I64), - "Type mismatch, expected i64, got {:?}", - next - ); - Ok(()) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - let ret = *(*ptr).cast::(); - *ptr = (*ptr).add(1); - return ret; - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - *ptr.cast::() = abi; - } -} - -unsafe impl WasmTy for u64 { - type Abi = ::Abi; - - #[inline] - fn compatible_with_store(&self, _store: &Store) -> bool { - true - } - - #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - self as i64 - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - abi as Self - } - - fn valtype() -> Option { - ::valtype() - } - - fn matches(tys: impl Iterator) -> anyhow::Result<()> { - ::matches(tys) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - ::load_from_args(ptr) - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - ::store_to_args(abi, ptr) - } -} - -unsafe impl WasmTy for f32 { - type Abi = Self; - - #[inline] - fn compatible_with_store(&self, _store: &Store) -> bool { - true - } - - #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - self - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - abi - } - - fn valtype() -> Option { - Some(ValType::F32) - } - - fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { - let next = tys.next(); - ensure!( - next == Some(ValType::F32), - "Type mismatch, expected f32, got {:?}", - next - ); - Ok(()) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - let ret = f32::from_bits(*(*ptr).cast::()); - *ptr = (*ptr).add(1); - return ret; - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - *ptr.cast::() = abi.to_bits(); - } -} - -unsafe impl WasmTy for f64 { - type Abi = Self; - - #[inline] - fn compatible_with_store(&self, _store: &Store) -> bool { - true - } - - #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - self - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - abi - } - - fn valtype() -> Option { - Some(ValType::F64) - } - - fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { - let next = tys.next(); - ensure!( - next == Some(ValType::F64), - "Type mismatch, expected f64, got {:?}", - next - ); - Ok(()) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - let ret = f64::from_bits(*(*ptr).cast::()); - *ptr = (*ptr).add(1); - return ret; - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - *ptr.cast::() = abi.to_bits(); - } -} - -unsafe impl WasmTy for Option { - type Abi = *mut u8; - - #[inline] - fn compatible_with_store(&self, _store: &Store) -> bool { - true - } - - #[inline] - fn into_abi_for_arg(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() + unsafe fn into_abi_for_ret(self, _store: &Store) { + match self { + Ok(()) => {} + Err(trap) => raise_user_trap(trap.into()), } } #[inline] - unsafe fn from_abi(abi: Self::Abi, _store: &Store) -> Self { - if abi.is_null() { - None - } else { - Some(ExternRef { - inner: VMExternRef::clone_from_raw(abi), - }) - } - } - fn valtype() -> Option { - Some(ValType::ExternRef) - } - - fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { - let next = tys.next(); - ensure!( - next == Some(ValType::ExternRef), - "Type mismatch, expected externref, got {:?}", - next - ); - Ok(()) - } - - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - let ret = *(*ptr).cast::() as *mut u8; - *ptr = (*ptr).add(1); - ret - } - - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - ptr::write(ptr.cast::(), abi as usize); - } -} - -unsafe impl WasmTy for Option { - type Abi = *mut VMCallerCheckedAnyfunc; - - #[inline] - fn compatible_with_store(&self, store: &Store) -> bool { - if let Some(f) = self { - Store::same(store, f.store()) - } else { - true - } + None } #[inline] - fn into_abi_for_arg(self, _store: &Store) -> Self::Abi { - if let Some(f) = self { - f.caller_checked_anyfunc().as_ptr() - } else { - ptr::null_mut() - } + fn into_fallible(self) -> Result<(), Trap> { + self } #[inline] - unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self { - Func::from_caller_checked_anyfunc(store, abi) - } - - fn valtype() -> Option { - Some(ValType::FuncRef) - } - - fn matches(mut tys: impl Iterator) -> anyhow::Result<()> { - let next = tys.next(); - ensure!( - next == Some(ValType::FuncRef), - "Type mismatch, expected funcref, got {:?}", - next - ); - Ok(()) - } - - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - let ret = *(*ptr).cast::() as *mut VMCallerCheckedAnyfunc; - *ptr = (*ptr).add(1); - ret - } - - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - ptr::write(ptr.cast::(), abi as usize); + fn fallible_from_trap(trap: Trap) -> Result<(), Trap> { + Err(trap) } } @@ -1672,46 +1269,27 @@ where T: WasmTy, { type Abi = ::Abi; - type Ok = T; + type Fallible = Result; - #[inline] fn compatible_with_store(&self, store: &Store) -> bool { ::compatible_with_store(self, store) } - #[inline] unsafe fn into_abi_for_ret(self, store: &Store) -> Self::Abi { - ::into_abi_for_arg(self, store) - } - - #[inline] - unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self { - ::from_abi(abi, store) + ::into_abi(self, store) } fn valtype() -> Option { - ::valtype() + Some(::valtype()) } - #[inline] - fn matches(tys: impl Iterator) -> anyhow::Result<()> { - ::matches(tys) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - ::load_from_args(ptr) - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - ::store_to_args(abi, ptr) - } - - #[inline] - fn into_result(self) -> Result { + fn into_fallible(self) -> Result { Ok(self) } + + fn fallible_from_trap(trap: Trap) -> Result { + Err(trap) + } } unsafe impl WasmRet for Result @@ -1719,9 +1297,8 @@ where T: WasmTy, { type Abi = ::Abi; - type Ok = T; + type Fallible = Self; - #[inline] fn compatible_with_store(&self, store: &Store) -> bool { match self { Ok(x) => ::compatible_with_store(x, store), @@ -1729,10 +1306,9 @@ where } } - #[inline] unsafe fn into_abi_for_ret(self, store: &Store) -> Self::Abi { match self { - Ok(val) => return ::into_abi_for_arg(val, store), + Ok(val) => return ::into_abi(val, store), Err(trap) => handle_trap(trap), } @@ -1741,33 +1317,17 @@ where } } - #[inline] - unsafe fn from_abi(abi: Self::Abi, store: &Store) -> Self { - Ok(::from_abi(abi, store)) - } - fn valtype() -> Option { - ::valtype() + Some(::valtype()) } - fn matches(tys: impl Iterator) -> anyhow::Result<()> { - ::matches(tys) - } - - #[inline] - unsafe fn load_from_args(ptr: &mut *const u128) -> Self::Abi { - ::load_from_args(ptr) - } - - #[inline] - unsafe fn store_to_args(abi: Self::Abi, ptr: *mut u128) { - ::store_to_args(abi, ptr); - } - - #[inline] - fn into_result(self) -> Result { + fn into_fallible(self) -> Result { self } + + fn fallible_from_trap(trap: Trap) -> Result { + Err(trap) + } } /// Internal trait implemented for all arguments that can be passed to @@ -1999,15 +1559,18 @@ macro_rules! impl_into_func { ) -> R::Abi, >(ptr); - let mut _next = args as *const u128; - $( let $args = $args::load_from_args(&mut _next); )* + let mut _n = 0; + $( + let $args = *args.add(_n).cast::<$args::Abi>(); + _n += 1; + )* let ret = ptr(callee_vmctx, caller_vmctx, $( $args ),*); - R::store_to_args(ret, args); + *args.cast::() = ret; } let ty = FuncType::new( None::.into_iter() - $(.chain($args::valtype()))* + $(.chain(Some($args::valtype())))* , R::valtype(), ); @@ -2040,20 +1603,6 @@ macro_rules! impl_into_func { for_each_function_signature!(impl_into_func); -/// Returned future from the [`Func::get1_async`] family of methods, used to -/// represent an asynchronous call into WebAssembly. -pub struct WasmCall { - inner: Pin>>>, -} - -impl Future for WasmCall { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.inner.as_mut().poll(cx) - } -} - #[test] fn wasm_ty_roundtrip() -> Result<(), anyhow::Error> { use crate::*; @@ -2102,10 +1651,7 @@ fn wasm_ty_roundtrip() -> Result<(), anyhow::Error> { "#, )?; let instance = Instance::new(&store, &module, &[debug.into()])?; - let foo = instance - .get_func("foo") - .unwrap() - .get6::()?; - foo(-1, 1, 2.0, -3, 3, 4.0)?; + let foo = instance.get_typed_func::<(i32, u32, f32, i64, u64, f64), ()>("foo")?; + foo.call((-1, 1, 2.0, -3, 3, 4.0))?; Ok(()) } diff --git a/crates/wasmtime/src/func/typed.rs b/crates/wasmtime/src/func/typed.rs new file mode 100644 index 0000000000..1558c91982 --- /dev/null +++ b/crates/wasmtime/src/func/typed.rs @@ -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 { + _a: marker::PhantomData Results>, + func: Func, +} + +impl Clone for TypedFunc { + fn clone(&self) -> TypedFunc { + TypedFunc { + _a: marker::PhantomData, + func: self.func.clone(), + } + } +} + +impl TypedFunc +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 { + 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 { + 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 { + // 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::( + &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 { + 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 { + 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) -> Result<()>; + #[doc(hidden)] + fn compatible_with_store(&self, store: &Store) -> bool; + #[doc(hidden)] + unsafe fn invoke( + 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 WasmParams for T +where + T: WasmTy, +{ + fn typecheck(params: impl ExactSizeIterator) -> Result<()> { + <(T,)>::typecheck(params) + } + fn compatible_with_store(&self, store: &Store) -> bool { + ::compatible_with_store(self, store) + } + unsafe fn invoke( + 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) -> 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( + 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(_: impl FnOnce(&mut [u128]) -> R) -> R; + #[doc(hidden)] + unsafe fn from_storage(ptr: *const u128, store: &Store) -> Self; +} + +unsafe impl 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(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(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); diff --git a/crates/wasmtime/src/instance.rs b/crates/wasmtime/src/instance.rs index 24b695c438..7c89b919e8 100644 --- a/crates/wasmtime/src/instance.rs +++ b/crates/wasmtime/src/instance.rs @@ -2,8 +2,9 @@ use crate::trampoline::StoreInstanceHandle; use crate::types::matching; use crate::{ 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::rc::Rc; use wasmtime_environ::entity::PrimaryMap; @@ -216,6 +217,25 @@ impl Instance { 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(&self, name: &str) -> Result> + 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::()?.clone()) + } + /// Looks up an exported [`Table`] value by name. /// /// Returns `None` if there was no export named `name`, or if there was but diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 35b1fdf7b3..6d4e847f4f 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -47,15 +47,12 @@ //! //! // Instantiation of a module requires specifying its imports and then //! // 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 hello = instance -//! .get_func("hello") -//! .ok_or(anyhow::format_err!("failed to find `hello` function export"))? -//! .get0::<()>()?; +//! let hello = instance.get_typed_func::<(), ()>("hello")?; //! //! // And finally we can call the wasm as if it were a Rust function! -//! hello()?; +//! hello.call(())?; //! //! Ok(()) //! } @@ -262,8 +259,8 @@ //! "#, //! )?; //! let instance = Instance::new(&store, &module, &[log_str.into()])?; -//! let foo = instance.get_func("foo").unwrap().get0::<()>()?; -//! foo()?; +//! let foo = instance.get_typed_func::<(), ()>("foo")?; +//! foo.call(())?; //! # Ok(()) //! # } //! ``` diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 92a6e54c47..9418cf8581 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -355,10 +355,10 @@ impl Linker { /// "#; /// let module = Module::new(store.engine(), wat)?; /// linker.module("commander", &module)?; - /// let run = linker.get_default("")?.get0::<()>()?; - /// run()?; - /// run()?; - /// run()?; + /// let run = linker.get_default("")?.typed::<(), ()>()?.clone(); + /// run.call(())?; + /// run.call(())?; + /// run.call(())?; /// /// let wat = r#" /// (module @@ -374,7 +374,8 @@ impl Linker { /// "#; /// let module = Module::new(store.engine(), wat)?; /// linker.module("", &module)?; - /// let count = linker.get_one_by_name("", Some("run"))?.into_func().unwrap().get0::()?()?; + /// 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"); /// /// # Ok(()) @@ -388,8 +389,8 @@ impl Linker { if let Some(export) = instance.get_export("_initialize") { if let Extern::Func(func) = export { - func.get0::<()>() - .and_then(|f| f().map_err(Into::into)) + func.typed::<(), ()>() + .and_then(|f| f.call(()).map_err(Into::into)) .context("calling the Reactor initialization function")?; } } diff --git a/crates/wasmtime/src/store.rs b/crates/wasmtime/src/store.rs index 4850c74460..07f528db9e 100644 --- a/crates/wasmtime/src/store.rs +++ b/crates/wasmtime/src/store.rs @@ -479,10 +479,7 @@ impl Store { /// (func (export "run") (loop br 0)) /// "#)?; /// let instance = Instance::new(&store, &module, &[])?; - /// let run = instance - /// .get_func("run") - /// .ok_or(anyhow::format_err!("failed to find `run` function export"))? - /// .get0::<()>()?; + /// let run = instance.get_typed_func::<(), ()>("run")?; /// /// // Spin up a thread to send us an interrupt in a second /// std::thread::spawn(move || { @@ -490,7 +487,7 @@ impl Store { /// interrupt_handle.interrupt(); /// }); /// - /// let trap = run().unwrap_err(); + /// let trap = run.call(()).unwrap_err(); /// assert!(trap.to_string().contains("wasm trap: interrupt")); /// # Ok(()) /// # } diff --git a/docs/lang-rust.md b/docs/lang-rust.md index aa97e97751..e5974cb312 100644 --- a/docs/lang-rust.md +++ b/docs/lang-rust.md @@ -79,13 +79,13 @@ fn main() -> Result<(), Box> { .expect("`answer` was not an exported function"); // 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 - // it takes no arguments and returns one i32) and then call it. - let answer = answer.get0::()?; + // is to statically assert its signature with `typed` (in this case + // asserting it takes no arguments and returns one i32) and then call it. + let answer = answer.typed::<(), i32>()?; // And finally we can call our function! Note that the error propagation // with `?` is done to handle the case where the wasm function traps. - let result = answer()?; + let result = answer.call(())?; println!("Answer: {:?}", result); Ok(()) } @@ -164,13 +164,8 @@ fn main() -> Result<(), Box> { // entry in the slice must line up with the imports in the module. let instance = Instance::new(&store, &module, &[log.into(), double.into()])?; - let run = instance - .get_func("run") - .expect("`run` was not an exported function"); - - let run = run.get0::<()>()?; - - Ok(run()?) + let run = instance.get_typed_func::<(), ()>("run")?; + Ok(run.call(())?) } ``` diff --git a/docs/wasm-wat.md b/docs/wasm-wat.md index 9210e67c57..9df2536993 100644 --- a/docs/wasm-wat.md +++ b/docs/wasm-wat.md @@ -48,9 +48,8 @@ let wat = r#" "#; let module = Module::new(&engine, wat)?; let instance = Instance::new(&store, &module, &[])?; -let add = instance.get_func("add").unwrap(); -let add = add.get2::()?; -println!("1 + 2 = {}", add(1, 2)?); +let add = instance.get_typed_func::<(i32, i32), i32>("add")?; +println!("1 + 2 = {}", add.call((1, 2))?); # Ok(()) # } ``` diff --git a/examples/externref.rs b/examples/externref.rs index 02bd247fda..f49f3bb6f1 100644 --- a/examples/externref.rs +++ b/examples/externref.rs @@ -40,9 +40,8 @@ fn main() -> Result<()> { assert!(global_val.ptr_eq(&externref)); println!("Calling `externref` func..."); - let func = instance.get_func("func").unwrap(); - let func = func.get1::, Option>()?; - let ret = func(Some(externref.clone()))?; + let func = instance.get_typed_func::, Option>("func")?; + let ret = func.call(Some(externref.clone()))?; assert!(ret.is_some()); assert!(ret.unwrap().ptr_eq(&externref)); diff --git a/examples/fib-debug/main.rs b/examples/fib-debug/main.rs index 3695cdb569..920cef8907 100644 --- a/examples/fib-debug/main.rs +++ b/examples/fib-debug/main.rs @@ -21,10 +21,7 @@ fn main() -> Result<()> { let instance = Instance::new(&store, &module, &[])?; // Invoke `fib` export - let fib = instance - .get_func("fib") - .ok_or(anyhow::format_err!("failed to find `fib` function export"))? - .get1::()?; - println!("fib(6) = {}", fib(6)?); + let fib = instance.get_typed_func::("fib")?; + println!("fib(6) = {}", fib.call(6)?); Ok(()) } diff --git a/examples/gcd.rs b/examples/gcd.rs index 45c87d7f26..1cc13d3781 100644 --- a/examples/gcd.rs +++ b/examples/gcd.rs @@ -15,11 +15,8 @@ fn main() -> Result<()> { let instance = Instance::new(&store, &module, &[])?; // Invoke `gcd` export - let gcd = instance - .get_func("gcd") - .ok_or(anyhow::format_err!("failed to find `gcd` function export"))? - .get2::()?; + let gcd = instance.get_typed_func::<(i32, i32), i32>("gcd")?; - println!("gcd(6, 27) = {}", gcd(6, 27)?); + println!("gcd(6, 27) = {}", gcd.call((6, 27))?); Ok(()) } diff --git a/examples/hello.rs b/examples/hello.rs index 89f904670b..7936fa82d1 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -34,14 +34,11 @@ fn main() -> Result<()> { // Next we poke around a bit to extract the `run` function from the module. println!("Extracting export..."); - let run = instance - .get_func("run") - .ok_or(anyhow::format_err!("failed to find `run` function export"))? - .get0::<()>()?; + let run = instance.get_typed_func::<(), ()>("run")?; // And last but not least we can call it! println!("Calling export..."); - run()?; + run.call(())?; println!("Done."); Ok(()) diff --git a/examples/interrupt.rs b/examples/interrupt.rs index 1c6bd3a730..3f12afe096 100644 --- a/examples/interrupt.rs +++ b/examples/interrupt.rs @@ -16,10 +16,7 @@ fn main() -> Result<()> { // Compile and instantiate a small example with an infinite loop. let module = Module::from_file(&engine, "examples/interrupt.wat")?; let instance = Instance::new(&store, &module, &[])?; - let run = instance - .get_func("run") - .ok_or(anyhow::format_err!("failed to find `run` function export"))? - .get0::<()>()?; + let run = instance.get_typed_func::<(), ()>("run")?; // Spin up a thread to send us an interrupt in a second std::thread::spawn(move || { @@ -29,7 +26,7 @@ fn main() -> Result<()> { }); println!("Entering infinite loop ..."); - let trap = run().unwrap_err(); + let trap = run.call(()).unwrap_err(); println!("trap received..."); assert!(trap.to_string().contains("wasm trap: interrupt")); diff --git a/examples/linking.rs b/examples/linking.rs index b7c862e8d9..11f4022bcd 100644 --- a/examples/linking.rs +++ b/examples/linking.rs @@ -34,8 +34,7 @@ fn main() -> Result<()> { // And with that we can perform the final link and the execute the module. let linking1 = linker.instantiate(&linking1)?; - let run = linking1.get_func("run").unwrap(); - let run = run.get0::<()>()?; - run()?; + let run = linking1.get_typed_func::<(), ()>("run")?; + run.call(())?; Ok(()) } diff --git a/examples/memory.rs b/examples/memory.rs index 9de8488ae2..70e1b724a9 100644 --- a/examples/memory.rs +++ b/examples/memory.rs @@ -20,18 +20,9 @@ fn main() -> Result<()> { let memory = instance .get_memory("memory") .ok_or(anyhow::format_err!("failed to find `memory` export"))?; - let size = instance - .get_func("size") - .ok_or(anyhow::format_err!("failed to find `size` export"))? - .get0::()?; - let load = instance - .get_func("load") - .ok_or(anyhow::format_err!("failed to find `load` export"))? - .get1::()?; - let store = instance - .get_func("store") - .ok_or(anyhow::format_err!("failed to find `store` export"))? - .get2::()?; + let size = instance.get_typed_func::<(), i32>("size")?; + let load = instance.get_typed_func::("load")?; + let store = instance.get_typed_func::<(i32, i32), ()>("store")?; // Note that these memory reads are *unsafe* due to unknown knowledge about // 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!(size()?, 2); - assert_eq!(load(0)?, 0); - assert_eq!(load(0x1000)?, 1); - assert_eq!(load(0x1003)?, 4); - assert_eq!(load(0x1ffff)?, 0); - assert!(load(0x20000).is_err()); // out of bounds trap + assert_eq!(size.call(())?, 2); + assert_eq!(load.call(0)?, 0); + assert_eq!(load.call(0x1000)?, 1); + assert_eq!(load.call(0x1003)?, 4); + assert_eq!(load.call(0x1ffff)?, 0); + assert!(load.call(0x20000).is_err()); // out of bounds trap println!("Mutating memory..."); unsafe { memory.data_unchecked_mut()[0x1003] = 5; } - store(0x1002, 6)?; - assert!(store(0x20000, 0).is_err()); // out of bounds trap + store.call((0x1002, 6))?; + assert!(store.call((0x20000, 0)).is_err()); // out of bounds trap unsafe { assert_eq!(memory.data_unchecked_mut()[0x1002], 6); assert_eq!(memory.data_unchecked_mut()[0x1003], 5); } - assert_eq!(load(0x1002)?, 6); - assert_eq!(load(0x1003)?, 5); + assert_eq!(load.call(0x1002)?, 6); + assert_eq!(load.call(0x1003)?, 5); // Grow memory. println!("Growing memory..."); @@ -74,10 +65,10 @@ fn main() -> Result<()> { assert_eq!(memory.size(), 3); assert_eq!(memory.data_size(), 0x30000); - assert_eq!(load(0x20000)?, 0); - store(0x20000, 0)?; - assert!(load(0x30000).is_err()); - assert!(store(0x30000, 0).is_err()); + assert_eq!(load.call(0x20000)?, 0); + store.call((0x20000, 0))?; + assert!(load.call(0x30000).is_err()); + assert!(store.call((0x30000, 0)).is_err()); assert!(memory.grow(1).is_err()); assert!(memory.grow(0).is_ok()); diff --git a/examples/multi.rs b/examples/multi.rs index 67f1c4b6ee..cb2d20bf8f 100644 --- a/examples/multi.rs +++ b/examples/multi.rs @@ -7,7 +7,7 @@ // You can execute this example with `cargo run --example multi` -use anyhow::{format_err, Result}; +use anyhow::Result; use wasmtime::*; fn main() -> Result<()> { @@ -40,51 +40,31 @@ fn main() -> Result<()> { // Extract exports. println!("Extracting export..."); - let g = instance - .get_func("g") - .ok_or(format_err!("failed to find export `g`"))?; + let g = instance.get_typed_func::<(i32, i64), (i64, i32)>("g")?; // Call `$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!("> {} {}", results[0].unwrap_i64(), results[1].unwrap_i32()); + println!("> {} {}", a, b); - assert_eq!(results[0].unwrap_i64(), 4); - assert_eq!(results[1].unwrap_i32(), 2); + assert_eq!(a, 4); + assert_eq!(b, 2); // Call `$round_trip_many`. println!("Calling export \"round_trip_many\"..."); let round_trip_many = instance - .get_func("round_trip_many") - .ok_or(format_err!("failed to find export `round_trip_many`"))?; - let args = vec![ - Val::I64(0), - Val::I64(1), - Val::I64(2), - 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)?; + .get_typed_func::< + (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64), + (i64, i64, i64, i64, i64, i64, i64, i64, i64, i64), + > + ("round_trip_many")?; + let results = round_trip_many.call((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))?; println!("Printing result..."); - print!(">"); - for r in results.iter() { - print!(" {}", r.unwrap_i64()); - } - println!(); - - assert_eq!(results.len(), 10); - assert!(args - .iter() - .zip(results.iter()) - .all(|(a, r)| a.i64() == r.i64())); + println!("> {:?}", results); + assert_eq!(results, (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); Ok(()) } diff --git a/examples/serialize.rs b/examples/serialize.rs index ce69c76057..dd30b47a98 100644 --- a/examples/serialize.rs +++ b/examples/serialize.rs @@ -50,14 +50,11 @@ fn deserialize(buffer: &[u8]) -> Result<()> { // Next we poke around a bit to extract the `run` function from the module. println!("Extracting export..."); - let run = instance - .get_func("run") - .ok_or(anyhow::format_err!("failed to find `run` function export"))? - .get0::<()>()?; + let run = instance.get_typed_func::<(), ()>("run")?; // And last but not least we can call it! println!("Calling export..."); - run()?; + run.call(())?; println!("Done."); Ok(()) diff --git a/examples/threads.rs b/examples/threads.rs index b83a1da3ba..b51e8fe41b 100644 --- a/examples/threads.rs +++ b/examples/threads.rs @@ -1,6 +1,6 @@ // You can execute this example with `cargo run --example threads` -use anyhow::{format_err, Result}; +use anyhow::Result; use std::thread; use std::time; use wasmtime::*; @@ -26,14 +26,12 @@ fn run(engine: &Engine, module: Module, id: i32) -> Result<()> { // Extract exports. println!("Extracting export..."); - let g = instance - .get_func("run") - .ok_or(format_err!("failed to find export `run`"))?; + let g = instance.get_typed_func::<(), ()>("run")?; for _ in 0..N_REPS { thread::sleep(time::Duration::from_millis(100)); // Call `$run`. - drop(g.call(&[])?); + drop(g.call(())?); } Ok(()) diff --git a/examples/wasi/main.rs b/examples/wasi/main.rs index d279ae7816..f1487171f7 100644 --- a/examples/wasi/main.rs +++ b/examples/wasi/main.rs @@ -37,7 +37,7 @@ fn main() -> Result<()> { // 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")?; linker.module("", &module)?; - linker.get_default("")?.get0::<()>()?()?; + linker.get_default("")?.typed::<(), ()>()?.call(())?; Ok(()) } diff --git a/tests/all/async_functions.rs b/tests/all/async_functions.rs index ce084f0dca..d065fd4f3c 100644 --- a/tests/all/async_functions.rs +++ b/tests/all/async_functions.rs @@ -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(future: F) { diff --git a/tests/all/custom_signal_handler.rs b/tests/all/custom_signal_handler.rs index 4bb2535e43..805f759935 100644 --- a/tests/all/custom_signal_handler.rs +++ b/tests/all/custom_signal_handler.rs @@ -41,8 +41,8 @@ mod tests { ) "#; - fn invoke_export(instance: &Instance, func_name: &str) -> Result> { - let ret = instance.get_func(func_name).unwrap().call(&[])?; + fn invoke_export(instance: &Instance, func_name: &str) -> Result { + 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::()?; + 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(()) } } diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 5c75ec28c0..9bffa2c08d 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -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::, ()>()?; + let s2_f_t = s2_f.typed::, ()>()?; + + 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(()) } diff --git a/tests/all/func.rs b/tests/all/func.rs index 36cc918c28..73166cc891 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -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::().is_err()); - assert!(f.get1::<(), ()>().is_ok()); - assert!(f.get1::().is_err()); - assert!(f.get1::().is_err()); - assert!(f.get2::<(), (), ()>().is_ok()); - assert!(f.get2::().is_err()); - assert!(f.get2::().is_err()); + assert!(f.typed::<(), ()>().is_ok()); + assert!(f.typed::<(), i32>().is_err()); + assert!(f.typed::<(), ()>().is_ok()); + assert!(f.typed::().is_err()); + assert!(f.typed::().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::().is_ok()); + assert!(f.typed::<(), i32>().is_ok()); let f = Func::wrap(&store, || -> f32 { loop {} }); - assert!(f.get0::().is_ok()); + assert!(f.typed::<(), f32>().is_ok()); let f = Func::wrap(&store, || -> f64 { loop {} }); - assert!(f.get0::().is_ok()); + assert!(f.typed::<(), f64>().is_ok()); let f = Func::wrap(&store, || -> Option { loop {} }); - assert!(f.get0::>().is_ok()); + assert!(f.typed::<(), Option>().is_ok()); let f = Func::wrap(&store, || -> Option { loop {} }); - assert!(f.get0::>().is_ok()); + assert!(f.typed::<(), Option>().is_ok()); let f = Func::wrap(&store, |_: i32| {}); - assert!(f.get1::().is_ok()); - assert!(f.get1::().is_err()); - assert!(f.get1::().is_err()); - assert!(f.get1::().is_err()); + assert!(f.typed::().is_ok()); + assert!(f.typed::().is_err()); + assert!(f.typed::().is_err()); + assert!(f.typed::().is_err()); let f = Func::wrap(&store, |_: i64| {}); - assert!(f.get1::().is_ok()); + assert!(f.typed::().is_ok()); let f = Func::wrap(&store, |_: f32| {}); - assert!(f.get1::().is_ok()); + assert!(f.typed::().is_ok()); let f = Func::wrap(&store, |_: f64| {}); - assert!(f.get1::().is_ok()); + assert!(f.typed::().is_ok()); let f = Func::wrap(&store, |_: Option| {}); - assert!(f.get1::, ()>().is_ok()); + assert!(f.typed::, ()>().is_ok()); let f = Func::wrap(&store, |_: Option| {}); - assert!(f.get1::, ()>().is_ok()); + assert!(f.typed::, ()>().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::().is_err()); - assert!(f.get1::().is_err()); + assert!(f.typed::<(), ()>().is_ok()); + assert!(f.typed::<(), i32>().is_err()); + assert!(f.typed::().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::().is_err()); - assert!(f.get1::().is_err()); - assert!(f.get1::().is_ok()); + assert!(f.typed::<(), ()>().is_err()); + assert!(f.typed::<(), i32>().is_err()); + assert!(f.typed::().is_err()); + assert!(f.typed::().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::().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::().is_ok()); - assert!(f1.get1::().is_err()); + assert!(f1.typed::<(), ()>().is_err()); + assert!(f1.typed::().is_ok()); + assert!(f1.typed::().is_err()); let f2 = instance.get_func("f2").unwrap(); - assert!(f2.get0::<()>().is_err()); - assert!(f2.get0::().is_ok()); - assert!(f2.get1::().is_err()); - assert!(f2.get1::().is_err()); + assert!(f2.typed::<(), ()>().is_err()); + assert!(f2.typed::<(), i32>().is_ok()); + assert!(f2.typed::().is_err()); + assert!(f2.typed::().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::()?(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::()?()?, 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::()?()?, 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::()?()?, 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::()?()?, 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::, ()>()?; - let result = f(Some(store2_func)); + let f = store1_func.typed::, ()>()?; + 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(()) +} diff --git a/tests/all/host_funcs.rs b/tests/all/host_funcs.rs index 365a8ec7ed..de94701144 100644 --- a/tests/all/host_funcs.rs +++ b/tests/all/host_funcs.rs @@ -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::().is_err()); - assert!(f.get1::().is_err()); + assert!(f.typed::<(), ()>().is_ok()); + assert!(f.typed::<(), i32>().is_err()); + assert!(f.typed::().is_err()); let f = store.get_host_func("", "f2").expect("func defined"); - assert!(f.get0::<()>().is_err()); - assert!(f.get0::().is_err()); - assert!(f.get1::().is_err()); - assert!(f.get1::().is_ok()); + assert!(f.typed::<(), ()>().is_err()); + assert!(f.typed::<(), i32>().is_err()); + assert!(f.typed::().is_err()); + assert!(f.typed::().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::()?(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::()?()?, 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::()?()?, 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::()?()?, 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::()?()?, 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(()) diff --git a/tests/all/iloop.rs b/tests/all/iloop.rs index a62fc76dea..847334782a 100644 --- a/tests/all/iloop.rs +++ b/tests/all/iloop.rs @@ -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"), diff --git a/tests/all/import_indexes.rs b/tests/all/import_indexes.rs index 3577467c3b..bfa6865270 100644 --- a/tests/all/import_indexes.rs +++ b/tests/all/import_indexes.rs @@ -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(()) } diff --git a/tests/all/linker.rs b/tests/all/linker.rs index 2cb10d4074..078509774d 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -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::()?; - 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::()?; - 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::()?; - assert_eq!(func()?, 112); + let func = func.typed::<(), i32>()?; + assert_eq!(func.call(())?, 112); Ok(()) } diff --git a/tests/all/module_serialize.rs b/tests/all/module_serialize.rs index 4b142f9d2e..a77f62b077 100644 --- a/tests/all/module_serialize.rs +++ b/tests/all/module_serialize.rs @@ -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::()?; - let result = run()?; + let run = instance.get_typed_func::<(), i32>("run")?; + let result = run.call(())?; assert_eq!(42, result); Ok(()) diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs index 92a0fdc348..4f0e3307a4 100644 --- a/tests/all/pooling_allocator.rs +++ b/tests/all/pooling_allocator.rs @@ -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::().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::().unwrap(); + let f = instance.get_typed_func::("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::().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 diff --git a/tests/all/stack_overflow.rs b/tests/all/stack_overflow.rs index 97e9dc1a08..62c37707cb 100644 --- a/tests/all/stack_overflow.rs +++ b/tests/all/stack_overflow.rs @@ -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"), "{}", diff --git a/tests/all/traps.rs b/tests/all/traps.rs index 6fd7f10779..b83ac38daa 100644 --- a/tests/all/traps.rs +++ b/tests/all/traps.rs @@ -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::()?; + 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::()?; + 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::()?; + 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::()?; + 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) { diff --git a/tests/all/use_after_drop.rs b/tests/all/use_after_drop.rs index 293dacafd0..31d472895a 100644 --- a/tests/all/use_after_drop.rs +++ b/tests/all/use_after_drop.rs @@ -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(()) }