diff --git a/crates/c-api/include/wasmtime/func.h b/crates/c-api/include/wasmtime/func.h index f254d922aa0f..8fe9a96cdc63 100644 --- a/crates/c-api/include/wasmtime/func.h +++ b/crates/c-api/include/wasmtime/func.h @@ -87,6 +87,61 @@ WASM_API_EXTERN void wasmtime_func_new( wasmtime_func_t *ret ); +/** + * \brief Creates a new host-defined function with a custom signature. + * + * This function is the same as #wasmtime_func_new except that the signature of + * `callback` is different. The signature of `callback` must correspond to the + * function `type` specified, if it's not then calling the function is UB. + * + * \param store the store in which to create the function + * \param type the wasm type of the function that's being created + * \param callback the host-defined callback to invoke + * \param env host-specific data passed to the callback invocation, can be + * `NULL` + * \param finalizer optional finalizer for `env`, can be `NULL` + * \param ret the #wasmtime_func_t return value to be filled in. + * + * The returned function can only be used with the specified `store`. + * + * The signature of `callback` is as follows: + * + * * First it takes a `void*` parameter which is the `env` provided here. + * * Next it receives a `wasmtime_caller_t*` which is only valid for the + * duration of that one call. + * * Next it receives each WebAssembly argument individually. Mapping from wasm + * types to host types is provided below. + * * Next it receives an out-pointer for each WebAssembly result individually. + * * Finally the function must return a `wasm_trap_t*` indicating whether the + * host function should trap or not. + * + * The mapping of WebAssembly types to host types is: + * + * * `i32 <=> int32_t` + * * `i64 <=> int64_t` + * * `f32 <=> float` + * * `f64 <=> double` + * * `v128` + * * Received indirectly as `const uint8_t*` when passed as an argument + * * Also passed as `const uint8_t*` for an out-pointer. + * * `extenref` - not supported + * * `funcref` - not supported + * + * All arguments are passed by-value except for `v128`, which is passed + * indirectly to the host function. + * + * This function will return an error if the `type` specified uses either + * `externref` or `funcref`. + */ +WASM_API_EXTERN wasmtime_error_t *wasmtime_func_wrap( + wasmtime_context_t *store, + const wasm_functype_t* type, + size_t callback, + void *env, + void (*finalizer)(void*), + wasmtime_func_t *ret +); + /** * \brief Returns the type of the function specified * diff --git a/crates/c-api/include/wasmtime/linker.h b/crates/c-api/include/wasmtime/linker.h index 09bc0bb10a24..08f2f9a9e444 100644 --- a/crates/c-api/include/wasmtime/linker.h +++ b/crates/c-api/include/wasmtime/linker.h @@ -81,7 +81,8 @@ WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_define( ); /** - * \brief Defines a new function in this linker. + * \brief Defines a new function in this linker along the lines of + * #wasmtime_func_new. * * \param linker the linker the name is being defined in. * \param module the module name the item is defined under. @@ -115,6 +116,46 @@ WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_define_func( void (*finalizer)(void*) ); +/** + * \brief Defines a new function in this linker along the lines of + * #wasmtime_func_wrap. + * + * \param linker the linker the name is being defined in. + * \param module the module name the item is defined under. + * \param module_len the byte length of `module` + * \param name the field name the item is defined under + * \param name_len the byte length of `name` + * \param ty the type of the function that's being defined + * \param cb the host callback to invoke when the function is called + * \param data the host-provided data to provide as the first argument to the callback + * \param finalizer an optional finalizer for the `data` argument. + * + * \return On success `NULL` is returned, otherwise an error is returned which + * describes why the definition failed. + * + * For more information about name resolution consult the [Rust + * documentation](https://bytecodealliance.github.io/wasmtime/api/wasmtime/struct.Linker.html#name-resolution). + * + * Note that this function does not create a #wasmtime_func_t. This creates a + * store-independent function within the linker, allowing this function + * definition to be used with multiple stores. + * + * Also note that the documentation of #wasmtime_func_wrap should be consulted + * to determine the correct function signature for the function provided in + * `cb`. + */ +WASM_API_EXTERN wasmtime_error_t* wasmtime_linker_func_wrap( + wasmtime_linker_t *linker, + const char *module, + size_t module_len, + const char *name, + size_t name_len, + const wasm_functype_t *ty, + size_t cb, + void *data, + void (*finalizer)(void*) +); + /** * \brief Defines WASI functions in this linker. * diff --git a/crates/c-api/src/func.rs b/crates/c-api/src/func.rs index 7e6a2e8c2d60..c13f33ba75b5 100644 --- a/crates/c-api/src/func.rs +++ b/crates/c-api/src/func.rs @@ -1,7 +1,8 @@ use crate::wasm_trap_t; use crate::{ - wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t, wasmtime_error_t, - wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext, CStoreContextMut, + handle_result, wasm_extern_t, wasm_functype_t, wasm_store_t, wasm_val_t, wasm_val_vec_t, + wasmtime_error_t, wasmtime_extern_t, wasmtime_val_t, wasmtime_val_union, CStoreContext, + CStoreContextMut, }; use anyhow::anyhow; use std::ffi::c_void; @@ -182,7 +183,10 @@ pub extern "C" fn wasm_func_as_extern_const(f: &wasm_func_t) -> &wasm_extern_t { &(*f).ext } -#[repr(C)] +// Note the `transparent` annotation which is required due to the +// `wasmtime_func_wrap` function where the C code uses `*mut wasmtime_caller_t` +// to represent the `&mut Caller<'_, T>` type in Rust. +#[repr(transparent)] pub struct wasmtime_caller_t<'a> { caller: Caller<'a, crate::StoreData>, } @@ -211,6 +215,22 @@ pub unsafe extern "C" fn wasmtime_func_new( *func = f; } +#[no_mangle] +pub unsafe extern "C" fn wasmtime_func_wrap( + store: CStoreContextMut<'_>, + ty: &wasm_functype_t, + callback: usize, + data: usize, + finalizer: Option, + func: &mut Func, +) -> Option> { + let ty = ty.ty().ty.clone(); + + handle_result(Func::wrap_cabi(store, ty, callback, data, finalizer), |f| { + *func = f; + }) +} + pub(crate) unsafe fn c_callback_to_rust_fn( callback: wasmtime_func_callback_t, data: *mut c_void, diff --git a/crates/c-api/src/linker.rs b/crates/c-api/src/linker.rs index 1b582b50c7f1..d4983d01e569 100644 --- a/crates/c-api/src/linker.rs +++ b/crates/c-api/src/linker.rs @@ -75,6 +75,29 @@ pub unsafe extern "C" fn wasmtime_linker_define_func( handle_result(linker.linker.func_new(module, name, ty, cb), |_linker| ()) } +#[no_mangle] +pub unsafe extern "C" fn wasmtime_linker_func_wrap( + linker: &mut wasmtime_linker_t, + module: *const u8, + module_len: usize, + name: *const u8, + name_len: usize, + ty: &wasm_functype_t, + callback: usize, + data: usize, + finalizer: Option, +) -> Option> { + let ty = ty.ty().ty.clone(); + let module = to_str!(module, module_len); + let name = to_str!(name, name_len); + handle_result( + linker + .linker + .func_wrap_cabi(module, name, ty, callback, data, finalizer), + |_linker| (), + ) +} + #[cfg(feature = "wasi")] #[no_mangle] pub extern "C" fn wasmtime_linker_define_wasi( diff --git a/crates/c-api/src/trap.rs b/crates/c-api/src/trap.rs index 941ec465aafd..ac38087abe23 100644 --- a/crates/c-api/src/trap.rs +++ b/crates/c-api/src/trap.rs @@ -2,7 +2,12 @@ use crate::{wasm_frame_vec_t, wasm_instance_t, wasm_name_t, wasm_store_t}; use once_cell::unsync::OnceCell; use wasmtime::{Trap, TrapCode}; -#[repr(C)] +// Note the `transparent` representation here which is required by the +// `wasmtime_func_wrap` API since the C-ABI function pointers return `*mut +// wasm_trap_t` which is interpreted as `Option>` in Rust. Note that +// `wasm_trap_t` is opaque in C, though, so it's always asking this library to +// allocate. +#[repr(transparent)] #[derive(Clone)] pub struct wasm_trap_t { pub(crate) trap: Trap, diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index a6b8ea74ca26..924b1ae8f779 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -8,7 +8,7 @@ use crate::{ }; use anyhow::{Context as _, Result}; use cranelift_codegen::ir::{self, ExternalName, InstBuilder, MemFlags}; -use cranelift_codegen::isa::TargetIsa; +use cranelift_codegen::isa::{CallConv, TargetIsa}; use cranelift_codegen::print_errors::pretty_error; use cranelift_codegen::settings; use cranelift_codegen::MachSrcLoc; @@ -29,7 +29,7 @@ use std::sync::Mutex; use wasmtime_environ::{ AddressMapSection, CompileError, FilePos, FlagValue, FunctionBodyData, FunctionInfo, InstructionAddressMap, Module, ModuleTranslation, StackMapInformation, Trampoline, TrapCode, - TrapEncodingBuilder, TrapInformation, Tunables, TypeTables, VMOffsets, + TrapEncodingBuilder, TrapInformation, Tunables, TypeTables, VMOffsets, WasmType, }; /// A compiler that compiles a WebAssembly module with Compiler, translating @@ -325,16 +325,18 @@ impl wasmtime_environ::Compiler for Compiler { ty: &WasmFuncType, host_fn: usize, obj: &mut Object, - ) -> Result<(Trampoline, Trampoline)> { + ) -> Result<(Trampoline, Trampoline, Trampoline)> { let host_to_wasm = self.host_to_wasm_trampoline(ty)?; let wasm_to_host = self.wasm_to_host_trampoline(ty, host_fn)?; + let host_to_c = self.host_to_c_trampoline(ty)?; let module = Module::new(); let mut builder = ObjectBuilder::new(obj, &module, &*self.isa); let a = builder.trampoline(SignatureIndex::new(0), &host_to_wasm); let b = builder.trampoline(SignatureIndex::new(1), &wasm_to_host); + let c = builder.trampoline(SignatureIndex::new(2), &host_to_c); builder.unwind_info(); builder.finish()?; - Ok((a, b)) + Ok((a, b, c)) } fn triple(&self) -> &target_lexicon::Triple { @@ -525,6 +527,116 @@ impl Compiler { Ok(func) } + fn host_to_c_trampoline(&self, ty: &WasmFuncType) -> Result { + let isa = &*self.isa; + let pointer_type = isa.pointer_type(); + + // This is the signature of the C stub that this trampoline calls. + // Effectively this is the definition of how wasmtime translates a wasm + // signature to a C signature. Currently the signature parameters are: + // + // * a void* pointer argument (user-provided) + // * a pointer argument for a "caller" structure (`wasmtime::Caller`) + // * each wasm type as an individual parameter + // * each wasm return as an out-pointer + // + // And then the function returns a pointer which is a trap object. The + // trap is a nullable pointer. + let mut c_sig = ir::Signature::new(CallConv::triple_default(isa.triple())); + c_sig.params.push(ir::AbiParam::new(pointer_type)); + c_sig.params.push(ir::AbiParam::new(pointer_type)); + for ty in ty.params.iter() { + let ty = match ty { + WasmType::I32 | WasmType::I64 | WasmType::F32 | WasmType::F64 => { + value_type(isa, *ty) + } + WasmType::V128 => pointer_type, + WasmType::FuncRef | WasmType::ExternRef | WasmType::ExnRef => unimplemented!(), + }; + c_sig.params.push(ir::AbiParam::new(ty)); + } + for _ in ty.returns.iter() { + c_sig.params.push(ir::AbiParam::new(pointer_type)); + } + c_sig.returns.push(ir::AbiParam::new(pointer_type)); + + // This trampoline itself has a fixed signature so it can be called from + // Rust. The parameters here are: + // + // * a void* pionter argument (user-provided) + // * a pointer argument for a "caller" structure (`wasmtime::Caller`) + // * the `*const u128` values_vec allocation for arguments/returns + // * the C function that we'll be calling + // + // This function returns a pointer which is the trap the C function + // returns. + let mut stub_signature = ir::Signature::new(CallConv::triple_default(isa.triple())); + stub_signature.params.push(ir::AbiParam::new(pointer_type)); + stub_signature.params.push(ir::AbiParam::new(pointer_type)); + stub_signature.params.push(ir::AbiParam::new(pointer_type)); + stub_signature.params.push(ir::AbiParam::new(pointer_type)); + stub_signature.returns.push(ir::AbiParam::new(pointer_type)); + + let value_size = mem::size_of::(); + let mut context = Context::new(); + context.func = + ir::Function::with_name_signature(ir::ExternalName::user(0, 0), stub_signature); + let mut func_translator = self.take_translator(); + let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context()); + let block0 = builder.create_block(); + builder.append_block_params_for_function_params(block0); + builder.switch_to_block(block0); + builder.seal_block(block0); + + // Build the arguments to the C function that we're going to call. The + // first two arguments are simply forwarding our own first two + // arguments. Next comes all the wasm parameters which are passed + // by-value. Finally all returns get a return pointer which points back + // into our `values_vec` parameter. + let mut args = Vec::new(); + args.push(builder.func.dfg.block_params(block0)[0]); + args.push(builder.func.dfg.block_params(block0)[1]); + let values_vec_ptr_val = builder.func.dfg.block_params(block0)[2]; + for (i, ty) in ty.params.iter().enumerate() { + let offset = (i * value_size) as i32; + let arg = match ty { + WasmType::I32 | WasmType::I64 | WasmType::F32 | WasmType::F64 => { + builder.ins().load( + value_type(isa, *ty), + MemFlags::trusted(), + values_vec_ptr_val, + offset, + ) + } + WasmType::V128 => builder + .ins() + .iadd_imm(values_vec_ptr_val, i64::from(offset)), + WasmType::FuncRef | WasmType::ExternRef | WasmType::ExnRef => unimplemented!(), + }; + args.push(arg); + } + for (i, _ty) in ty.returns.iter().enumerate() { + let addr = builder + .ins() + .iadd_imm(values_vec_ptr_val, (i * value_size) as i64); + args.push(addr); + } + + // With all the arguments we call the indirect function passed as a + // parameter to our function, then we're good to go and we simply return + // that function's value. + let new_sig = builder.import_signature(c_sig); + let callee_value = builder.func.dfg.block_params(block0)[3]; + let call = builder.ins().call_indirect(new_sig, callee_value, &args); + let trap = builder.inst_results(call)[0]; + builder.ins().return_(&[trap]); + builder.finalize(); + + let func = self.finish_trampoline(context, isa)?; + self.save_translator(func_translator); + Ok(func) + } + fn finish_trampoline( &self, mut context: Context, diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 30111e59db93..cf296489b79d 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -175,19 +175,23 @@ pub trait Compiler: Send + Sync { obj: &mut Object, ) -> Result<(PrimaryMap, Vec)>; - /// Inserts two functions for host-to-wasm and wasm-to-host trampolines into - /// the `obj` provided. + /// Inserts various trampolines for interacting with the wasm function + /// siganture `ty` into the `obj` provided. /// /// This will configure the same sections as `emit_obj`, but will likely be - /// much smaller. The two returned `Trampoline` structures describe where to - /// find the host-to-wasm and wasm-to-host trampolines in the text section, - /// respectively. + /// much smaller. The returned `Trampoline` structures at this time are: + /// + /// * Where to find the host-to-wasm trampoline. + /// * Where to find the wasm-to-host trampoline. + /// * Where to fidn the host-to-c trampoline. + /// + /// All trampoline offsets are relative to the text section. fn emit_trampoline_obj( &self, ty: &WasmFuncType, host_fn: usize, obj: &mut Object, - ) -> Result<(Trampoline, Trampoline)>; + ) -> Result<(Trampoline, Trampoline, Trampoline)>; /// Creates a new `Object` file which is used to build the results of a /// compilation into. diff --git a/crates/wasmtime/src/func.rs b/crates/wasmtime/src/func.rs index f11e20117953..1ec0046cdde6 100644 --- a/crates/wasmtime/src/func.rs +++ b/crates/wasmtime/src/func.rs @@ -1,4 +1,5 @@ use crate::store::{StoreData, StoreOpaque, Stored}; +use crate::trampoline::CTrampoline; use crate::{ AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, InterruptHandle, StoreContext, StoreContextMut, Trap, Val, ValType, @@ -316,6 +317,131 @@ impl Func { } } + /// Creates a new `Func` where, when called, will invoke a host-defined + /// function with a well-defined C ABI. + /// + /// > **Note**: This function is primarily here for Wasmtime's C API and + /// > probably doesn't want to be used by Rust consumers directly because + /// > `Func::wrap` is faster, more ergonomic, and safer. + /// + /// This function will interpret the `func` paramter as a C-ABI function + /// pointer. The signature of `func` depends on the `ty` specified, and the + /// caller of this function is unsafely asserting that `func` is indeed a + /// valid function pointer with the correct signature. + /// + /// The `data` specified will be the first parameter to the `func` provided + /// (more on the signature in a moment). The optional `finalizer` specified + /// can be used to destroy the `data` when the returned `Func` is destroyed. + /// + /// ## C ABI Translation + /// + /// The `func` argument should always have the C ABI. It also always takes + /// two pointer-sized parameters as its first two arguments. The first + /// argument will be the `data` value provided here. The second argument is + /// `&mut Caller<'_, T>` to learn about caller-based information. + /// + /// After these two arguments the C function will receive all the wasm + /// function's parameters as individual arguments. The mapping from Wasm + /// type to native host type is as follows: + /// + /// | WebAssembly type | Rust type | C type | + /// |-------------------|-----------|------------------| + /// | `i32` | `i32` | `int32_t` | + /// | `i64` | `i64` | `int64_t` | + /// | `f32` | `f32` | `float` | + /// | `f64` | `f64` | `double` | + /// | `v128` | `&u128` | `const uint8_t*` | + /// + /// The `funcref` and `externref` types are not supported with this + /// function at this time. These types, if specified, will return an error + /// from the `wrap_cabi` function. + /// + /// Note that all values are passed by-value except for the `v128` type + /// which is passed indirectly. + /// + /// After the WebAssembly parameters the function receives a single + /// out-pointer for each WebAssembly result type. This out-pointer has + /// the type corresponding to the native host type. + /// + /// Finally, the C function must return an `Option>`, or a + /// nullable pointer to a trap. If `None` or `NULL` is returned then + /// the function won't trap. In this case the results will be read from as + /// they were stored by the callee. If a trap is returned, though, then the + /// results of the function are discarded and a trap is generated. + /// + /// Here are some Rust-based examples of translating between a wasm + /// function signature and the C ABI: + /// + /// ``` + /// use wasmtime::{Caller, Trap}; + /// + /// // (func) + /// extern "C" fn f1(userdata: usize, caller: &mut Caller<'_, ()>) -> Option> { + /// // ... + /// # None + /// } + /// + /// // (func (param i32)) + /// extern "C" fn f2( + /// userdata: usize, + /// caller: &mut Caller<'_, ()>, + /// wasm_param: i32, + /// ) -> Option> { + /// // ... + /// # None + /// } + /// + /// // (func (result f32)) + /// extern "C" fn f3( + /// userdata: usize, + /// caller: &mut Caller<'_, ()>, + /// wasm_result: &mut f32, + /// ) -> Option> { + /// // ... + /// # None + /// } + /// + /// // (func (param i64 f64 v128) (result f32 i32)) + /// extern "C" fn f3( + /// userdata: usize, + /// caller: &mut Caller<'_, ()>, + /// wasm_param1: i64, + /// wasm_param2: f64, + /// wasm_param3: &u128, + /// wasm_result1: &mut f32, + /// wasm_result2: &mut i32, + /// ) -> Option> { + /// // ... + /// # None + /// } + /// ``` + /// + /// # Errors + /// + /// This function will return an error if the `ty` specified uses the + /// `externref` or the `funcref` types. + /// + /// # Unsafety + /// + /// This is not a safe method because the validity of `func` cannot be + /// guaranteed. It's up to the caller to ensure that `func` has the right + /// function signature corresponding to the dynamically-provided WebAssembly + /// type signature in `ty`. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub unsafe fn wrap_cabi( + mut store: impl AsContextMut, + ty: FuncType, + func: usize, + data: usize, + finalizer: Option, + ) -> Result { + let store = store.as_context_mut().0; + + let host = HostFunc::wrap_cabi::(store.engine(), ty, func, data, finalizer)?; + Ok(host.into_func(store)) + } + /// Creates a new host-defined WebAssembly function which, when called, /// will run the asynchronous computation defined by `func` to completion /// and then return the result to WebAssembly. @@ -1892,7 +2018,7 @@ impl HostFunc { let ty_clone = ty.clone(); // Create a trampoline that converts raw u128 values to `Val` - let func = move |caller_vmctx, values_vec: *mut u128| unsafe { + let func = move |caller_vmctx, values_vec: *mut u128, _| unsafe { Caller::with(caller_vmctx, |caller| { Func::invoke(caller, &ty_clone, values_vec, &func) }) @@ -1903,6 +2029,64 @@ impl HostFunc { HostFunc::_new(engine, instance, trampoline) } + /// Analog of [`Func::wrap_cabi`] + #[cfg(compiler)] + pub unsafe fn wrap_cabi( + engine: &Engine, + ty: FuncType, + func: usize, + data: usize, + finalizer: Option, + ) -> Result { + struct CFinalizer { + data: usize, + finalizer: Option, + } + impl Drop for CFinalizer { + fn drop(&mut self) { + if let Some(dtor) = self.finalizer { + dtor(self.data); + } + } + } + + // See documentation for `Func::wrap_cabi`, but not all function + // parameters/results are supported here. + for ty in ty.params().chain(ty.results()) { + match ty { + ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {} + ValType::ExternRef => { + bail!("cannot use `wrap_cabi` on functions with externref params/results") + } + ValType::FuncRef => { + bail!("cannot use `wrap_cabi` on functions with funcref params/results") + } + } + } + + // Create a trampoline that converts raw u128 values to `Val` + let data = CFinalizer { data, finalizer }; + let func = move |caller_vmctx, values_vec: *mut u128, c_trampoline: CTrampoline| unsafe { + Caller::::with(caller_vmctx, |mut caller| { + let trap = c_trampoline( + data.data, + &mut caller as *mut Caller<'_, T> as usize, + values_vec, + func, + ); + if trap.is_null() { + Ok(()) + } else { + Err(*Box::from_raw(trap)) + } + }) + }; + + let (instance, trampoline) = crate::trampoline::create_function(&ty, func, engine) + .expect("failed to create function"); + Ok(HostFunc::_new(engine, instance, trampoline)) + } + /// Analog of [`Func::wrap`] pub fn wrap( engine: &Engine, diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index bb49ca09b543..006dbf4579a1 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -304,6 +304,26 @@ impl Linker { Ok(self) } + /// Creates a [`Func::wrap_cabi`]-style function named in this linker. + /// + /// For more information see [`Linker::func_wrap`]. + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub unsafe fn func_wrap_cabi( + &mut self, + module: &str, + name: &str, + ty: FuncType, + func: usize, + data: usize, + finalizer: Option, + ) -> Result<&mut Self> { + let func = HostFunc::wrap_cabi::(&self.engine, ty, func, data, finalizer)?; + let key = self.import_key(module, Some(name)); + self.insert(key, Definition::HostFunc(Arc::new(func)))?; + Ok(self) + } + /// Creates a [`Func::new_async`]-style function named in this linker. /// /// For more information see [`Linker::func_wrap`]. diff --git a/crates/wasmtime/src/trampoline.rs b/crates/wasmtime/src/trampoline.rs index 4452d7b42ae4..d706f095c6fc 100644 --- a/crates/wasmtime/src/trampoline.rs +++ b/crates/wasmtime/src/trampoline.rs @@ -12,7 +12,7 @@ use self::global::create_global; use self::memory::create_memory; use self::table::create_table; use crate::store::{InstanceId, StoreOpaque}; -use crate::{GlobalType, MemoryType, TableType, Val}; +use crate::{GlobalType, MemoryType, TableType, Trap, Val}; use anyhow::Result; use std::any::Any; use std::sync::Arc; @@ -22,6 +22,14 @@ use wasmtime_runtime::{ VMFunctionImport, VMSharedSignatureIndex, }; +// For more information see `host_to_c_trampoline` in the cranelift compiler. +pub type CTrampoline = extern "C" fn( + usize, // user-provided void* + usize, // &mut Caller<'_, T> + *mut u128, // storage for parameters and results + usize, // actual host function to indirectly call +) -> *mut Trap; + fn create_handle( module: Module, store: &mut StoreOpaque, diff --git a/crates/wasmtime/src/trampoline/func.rs b/crates/wasmtime/src/trampoline/func.rs index e215dfe8f252..4758bdc49017 100644 --- a/crates/wasmtime/src/trampoline/func.rs +++ b/crates/wasmtime/src/trampoline/func.rs @@ -1,5 +1,6 @@ //! Support for a calling of an imported function. +use super::CTrampoline; use crate::{Engine, FuncType, Trap}; use anyhow::Result; use std::any::Any; @@ -14,6 +15,7 @@ use wasmtime_runtime::{ struct TrampolineState { func: F, + c_trampoline: CTrampoline, #[allow(dead_code)] code_memory: CodeMemory, } @@ -23,7 +25,7 @@ unsafe extern "C" fn stub_fn( caller_vmctx: *mut VMContext, values_vec: *mut u128, ) where - F: Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + 'static, + F: Fn(*mut VMContext, *mut u128, CTrampoline) -> Result<(), Trap> + 'static, { // Here we are careful to use `catch_unwind` to ensure Rust panics don't // unwind past us. The primary reason for this is that Rust considers it UB @@ -45,7 +47,7 @@ unsafe extern "C" fn stub_fn( let state = (*vmctx).host_state(); debug_assert!(state.is::>()); let state = &*(state as *const _ as *const TrampolineState); - (state.func)(caller_vmctx, values_vec) + (state.func)(caller_vmctx, values_vec, state.c_trampoline) })); match result { @@ -72,10 +74,10 @@ pub fn create_function( engine: &Engine, ) -> Result<(InstanceHandle, VMTrampoline)> where - F: Fn(*mut VMContext, *mut u128) -> Result<(), Trap> + Send + Sync + 'static, + F: Fn(*mut VMContext, *mut u128, CTrampoline) -> Result<(), Trap> + Send + Sync + 'static, { let mut obj = engine.compiler().object()?; - let (t1, t2) = engine.compiler().emit_trampoline_obj( + let (t1, t2, t3) = engine.compiler().emit_trampoline_obj( ft.as_wasm_func_type(), stub_fn:: as usize, &mut obj, @@ -89,20 +91,25 @@ where // Extract the host/wasm trampolines from the results of compilation since // we know their start/length. - let host_trampoline = code.text[t1.start as usize..][..t1.length as usize].as_ptr(); - let wasm_trampoline = &code.text[t2.start as usize..][..t2.length as usize]; - let wasm_trampoline = wasm_trampoline as *const [u8] as *mut [VMFunctionBody]; + let host_to_wasm = &code.text[t1.start as usize..][..t1.length as usize]; + let wasm_to_host = &code.text[t2.start as usize..][..t2.length as usize]; + let host_to_c = &code.text[t3.start as usize..][..t3.length as usize]; let sig = engine.signatures().register(ft.as_wasm_func_type()); unsafe { + let host_to_wasm = std::mem::transmute::<*const u8, VMTrampoline>(host_to_wasm.as_ptr()); + let c_trampoline = std::mem::transmute::<*const u8, CTrampoline>(host_to_c.as_ptr()); let instance = create_raw_function( - wasm_trampoline, + wasm_to_host as *const [u8] as *mut [VMFunctionBody], sig, - Box::new(TrampolineState { func, code_memory }), + Box::new(TrampolineState { + func, + c_trampoline, + code_memory, + }), )?; - let host_trampoline = std::mem::transmute::<*const u8, VMTrampoline>(host_trampoline); - Ok((instance, host_trampoline)) + Ok((instance, host_to_wasm)) } } diff --git a/tests/all/func.rs b/tests/all/func.rs index 64d0b3e63efe..2ef12ed93cfb 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -921,3 +921,271 @@ fn typed_funcs_count_params_correctly_in_error_messages() -> anyhow::Result<()> Ok(()) } + +#[test] +fn wrap_cabi() -> anyhow::Result<()> { + unsafe { + let mut store = Store::<()>::default(); + let host = Func::wrap_cabi( + &mut store, + FuncType::new([], []), + host_thunk as usize, + 0, + None, + )?; + assert!(host.call(&mut store, &[]).is_ok()); + + let module = Module::new( + store.engine(), + r#" + (module + (import "" "" (func)) + (start 0)) + "#, + )?; + Instance::new(&mut store, &module, &[host.into()])?; + } + return Ok(()); + + extern "C" fn host_thunk(param: usize, _caller: &mut Caller<'_, ()>) -> Option> { + assert_eq!(param, 0); + None + } +} + +#[test] +fn cabi_dtor() -> anyhow::Result<()> { + static HITS: AtomicUsize = AtomicUsize::new(0); + + struct A; + + impl Drop for A { + fn drop(&mut self) { + HITS.fetch_add(1, SeqCst); + } + } + + unsafe { + let mut store = Store::<()>::default(); + assert_eq!(HITS.load(SeqCst), 0); + Func::wrap_cabi( + &mut store, + FuncType::new([], []), + host_thunk as usize, + Box::into_raw(Box::new(A)) as usize, + Some(finalize), + )?; + assert_eq!(HITS.load(SeqCst), 0); + } + assert_eq!(HITS.load(SeqCst), 1); + return Ok(()); + + extern "C" fn host_thunk(_param: usize, _caller: &mut Caller<'_, ()>) -> Option> { + None + } + + extern "C" fn finalize(param: usize) { + unsafe { + drop(Box::from_raw(param as *mut A)); + } + } +} + +#[test] +fn cabi_trap() -> anyhow::Result<()> { + unsafe { + let mut store = Store::<()>::default(); + let func = Func::wrap_cabi( + &mut store, + FuncType::new([], []), + host_thunk as usize, + 0, + None, + )?; + let trap = match func.call(&mut store, &[]) { + Ok(_) => panic!(), + Err(e) => e.downcast::()?, + }; + assert_eq!(trap.display_reason().to_string(), "hello"); + } + return Ok(()); + + extern "C" fn host_thunk(_param: usize, _caller: &mut Caller<'_, ()>) -> Option> { + Some(Box::new(Trap::new("hello"))) + } +} + +#[test] +fn cabi_type_mappings() -> anyhow::Result<()> { + unsafe { + let mut store = Store::<()>::default(); + let func = Func::wrap_cabi( + &mut store, + FuncType::new( + [ + ValType::I32, + ValType::I64, + ValType::F32, + ValType::F64, + ValType::V128, + ], + [ + ValType::I32, + ValType::I64, + ValType::F32, + ValType::F64, + ValType::V128, + ], + ), + host_impl as usize, + 0, + None, + )?; + let results = func.call( + &mut store, + &[ + Val::I32(1), + Val::I64(2), + 3.0f32.into(), + 4.0f64.into(), + Val::V128(5), + ], + )?; + assert_eq!(results.len(), 5); + assert_eq!(results[0].i32(), Some(6)); + assert_eq!(results[1].i64(), Some(7)); + assert_eq!(results[2].f32(), Some(8.)); + assert_eq!(results[3].f64(), Some(9.)); + assert_eq!(results[4].v128(), Some(10)); + } + return Ok(()); + + extern "C" fn host_impl( + _param: usize, + _caller: &mut Caller<'_, ()>, + arg1: i32, + arg2: i64, + arg3: f32, + arg4: f64, + arg5: &u128, + out1: &mut i32, + out2: &mut i64, + out3: &mut f32, + out4: &mut f64, + out5: &mut u128, + ) -> Option> { + assert_eq!(arg1, 1); + assert_eq!(arg2, 2); + assert_eq!(arg3, 3.); + assert_eq!(arg4, 4.); + assert_eq!(*arg5, 5); + *out1 = 6; + *out2 = 7; + *out3 = 8.; + *out4 = 9.; + *out5 = 10; + None + } +} + +#[test] +fn cabi_call_typed() -> anyhow::Result<()> { + unsafe { + let mut store = Store::<()>::default(); + let func = Func::wrap_cabi( + &mut store, + FuncType::new([ValType::I32], [ValType::I64]), + host_impl as usize, + 0, + None, + )?; + let func = func.typed::(&store)?; + assert_eq!(func.call(&mut store, 1)?, 2); + } + return Ok(()); + + extern "C" fn host_impl( + _param: usize, + _caller: &mut Caller<'_, ()>, + arg: i32, + out: &mut i64, + ) -> Option> { + assert_eq!(arg, 1); + *out = 2; + None + } +} + +#[test] +fn cabi_wasm_interop_works() -> anyhow::Result<()> { + unsafe { + let mut config = Config::new(); + config.wasm_simd(true); + let engine = Engine::new(&config)?; + let mut store = Store::new(&engine, ()); + let a = Func::wrap_cabi( + &mut store, + FuncType::new( + [ValType::I32, ValType::F32], + [ValType::I64, ValType::F64, ValType::V128], + ), + a_impl as usize, + 0, + None, + )?; + let b = Func::wrap_cabi( + &mut store, + FuncType::new([ValType::I64, ValType::F64, ValType::V128], []), + b_impl as usize, + 0, + None, + )?; + let module = Module::new( + store.engine(), + r#" + (module + (import "" "a" (func $a (param i32 f32) (result i64 f64 v128))) + (import "" "b" (func $b (param i64 f64 v128))) + (func (export "") + i32.const 100 + f32.const 200 + call $a + call $b)) + "#, + )?; + let i = Instance::new(&mut store, &module, &[a.into(), b.into()])?; + let f = i.get_typed_func::<(), (), _>(&mut store, "")?; + f.call(&mut store, ())?; + } + return Ok(()); + + extern "C" fn a_impl( + _param: usize, + _caller: &mut Caller<'_, ()>, + arg1: i32, + arg2: f32, + out1: &mut i64, + out2: &mut f64, + out3: &mut u128, + ) -> Option> { + assert_eq!(arg1, 100); + assert_eq!(arg2, 200.); + *out1 = 300; + *out2 = 400.; + *out3 = 500; + None + } + + extern "C" fn b_impl( + _param: usize, + _caller: &mut Caller<'_, ()>, + arg1: i64, + arg2: f64, + arg3: &mut u128, + ) -> Option> { + assert_eq!(arg1, 300); + assert_eq!(arg2, 400.); + assert_eq!(*arg3, 500); + None + } +}