From 2697a18d2f6907ab957ca6578389030b55cc55a9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 11 Mar 2021 14:43:34 -0600 Subject: [PATCH] Redo the statically typed `Func` API (#2719) * Redo the statically typed `Func` API This commit reimplements the `Func` API with respect to statically typed dispatch. Previously `Func` had a `getN` and `getN_async` family of methods which were implemented for 0 to 16 parameters. The return value of these functions was an `impl Fn(..)` closure with the appropriate parameters and return values. There are a number of downsides with this approach that have become apparent over time: * The addition of `*_async` doubled the API surface area (which is quite large here due to one-method-per-number-of-parameters). * The [documentation of `Func`][old-docs] are quite verbose and feel "polluted" with all these getters, making it harder to understand the other methods that can be used to interact with a `Func`. * These methods unconditionally pay the cost of returning an owned `impl Fn` with a `'static` lifetime. While cheap, this is still paying the cost for cloning the `Store` effectively and moving data into the closed-over environment. * Storage of the return value into a struct, for example, always requires `Box`-ing the returned closure since it otherwise cannot be named. * Recently I had the desire to implement an "unchecked" path for invoking wasm where you unsafely assert the type signature of a wasm function. Doing this with today's scheme would require doubling (again) the API surface area for both async and synchronous calls, further polluting the documentation. The main benefit of the previous scheme is that by returning a `impl Fn` it was quite easy and ergonomic to actually invoke the function. In practice, though, examples would often have something akin to `.get0::<()>()?()?` which is a lot of things to interpret all at once. Note that `get0` means "0 parameters" yet a type parameter is passed. There's also a double function invocation which looks like a lot of characters all lined up in a row. Overall, I think that the previous design is starting to show too many cracks and deserves a rewrite. This commit is that rewrite. The new design in this commit is to delete the `getN{,_async}` family of functions and instead have a new API: impl Func { fn typed(&self) -> Result<&Typed>; } impl Typed { fn call(&self, params: P) -> Result; async fn call_async(&self, params: P) -> Result; } This should entirely replace the current scheme, albeit by slightly losing ergonomics use cases. The idea behind the API is that the existence of `Typed` is a "proof" that the underlying function takes `P` and returns `R`. The `Func::typed` method peforms a runtime type-check to ensure that types all match up, and if successful you get a `Typed` value. Otherwise an error is returned. Once you have a `Typed` then, like `Func`, you can either `call` or `call_async`. The difference with a `Typed`, however, is that the params/results are statically known and hence these calls can be much more efficient. This is a much smaller API surface area from before and should greatly simplify the `Func` documentation. There's still a problem where `Func::wrapN_async` produces a lot of functions to document, but that's now the sole offender. It's a nice benefit that the statically-typed-async verisons are now expressed with an `async` function rather than a function-returning-a-future which makes it both more efficient and easier to understand. The type `P` and `R` are intended to either be bare types (e.g. `i32`) or tuples of any length (including 0). At this time `R` is only allowed to be `()` or a bare `i32`-style type because multi-value is not supported with a native ABI (yet). The `P`, however, can be any size of tuples of parameters. This is also where some ergonomics are lost because instead of `f(1, 2)` you now have to write `f.call((1, 2))` (note the double-parens). Similarly `f()` becomes `f.call(())`. Overall I feel that this is a better tradeoff than before. While not universally better due to the loss in ergonomics I feel that this design is much more flexible in terms of what you can do with the return value and also understanding the API surface area (just less to take in). [old-docs]: https://docs.rs/wasmtime/0.24.0/wasmtime/struct.Func.html#method.get0 * Rename Typed to TypedFunc * Implement multi-value returns through `Func::typed` * Fix examples in docs * Fix some more errors * More test fixes * Rebasing and adding `get_typed_func` * Updating tests * Fix typo * More doc tweaks * Tweak visibility on `Func::invoke` * Fix tests again --- crates/bench-api/src/lib.rs | 5 +- .../tests/wasm_tests/runtime/cap_std_sync.rs | 10 +- crates/wasmtime/src/config.rs | 4 +- crates/wasmtime/src/func.rs | 918 +++++------------- crates/wasmtime/src/func/typed.rs | 488 ++++++++++ crates/wasmtime/src/instance.rs | 22 +- crates/wasmtime/src/lib.rs | 13 +- crates/wasmtime/src/linker.rs | 15 +- crates/wasmtime/src/store.rs | 7 +- docs/lang-rust.md | 17 +- docs/wasm-wat.md | 5 +- examples/externref.rs | 5 +- examples/fib-debug/main.rs | 7 +- examples/gcd.rs | 7 +- examples/hello.rs | 7 +- examples/interrupt.rs | 7 +- examples/linking.rs | 5 +- examples/memory.rs | 43 +- examples/multi.rs | 48 +- examples/serialize.rs | 7 +- examples/threads.rs | 8 +- examples/wasi/main.rs | 2 +- tests/all/async_functions.rs | 32 +- tests/all/custom_signal_handler.rs | 32 +- tests/all/externals.rs | 10 + tests/all/func.rs | 122 ++- tests/all/host_funcs.rs | 34 +- tests/all/iloop.rs | 16 +- tests/all/import_indexes.rs | 10 +- tests/all/linker.rs | 12 +- tests/all/module_serialize.rs | 7 +- tests/all/pooling_allocator.rs | 34 +- tests/all/stack_overflow.rs | 4 +- tests/all/traps.rs | 58 +- tests/all/use_after_drop.rs | 4 +- 35 files changed, 1013 insertions(+), 1012 deletions(-) create mode 100644 crates/wasmtime/src/func/typed.rs 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(()) }