diff --git a/crates/api/examples/hello.rs b/crates/api/examples/hello.rs index 317e0361f3..9a461569ba 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/externals.rs b/crates/api/src/externals.rs index de9c373e18..fda3d1b635 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 0000000000..ff040b2b0c --- /dev/null +++ b/crates/api/src/func.rs @@ -0,0 +1,329 @@ +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. +/// +/// 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, +} + +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. + /// + /// * `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) + } + + 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, + 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 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 0858b7e971..d68d5802cc 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, 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 446e6f8591..e08feda3be 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 ffb224a232..98ac5f119b 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/src/values.rs b/crates/api/src/values.rs index 446a1c31a2..b61f11124f 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; diff --git a/crates/api/tests/func.rs b/crates/api/tests/func.rs new file mode 100644 index 0000000000..c7eb15eab8 --- /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 610760a324..255071b89b 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 a8109b86e5..9eddacd3fb 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 3f6d60a01b..4fcfe190f2 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));