From 69d160c5f7162a62cb18ebae6173e528a41cec34 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 17 Jan 2020 09:06:24 -0800 Subject: [PATCH 1/2] Move `Func` to its own file --- crates/api/src/externals.rs | 122 +----------------------------------- crates/api/src/func.rs | 120 +++++++++++++++++++++++++++++++++++ crates/api/src/lib.rs | 2 + crates/api/src/values.rs | 4 +- 4 files changed, 125 insertions(+), 123 deletions(-) create mode 100644 crates/api/src/func.rs diff --git a/crates/api/src/externals.rs b/crates/api/src/externals.rs index de9c373e18ad..fda3d1b6355a 100644 --- a/crates/api/src/externals.rs +++ b/crates/api/src/externals.rs @@ -1,12 +1,9 @@ -use crate::callable::{Callable, NativeCallable, WasmtimeFn, WrappedCallable}; -use crate::runtime::Store; use crate::trampoline::{generate_global_export, generate_memory_export, generate_table_export}; -use crate::trap::Trap; -use crate::types::{ExternType, FuncType, GlobalType, MemoryType, TableType, ValType}; use crate::values::{from_checked_anyfunc, into_checked_anyfunc, Val}; use crate::Mutability; +use crate::{ExternType, GlobalType, MemoryType, TableType, ValType}; +use crate::{Func, Store}; use anyhow::{anyhow, bail, Result}; -use std::fmt; use std::rc::Rc; use std::slice; use wasmtime_environ::wasm; @@ -140,121 +137,6 @@ impl From for Extern { } } -/// A WebAssembly function which can be called. -/// -/// This type can represent a number of callable items, such as: -/// -/// * An exported function from a WebAssembly module. -/// * A user-defined function used to satisfy an import. -/// -/// These types of callable items are all wrapped up in this `Func` and can be -/// used to both instantiate an [`Instance`](crate::Instance) as well as be -/// extracted from an [`Instance`](crate::Instance). -/// -/// # `Func` and `Clone` -/// -/// Functions are internally reference counted so you can `clone` a `Func`. The -/// cloning process only performs a shallow clone, so two cloned `Func` -/// instances are equivalent in their functionality. -#[derive(Clone)] -pub struct Func { - _store: Store, - callable: Rc, - ty: FuncType, -} - -impl Func { - /// Creates a new `Func` with the given arguments, typically to create a - /// user-defined function to pass as an import to a module. - /// - /// * `store` - a cache of data where information is stored, typically - /// shared with a [`Module`](crate::Module). - /// - /// * `ty` - the signature of this function, used to indicate what the - /// inputs and outputs are, which must be WebAssembly types. - /// - /// * `callable` - a type implementing the [`Callable`] trait which - /// is the implementation of this `Func` value. - /// - /// Note that the implementation of `callable` must adhere to the `ty` - /// signature given, error or traps may occur if it does not respect the - /// `ty` signature. - pub fn new(store: &Store, ty: FuncType, callable: Rc) -> Self { - let callable = Rc::new(NativeCallable::new(callable, &ty, &store)); - Func::from_wrapped(store, ty, callable) - } - - fn from_wrapped( - store: &Store, - ty: FuncType, - callable: Rc, - ) -> Func { - Func { - _store: store.clone(), - callable, - ty, - } - } - - /// Returns the underlying wasm type that this `Func` has. - pub fn ty(&self) -> &FuncType { - &self.ty - } - - /// Returns the number of parameters that this function takes. - pub fn param_arity(&self) -> usize { - self.ty.params().len() - } - - /// Returns the number of results this function produces. - pub fn result_arity(&self) -> usize { - self.ty.results().len() - } - - /// Invokes this function with the `params` given, returning the results and - /// any trap, if one occurs. - /// - /// The `params` here must match the type signature of this `Func`, or a - /// trap will occur. If a trap occurs while executing this function, then a - /// trap will also be returned. - /// - /// This function should not panic unless the underlying function itself - /// initiates a panic. - pub fn call(&self, params: &[Val]) -> Result, Trap> { - let mut results = vec![Val::null(); self.result_arity()]; - self.callable.call(params, &mut results)?; - Ok(results.into_boxed_slice()) - } - - pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export { - self.callable.wasmtime_export() - } - - pub(crate) fn from_wasmtime_function( - export: wasmtime_runtime::Export, - store: &Store, - instance_handle: InstanceHandle, - ) -> Self { - // This is only called with `Export::Function`, and since it's coming - // from wasmtime_runtime itself we should support all the types coming - // out of it, so assert such here. - let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export { - FuncType::from_wasmtime_signature(signature.clone()) - .expect("core wasm signature should be supported") - } else { - panic!("expected function export") - }; - let callable = WasmtimeFn::new(store, instance_handle, export); - Func::from_wrapped(store, ty, Rc::new(callable)) - } -} - -impl fmt::Debug for Func { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Func") - } -} - /// A WebAssembly `global` value which can be read and written to. /// /// A `global` in WebAssembly is sort of like a global variable within an diff --git a/crates/api/src/func.rs b/crates/api/src/func.rs new file mode 100644 index 000000000000..1cb6dd732502 --- /dev/null +++ b/crates/api/src/func.rs @@ -0,0 +1,120 @@ +use crate::{Store, FuncType, Callable, Val, Trap}; +use crate::callable::{WrappedCallable, WasmtimeFn, NativeCallable}; +use std::rc::Rc; +use std::fmt; +use wasmtime_jit::InstanceHandle; + +/// A WebAssembly function which can be called. +/// +/// This type can represent a number of callable items, such as: +/// +/// * An exported function from a WebAssembly module. +/// * A user-defined function used to satisfy an import. +/// +/// These types of callable items are all wrapped up in this `Func` and can be +/// used to both instantiate an [`Instance`](crate::Instance) as well as be +/// extracted from an [`Instance`](crate::Instance). +/// +/// # `Func` and `Clone` +/// +/// Functions are internally reference counted so you can `clone` a `Func`. The +/// cloning process only performs a shallow clone, so two cloned `Func` +/// instances are equivalent in their functionality. +#[derive(Clone)] +pub struct Func { + _store: Store, + callable: Rc, + ty: FuncType, +} + +impl Func { + /// Creates a new `Func` with the given arguments, typically to create a + /// user-defined function to pass as an import to a module. + /// + /// * `store` - a cache of data where information is stored, typically + /// shared with a [`Module`](crate::Module). + /// + /// * `ty` - the signature of this function, used to indicate what the + /// inputs and outputs are, which must be WebAssembly types. + /// + /// * `callable` - a type implementing the [`Callable`] trait which + /// is the implementation of this `Func` value. + /// + /// Note that the implementation of `callable` must adhere to the `ty` + /// signature given, error or traps may occur if it does not respect the + /// `ty` signature. + pub fn new(store: &Store, ty: FuncType, callable: Rc) -> Self { + let callable = Rc::new(NativeCallable::new(callable, &ty, &store)); + Func::from_wrapped(store, ty, callable) + } + + fn from_wrapped( + store: &Store, + ty: FuncType, + callable: Rc, + ) -> Func { + Func { + _store: store.clone(), + callable, + ty, + } + } + + /// Returns the underlying wasm type that this `Func` has. + pub fn ty(&self) -> &FuncType { + &self.ty + } + + /// Returns the number of parameters that this function takes. + pub fn param_arity(&self) -> usize { + self.ty.params().len() + } + + /// Returns the number of results this function produces. + pub fn result_arity(&self) -> usize { + self.ty.results().len() + } + + /// Invokes this function with the `params` given, returning the results and + /// any trap, if one occurs. + /// + /// The `params` here must match the type signature of this `Func`, or a + /// trap will occur. If a trap occurs while executing this function, then a + /// trap will also be returned. + /// + /// This function should not panic unless the underlying function itself + /// initiates a panic. + pub fn call(&self, params: &[Val]) -> Result, Trap> { + let mut results = vec![Val::null(); self.result_arity()]; + self.callable.call(params, &mut results)?; + Ok(results.into_boxed_slice()) + } + + pub(crate) fn wasmtime_export(&self) -> &wasmtime_runtime::Export { + self.callable.wasmtime_export() + } + + pub(crate) fn from_wasmtime_function( + export: wasmtime_runtime::Export, + store: &Store, + instance_handle: InstanceHandle, + ) -> Self { + // This is only called with `Export::Function`, and since it's coming + // from wasmtime_runtime itself we should support all the types coming + // out of it, so assert such here. + let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export { + FuncType::from_wasmtime_signature(signature.clone()) + .expect("core wasm signature should be supported") + } else { + panic!("expected function export") + }; + let callable = WasmtimeFn::new(store, instance_handle, export); + Func::from_wrapped(store, ty, Rc::new(callable)) + } +} + +impl fmt::Debug for Func { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Func") + } +} diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 0858b7e97196..6e130790e1f7 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -10,6 +10,7 @@ mod callable; mod externals; +mod func; mod instance; mod module; mod r#ref; @@ -21,6 +22,7 @@ mod values; pub use crate::callable::Callable; pub use crate::externals::*; +pub use crate::func::Func; pub use crate::instance::Instance; pub use crate::module::Module; pub use crate::r#ref::{AnyRef, HostInfo, HostRef}; diff --git a/crates/api/src/values.rs b/crates/api/src/values.rs index 446a1c31a254..b61f11124f81 100644 --- a/crates/api/src/values.rs +++ b/crates/api/src/values.rs @@ -1,7 +1,5 @@ -use crate::externals::Func; use crate::r#ref::AnyRef; -use crate::runtime::Store; -use crate::types::ValType; +use crate::{Func, Store, ValType}; use anyhow::{bail, Result}; use std::ptr; use wasmtime_environ::ir; From 695289d6923f73e755862d6ad1d9e24ccbdcbe2a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 17 Jan 2020 10:52:38 -0800 Subject: [PATCH 2/2] Support `Func` imports with zero shims This commit extends the `Func` type in the `wasmtime` crate with static `wrap*` constructors. The goal of these constructors is to create a `Func` type which has zero shims associated with it, creating as small of a layer as possible between wasm code and calling imported Rust code. This is achieved by creating an `extern "C"` shim function which matches the ABI of what Cranelift will generate, and then the host function is passed directly into an `InstanceHandle` to get called later. This also enables enough inlining opportunities that LLVM will be able to see all functions and inline everything to the point where your function is called immediately from wasm, no questions asked. --- crates/api/examples/hello.rs | 17 +-- crates/api/src/func.rs | 215 +++++++++++++++++++++++++++++- crates/api/src/lib.rs | 2 +- crates/api/src/trampoline/func.rs | 33 +++++ crates/api/src/trampoline/mod.rs | 16 +++ crates/api/tests/func.rs | 203 ++++++++++++++++++++++++++++ crates/api/tests/traps.rs | 30 ++++- crates/runtime/src/lib.rs | 1 + crates/wast/src/spectest.rs | 64 ++------- 9 files changed, 510 insertions(+), 71 deletions(-) create mode 100644 crates/api/tests/func.rs diff --git a/crates/api/examples/hello.rs b/crates/api/examples/hello.rs index 317e0361f34b..9a461569ba8b 100644 --- a/crates/api/examples/hello.rs +++ b/crates/api/examples/hello.rs @@ -1,19 +1,8 @@ //! Translation of hello example use anyhow::{ensure, Context as _, Result}; -use std::rc::Rc; use wasmtime::*; -struct HelloCallback; - -impl Callable for HelloCallback { - fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> { - println!("Calling back..."); - println!("> Hello World!"); - Ok(()) - } -} - fn main() -> Result<()> { // Configure the initial compilation environment, creating the global // `Store` structure. Note that you can also tweak configuration settings @@ -34,8 +23,10 @@ fn main() -> Result<()> { // Here we handle the imports of the module, which in this case is our // `HelloCallback` type and its associated implementation of `Callback. println!("Creating callback..."); - let hello_type = FuncType::new(Box::new([]), Box::new([])); - let hello_func = Func::new(&store, hello_type, Rc::new(HelloCallback)); + let hello_func = Func::wrap0(&store, || { + println!("Calling back..."); + println!("> Hello World!"); + }); // Once we've got that all set up we can then move to the instantiation // phase, pairing together a compiled module as well as a set of imports. diff --git a/crates/api/src/func.rs b/crates/api/src/func.rs index 1cb6dd732502..ff040b2b0c4f 100644 --- a/crates/api/src/func.rs +++ b/crates/api/src/func.rs @@ -1,8 +1,10 @@ -use crate::{Store, FuncType, Callable, Val, Trap}; -use crate::callable::{WrappedCallable, WasmtimeFn, NativeCallable}; -use std::rc::Rc; +use crate::callable::{NativeCallable, WasmtimeFn, WrappedCallable}; +use crate::{Callable, FuncType, Store, Trap, Val, ValType}; use std::fmt; +use std::panic::{self, AssertUnwindSafe}; +use std::rc::Rc; use wasmtime_jit::InstanceHandle; +use wasmtime_runtime::VMContext; /// A WebAssembly function which can be called. /// @@ -27,6 +29,59 @@ pub struct Func { ty: FuncType, } +macro_rules! wrappers { + ($( + $(#[$doc:meta])* + ($name:ident $(,$args:ident)*) + )*) => ($( + $(#[$doc])* + pub fn $name(store: &Store, func: F) -> Func + where + F: Fn($($args),*) -> R + 'static, + $($args: WasmArg,)* + R: WasmRet, + { + #[allow(non_snake_case)] + unsafe extern "C" fn shim( + vmctx: *mut VMContext, + _caller_vmctx: *mut VMContext, + $($args: $args,)* + ) -> R::Abi + where + F: Fn($($args),*) -> R + 'static, + R: WasmRet, + { + let ret = { + let instance = InstanceHandle::from_vmctx(vmctx); + let func = instance.host_state().downcast_ref::().expect("state"); + panic::catch_unwind(AssertUnwindSafe(|| func($($args),*))) + }; + match ret { + Ok(ret) => ret.into_abi(), + Err(panic) => wasmtime_runtime::resume_panic(panic), + } + } + + let mut _args = Vec::new(); + $($args::push(&mut _args);)* + let mut ret = Vec::new(); + R::push(&mut ret); + let ty = FuncType::new(_args.into(), ret.into()); + unsafe { + let (instance, export) = crate::trampoline::generate_raw_func_export( + &ty, + shim:: as *const _, + store, + Box::new(func), + ) + .expect("failed to generate export"); + let callable = Rc::new(WasmtimeFn::new(store, instance, export)); + Func::from_wrapped(store, ty, callable) + } + } + )*) +} + impl Func { /// Creates a new `Func` with the given arguments, typically to create a /// user-defined function to pass as an import to a module. @@ -48,6 +103,71 @@ impl Func { Func::from_wrapped(store, ty, callable) } + wrappers! { + /// Creates a new `Func` from the given Rust closure, which takes 0 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap0) + + /// Creates a new `Func` from the given Rust closure, which takes 1 + /// argument. + /// + /// This function will create a new `Func` which, when called, will + /// execute the given Rust closure. Unlike [`Func::new`] the target + /// function being called is known statically so the type signature can + /// be inferred. Rust types will map to WebAssembly types as follows: + /// + /// + /// | Rust Argument Type | WebAssembly Type | + /// |--------------------|------------------| + /// | `i32` | `i32` | + /// | `i64` | `i64` | + /// | `f32` | `f32` | + /// | `f64` | `f64` | + /// | (not supported) | `v128` | + /// | (not supported) | `anyref` | + /// + /// Any of the Rust types can be returned from the closure as well, in + /// addition to some extra types + /// + /// | Rust Return Type | WebAssembly Return Type | Meaning | + /// |-------------------|-------------------------|-------------------| + /// | `()` | nothing | no return value | + /// | `Result` | `T` | function may trap | + /// + /// Note that when using this API (and the related `wrap*` family of + /// functions), the intention is to create as thin of a layer as + /// possible for when WebAssembly calls the function provided. With + /// sufficient inlining and optimization the WebAssembly will call + /// straight into `func` provided, with no extra fluff entailed. + (wrap1, A) + + /// Creates a new `Func` from the given Rust closure, which takes 2 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap2, A, B) + + /// Creates a new `Func` from the given Rust closure, which takes 3 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap3, A, B, C) + + /// Creates a new `Func` from the given Rust closure, which takes 4 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap4, A, B, C, D) + + /// Creates a new `Func` from the given Rust closure, which takes 5 + /// arguments. + /// + /// For more information about this function, see [`Func::wrap1`]. + (wrap5, A, B, C, D, E) + } + fn from_wrapped( store: &Store, ty: FuncType, @@ -118,3 +238,92 @@ impl fmt::Debug for Func { write!(f, "Func") } } + +/// A trait implemented for types which can be arguments to closures passed to +/// [`Func::wrap1`] 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::wrap1`] +pub trait WasmArg { + #[doc(hidden)] + fn push(dst: &mut Vec); +} + +impl WasmArg for () { + fn push(_dst: &mut Vec) {} +} + +impl WasmArg for i32 { + fn push(dst: &mut Vec) { + dst.push(ValType::I32); + } +} + +impl WasmArg for i64 { + fn push(dst: &mut Vec) { + dst.push(ValType::I64); + } +} + +impl WasmArg for f32 { + fn push(dst: &mut Vec) { + dst.push(ValType::F32); + } +} + +impl WasmArg for f64 { + fn push(dst: &mut Vec) { + dst.push(ValType::F64); + } +} + +/// A trait implemented for types which can be returned from closures passed to +/// [`Func::wrap1`] 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::wrap1`] +pub trait WasmRet { + #[doc(hidden)] + type Abi; + #[doc(hidden)] + fn push(dst: &mut Vec); + #[doc(hidden)] + fn into_abi(self) -> Self::Abi; +} + +impl WasmRet for T { + type Abi = T; + fn push(dst: &mut Vec) { + T::push(dst) + } + + #[inline] + fn into_abi(self) -> Self::Abi { + self + } +} + +impl WasmRet for Result { + type Abi = T; + fn push(dst: &mut Vec) { + T::push(dst) + } + + #[inline] + fn into_abi(self) -> Self::Abi { + match self { + Ok(val) => return val, + Err(trap) => handle_trap(trap), + } + + fn handle_trap(trap: Trap) -> ! { + unsafe { wasmtime_runtime::raise_user_trap(Box::new(trap)) } + } + } +} diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 6e130790e1f7..d68d5802ccd2 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -22,7 +22,7 @@ mod values; pub use crate::callable::Callable; pub use crate::externals::*; -pub use crate::func::Func; +pub use crate::func::{Func, WasmArg, WasmRet}; pub use crate::instance::Instance; pub use crate::module::Module; pub use crate::r#ref::{AnyRef, HostInfo, HostRef}; diff --git a/crates/api/src/trampoline/func.rs b/crates/api/src/trampoline/func.rs index 446e6f8591ee..e08feda3be0e 100644 --- a/crates/api/src/trampoline/func.rs +++ b/crates/api/src/trampoline/func.rs @@ -4,6 +4,7 @@ use super::create_handle::create_handle; use super::trap::TrapSink; use crate::{Callable, FuncType, Store, Trap, Val}; use anyhow::{bail, Result}; +use std::any::Any; use std::cmp; use std::convert::TryFrom; use std::panic::{self, AssertUnwindSafe}; @@ -325,3 +326,35 @@ pub fn create_handle_with_function( Box::new(trampoline_state), ) } + +pub unsafe fn create_handle_with_raw_function( + ft: &FuncType, + func: *const VMFunctionBody, + store: &Store, + state: Box, +) -> Result { + let isa = { + let isa_builder = native::builder(); + let flag_builder = settings::builder(); + isa_builder.finish(settings::Flags::new(flag_builder)) + }; + + let pointer_type = isa.pointer_type(); + let sig = match ft.get_wasmtime_signature(pointer_type) { + Some(sig) => sig.clone(), + None => bail!("not a supported core wasm signature {:?}", ft), + }; + + let mut module = Module::new(); + let mut finished_functions: PrimaryMap = + PrimaryMap::new(); + + let sig_id = module.signatures.push(sig.clone()); + let func_id = module.functions.push(sig_id); + module + .exports + .insert("trampoline".to_string(), Export::Function(func_id)); + finished_functions.push(func); + + create_handle(module, Some(store), finished_functions, state) +} diff --git a/crates/api/src/trampoline/mod.rs b/crates/api/src/trampoline/mod.rs index ffb224a232fd..98ac5f119bcc 100644 --- a/crates/api/src/trampoline/mod.rs +++ b/crates/api/src/trampoline/mod.rs @@ -13,7 +13,9 @@ use self::memory::create_handle_with_memory; use self::table::create_handle_with_table; use super::{Callable, FuncType, GlobalType, MemoryType, Store, TableType, Val}; use anyhow::Result; +use std::any::Any; use std::rc::Rc; +use wasmtime_runtime::VMFunctionBody; pub use self::global::GlobalState; @@ -27,6 +29,20 @@ pub fn generate_func_export( Ok((instance, export)) } +/// Note that this is `unsafe` since `func` must be a valid function pointer and +/// have a signature which matches `ft`, otherwise the returned +/// instance/export/etc may exhibit undefined behavior. +pub unsafe fn generate_raw_func_export( + ft: &FuncType, + func: *const VMFunctionBody, + store: &Store, + state: Box, +) -> Result<(wasmtime_runtime::InstanceHandle, wasmtime_runtime::Export)> { + let instance = func::create_handle_with_raw_function(ft, func, store, state)?; + let export = instance.lookup("trampoline").expect("trampoline export"); + Ok((instance, export)) +} + pub fn generate_global_export( gt: &GlobalType, val: Val, diff --git a/crates/api/tests/func.rs b/crates/api/tests/func.rs new file mode 100644 index 000000000000..c7eb15eab8b3 --- /dev/null +++ b/crates/api/tests/func.rs @@ -0,0 +1,203 @@ +use anyhow::Result; +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; +use wasmtime::{Func, Instance, Module, Store, Trap, ValType}; + +#[test] +fn func_constructors() { + let store = Store::default(); + Func::wrap0(&store, || {}); + Func::wrap1(&store, |_: i32| {}); + Func::wrap2(&store, |_: i32, _: i64| {}); + Func::wrap2(&store, |_: f32, _: f64| {}); + Func::wrap0(&store, || -> i32 { 0 }); + Func::wrap0(&store, || -> i64 { 0 }); + Func::wrap0(&store, || -> f32 { 0.0 }); + Func::wrap0(&store, || -> f64 { 0.0 }); + + Func::wrap0(&store, || -> Result<(), Trap> { loop {} }); + Func::wrap0(&store, || -> Result { loop {} }); + Func::wrap0(&store, || -> Result { loop {} }); + Func::wrap0(&store, || -> Result { loop {} }); + Func::wrap0(&store, || -> Result { loop {} }); +} + +#[test] +fn dtor_runs() { + static HITS: AtomicUsize = AtomicUsize::new(0); + + struct A; + + impl Drop for A { + fn drop(&mut self) { + HITS.fetch_add(1, SeqCst); + } + } + + let store = Store::default(); + let a = A; + assert_eq!(HITS.load(SeqCst), 0); + Func::wrap0(&store, move || { + drop(&a); + }); + assert_eq!(HITS.load(SeqCst), 1); +} + +#[test] +fn dtor_delayed() -> Result<()> { + static HITS: AtomicUsize = AtomicUsize::new(0); + + struct A; + + impl Drop for A { + fn drop(&mut self) { + HITS.fetch_add(1, SeqCst); + } + } + + let store = Store::default(); + let a = A; + let func = Func::wrap0(&store, move || drop(&a)); + + assert_eq!(HITS.load(SeqCst), 0); + let wasm = wat::parse_str(r#"(import "" "" (func))"#)?; + let module = Module::new(&store, &wasm)?; + let instance = Instance::new(&module, &[func.into()])?; + assert_eq!(HITS.load(SeqCst), 0); + drop(instance); + assert_eq!(HITS.load(SeqCst), 1); + Ok(()) +} + +#[test] +fn signatures_match() { + let store = Store::default(); + + let f = Func::wrap0(&store, || {}); + assert_eq!(f.ty().params(), &[]); + assert_eq!(f.ty().results(), &[]); + + let f = Func::wrap0(&store, || -> i32 { loop {} }); + assert_eq!(f.ty().params(), &[]); + assert_eq!(f.ty().results(), &[ValType::I32]); + + let f = Func::wrap0(&store, || -> i64 { loop {} }); + assert_eq!(f.ty().params(), &[]); + assert_eq!(f.ty().results(), &[ValType::I64]); + + let f = Func::wrap0(&store, || -> f32 { loop {} }); + assert_eq!(f.ty().params(), &[]); + assert_eq!(f.ty().results(), &[ValType::F32]); + + let f = Func::wrap0(&store, || -> f64 { loop {} }); + assert_eq!(f.ty().params(), &[]); + assert_eq!(f.ty().results(), &[ValType::F64]); + + let f = Func::wrap5(&store, |_: f32, _: f64, _: i32, _: i64, _: i32| -> f64 { + loop {} + }); + assert_eq!( + f.ty().params(), + &[ + ValType::F32, + ValType::F64, + ValType::I32, + ValType::I64, + ValType::I32 + ] + ); + assert_eq!(f.ty().results(), &[ValType::F64]); +} + +#[test] +fn import_works() -> Result<()> { + static HITS: AtomicUsize = AtomicUsize::new(0); + + let wasm = wat::parse_str( + r#" + (import "" "" (func)) + (import "" "" (func (param i32) (result i32))) + (import "" "" (func (param i32) (param i64))) + (import "" "" (func (param i32 i64 i32 f32 f64))) + + (func $foo + call 0 + i32.const 0 + call 1 + i32.const 1 + i32.add + i64.const 3 + call 2 + + i32.const 100 + i64.const 200 + i32.const 300 + f32.const 400 + f64.const 500 + call 3 + ) + (start $foo) + "#, + )?; + let store = Store::default(); + let module = Module::new(&store, &wasm)?; + Instance::new( + &module, + &[ + Func::wrap0(&store, || { + assert_eq!(HITS.fetch_add(1, SeqCst), 0); + }) + .into(), + Func::wrap1(&store, |x: i32| -> i32 { + assert_eq!(x, 0); + assert_eq!(HITS.fetch_add(1, SeqCst), 1); + 1 + }) + .into(), + Func::wrap2(&store, |x: i32, y: i64| { + assert_eq!(x, 2); + assert_eq!(y, 3); + assert_eq!(HITS.fetch_add(1, SeqCst), 2); + }) + .into(), + Func::wrap5(&store, |a: i32, b: i64, c: i32, d: f32, e: f64| { + assert_eq!(a, 100); + assert_eq!(b, 200); + assert_eq!(c, 300); + assert_eq!(d, 400.0); + assert_eq!(e, 500.0); + assert_eq!(HITS.fetch_add(1, SeqCst), 3); + }) + .into(), + ], + )?; + Ok(()) +} + +#[test] +fn trap_smoke() { + let store = Store::default(); + let f = Func::wrap0(&store, || -> Result<(), Trap> { Err(Trap::new("test")) }); + let err = f.call(&[]).unwrap_err(); + assert_eq!(err.message(), "test"); +} + +#[test] +fn trap_import() -> Result<()> { + let wasm = wat::parse_str( + r#" + (import "" "" (func)) + (start 0) + "#, + )?; + let store = Store::default(); + let module = Module::new(&store, &wasm)?; + let trap = Instance::new( + &module, + &[Func::wrap0(&store, || -> Result<(), Trap> { Err(Trap::new("foo")) }).into()], + ) + .err() + .unwrap() + .downcast::()?; + assert_eq!(trap.message(), "foo"); + Ok(()) +} diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index a5dbbadf48c0..581334ebb05c 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -260,7 +260,9 @@ fn rust_panic_import() -> Result<()> { r#" (module $a (import "" "" (func $foo)) + (import "" "" (func $bar)) (func (export "foo") call $foo) + (func (export "bar") call $bar) ) "#, )?; @@ -268,13 +270,29 @@ fn rust_panic_import() -> Result<()> { let module = Module::new(&store, &binary)?; let sig = FuncType::new(Box::new([]), Box::new([])); let func = Func::new(&store, sig, Rc::new(Panic)); - let instance = Instance::new(&module, &[func.into()])?; + let instance = Instance::new( + &module, + &[ + func.into(), + Func::wrap0(&store, || panic!("this is another panic")).into(), + ], + )?; let func = instance.exports()[0].func().unwrap().clone(); 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.exports()[1].func().unwrap().clone(); + let err = panic::catch_unwind(AssertUnwindSafe(|| { + drop(func.call(&[])); + })) + .unwrap_err(); + assert_eq!( + err.downcast_ref::<&'static str>(), + Some(&"this is another panic") + ); Ok(()) } @@ -306,6 +324,16 @@ fn rust_panic_start_function() -> Result<()> { })) .unwrap_err(); assert_eq!(err.downcast_ref::<&'static str>(), Some(&"this is a panic")); + + let func = Func::wrap0(&store, || panic!("this is another panic")); + let err = panic::catch_unwind(AssertUnwindSafe(|| { + drop(Instance::new(&module, &[func.into()])); + })) + .unwrap_err(); + assert_eq!( + err.downcast_ref::<&'static str>(), + Some(&"this is another panic") + ); Ok(()) } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index a8109b86e585..9eddacd3fb39 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -43,6 +43,7 @@ pub use crate::instance::{InstanceHandle, InstantiationError, LinkError}; pub use crate::jit_int::GdbJitImageRegistration; pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; +pub use crate::trap_registry::TrapDescription; pub use crate::trap_registry::{get_mut_trap_registry, get_trap_registry, TrapRegistrationGuard}; pub use crate::traphandlers::resume_panic; pub use crate::traphandlers::{raise_user_trap, wasmtime_call, wasmtime_call_trampoline, Trap}; diff --git a/crates/wast/src/spectest.rs b/crates/wast/src/spectest.rs index 3f6d60a01b23..4fcfe190f2d6 100644 --- a/crates/wast/src/spectest.rs +++ b/crates/wast/src/spectest.rs @@ -1,77 +1,35 @@ -use anyhow::Result; use std::collections::HashMap; -use std::rc::Rc; use wasmtime::*; -struct MyCall(F); - -impl Callable for MyCall -where - F: Fn(&[Val], &mut [Val]) -> Result<(), Trap>, -{ - fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> { - (self.0)(params, results) - } -} - -fn wrap( - store: &Store, - ty: FuncType, - callable: impl Fn(&[Val], &mut [Val]) -> Result<(), Trap> + 'static, -) -> Func { - Func::new(store, ty, Rc::new(MyCall(callable))) -} - /// Return an instance implementing the "spectest" interface used in the /// spec testsuite. pub fn instantiate_spectest(store: &Store) -> HashMap<&'static str, Extern> { let mut ret = HashMap::new(); - let ty = FuncType::new(Box::new([]), Box::new([])); - let func = wrap(store, ty, |_params, _results| Ok(())); + let func = Func::wrap0(store, || {}); ret.insert("print", Extern::Func(func)); - let ty = FuncType::new(Box::new([ValType::I32]), Box::new([])); - let func = wrap(store, ty, |params, _results| { - println!("{}: i32", params[0].unwrap_i32()); - Ok(()) - }); + let func = Func::wrap1(store, |val: i32| println!("{}: i32", val)); ret.insert("print_i32", Extern::Func(func)); - let ty = FuncType::new(Box::new([ValType::I64]), Box::new([])); - let func = wrap(store, ty, |params, _results| { - println!("{}: i64", params[0].unwrap_i64()); - Ok(()) - }); + let func = Func::wrap1(store, |val: i64| println!("{}: i64", val)); ret.insert("print_i64", Extern::Func(func)); - let ty = FuncType::new(Box::new([ValType::F32]), Box::new([])); - let func = wrap(store, ty, |params, _results| { - println!("{}: f32", params[0].unwrap_f32()); - Ok(()) - }); + let func = Func::wrap1(store, |val: f32| println!("{}: f32", val)); ret.insert("print_f32", Extern::Func(func)); - let ty = FuncType::new(Box::new([ValType::F64]), Box::new([])); - let func = wrap(store, ty, |params, _results| { - println!("{}: f64", params[0].unwrap_f64()); - Ok(()) - }); + let func = Func::wrap1(store, |val: f64| println!("{}: f64", val)); ret.insert("print_f64", Extern::Func(func)); - let ty = FuncType::new(Box::new([ValType::I32, ValType::F32]), Box::new([])); - let func = wrap(store, ty, |params, _results| { - println!("{}: i32", params[0].unwrap_i32()); - println!("{}: f32", params[1].unwrap_f32()); - Ok(()) + let func = Func::wrap2(store, |i: i32, f: f32| { + println!("{}: i32", i); + println!("{}: f32", f); }); ret.insert("print_i32_f32", Extern::Func(func)); - let ty = FuncType::new(Box::new([ValType::F64, ValType::F64]), Box::new([])); - let func = wrap(store, ty, |params, _results| { - println!("{}: f64", params[0].unwrap_f64()); - println!("{}: f64", params[1].unwrap_f64()); - Ok(()) + let func = Func::wrap2(store, |f1: f64, f2: f64| { + println!("{}: f64", f1); + println!("{}: f64", f2); }); ret.insert("print_f64_f64", Extern::Func(func));