Refactor the internals of Func to remove layers of indirection (#1363)

* Remove `WrappedCallable` indirection

At this point `Func` has evolved quite a bit since inception and the
`WrappedCallable` trait I don't believe is needed any longer. This
should help clean up a few entry points by having fewer traits in play.

* Remove the `Callable` trait

This commit removes the `wasmtime::Callable` trait, changing the
signature of `Func::new` to take an appropriately typed `Fn`.
Additionally the function now always takes `&Caller` like `Func::wrap`
optionally can, to empower `Func::new` to have the same capabilities of
`Func::wrap`.

* Add a test for an already-fixed issue

Closes #849

* rustfmt

* Update more locations for `Callable`

* rustfmt

* Remove a stray leading borrow

* Review feedback

* Remove unneeded `wasmtime_call_trampoline` shim
This commit is contained in:
Alex Crichton
2020-03-19 14:21:45 -05:00
committed by GitHub
parent 39ba281bc7
commit afd980b4f6
16 changed files with 354 additions and 682 deletions

View File

@@ -1,214 +0,0 @@
use crate::runtime::Store;
use crate::trampoline::generate_func_export;
use crate::trap::Trap;
use crate::types::FuncType;
use crate::values::Val;
use std::cmp::max;
use std::ptr;
use std::rc::Rc;
use wasmtime_runtime::{ExportFunction, InstanceHandle, VMTrampoline};
/// A trait representing a function that can be imported and called from inside
/// WebAssembly.
/// # Example
/// ```
/// use wasmtime::Val;
///
/// struct TimesTwo;
///
/// impl wasmtime::Callable for TimesTwo {
/// fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), wasmtime::Trap> {
/// let mut value = params[0].unwrap_i32();
/// value *= 2;
/// results[0] = value.into();
///
/// Ok(())
/// }
/// }
///
/// # fn main () -> Result<(), Box<dyn std::error::Error>> {
/// // Simple module that imports our host function ("times_two") and re-exports
/// // it as "run".
/// let wat = r#"
/// (module
/// (func $times_two (import "" "times_two") (param i32) (result i32))
/// (func
/// (export "run")
/// (param i32)
/// (result i32)
/// (local.get 0)
/// (call $times_two))
/// )
/// "#;
///
/// // Initialise environment and our module.
/// let store = wasmtime::Store::default();
/// let module = wasmtime::Module::new(&store, wat)?;
///
/// // Define the type of the function we're going to call.
/// let times_two_type = wasmtime::FuncType::new(
/// // Parameters
/// Box::new([wasmtime::ValType::I32]),
/// // Results
/// Box::new([wasmtime::ValType::I32])
/// );
///
/// // Build a reference to the "times_two" function that can be used.
/// let times_two_function =
/// wasmtime::Func::new(&store, times_two_type, std::rc::Rc::new(TimesTwo));
///
/// // Create module instance that imports our function
/// let instance = wasmtime::Instance::new(
/// &module,
/// &[times_two_function.into()]
/// )?;
///
/// // Get "run" function from the exports.
/// let run_function = instance.exports()[0].func().unwrap();
///
/// // Borrow and call "run". Returning any error message from Wasm as a string.
/// let original = 5i32;
/// let results = run_function
/// .call(&[original.into()])
/// .map_err(|trap| trap.to_string())?;
///
/// // Compare that the results returned matches what we expect.
/// assert_eq!(original * 2, results[0].unwrap_i32());
/// # Ok(())
/// # }
/// ```
pub trait Callable {
/// What is called when the function is invoked in WebAssembly.
/// `params` is an immutable list of parameters provided to the function.
/// `results` is mutable list of results to be potentially set by your
/// function. Produces a `Trap` if the function encounters any errors.
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap>;
}
pub(crate) trait WrappedCallable {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap>;
fn wasmtime_handle(&self) -> &InstanceHandle;
fn wasmtime_function(&self) -> &ExportFunction;
}
pub(crate) struct WasmtimeFn {
store: Store,
instance: InstanceHandle,
export: ExportFunction,
trampoline: VMTrampoline,
}
impl WasmtimeFn {
pub fn new(
store: &Store,
instance: InstanceHandle,
export: ExportFunction,
trampoline: VMTrampoline,
) -> WasmtimeFn {
WasmtimeFn {
store: store.clone(),
instance,
export,
trampoline,
}
}
}
impl WrappedCallable for WasmtimeFn {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
let f = self.wasmtime_function();
let signature = self
.store
.compiler()
.signatures()
.lookup(f.signature)
.expect("missing signature");
if signature.params.len() - 2 != params.len() {
return Err(Trap::new(format!(
"expected {} arguments, got {}",
signature.params.len() - 2,
params.len()
)));
}
if signature.returns.len() != results.len() {
return Err(Trap::new(format!(
"expected {} results, got {}",
signature.returns.len(),
results.len()
)));
}
let mut values_vec = vec![0; max(params.len(), results.len())];
// Store the argument values into `values_vec`.
let param_tys = signature.params.iter().skip(2);
for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) {
if arg.ty().get_wasmtime_type() != Some(ty.value_type) {
return Err(Trap::new("argument type mismatch"));
}
unsafe {
arg.write_value_to(slot);
}
}
// Call the trampoline.
if let Err(error) = unsafe {
wasmtime_runtime::wasmtime_call_trampoline(
f.vmctx,
ptr::null_mut(),
self.trampoline,
f.address,
values_vec.as_mut_ptr() as *mut u8,
)
} {
return Err(Trap::from_jit(error));
}
// Load the return values out of `values_vec`.
for (index, abi_param) in signature.returns.iter().enumerate() {
unsafe {
let ptr = values_vec.as_ptr().add(index);
results[index] = Val::read_value_from(ptr, abi_param.value_type);
}
}
Ok(())
}
fn wasmtime_handle(&self) -> &InstanceHandle {
&self.instance
}
fn wasmtime_function(&self) -> &ExportFunction {
&self.export
}
}
pub struct NativeCallable {
callable: Rc<dyn Callable + 'static>,
instance: InstanceHandle,
export: ExportFunction,
}
impl NativeCallable {
pub(crate) fn new(callable: Rc<dyn Callable + 'static>, ft: &FuncType, store: &Store) -> Self {
let (instance, export) =
generate_func_export(ft, &callable, store).expect("generated func");
NativeCallable {
callable,
instance,
export,
}
}
}
impl WrappedCallable for NativeCallable {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
self.callable.call(params, results)
}
fn wasmtime_handle(&self) -> &InstanceHandle {
&self.instance
}
fn wasmtime_function(&self) -> &ExportFunction {
&self.export
}
}

View File

@@ -1,12 +1,12 @@
use crate::callable::{NativeCallable, WasmtimeFn, WrappedCallable}; use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType};
use crate::{Callable, Extern, FuncType, Memory, Store, Trap, Val, ValType};
use anyhow::{ensure, Context as _}; use anyhow::{ensure, Context as _};
use std::cmp::max;
use std::fmt; use std::fmt;
use std::mem; use std::mem;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::ptr; use std::ptr;
use std::rc::Rc;
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody}; use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
use wasmtime_runtime::{ExportFunction, VMTrampoline};
/// A WebAssembly function which can be called. /// A WebAssembly function which can be called.
/// ///
@@ -100,19 +100,6 @@ use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
/// ///
/// ``` /// ```
/// # use wasmtime::*; /// # use wasmtime::*;
/// use std::rc::Rc;
///
/// struct Double;
///
/// impl Callable for Double {
/// fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
/// let mut value = params[0].unwrap_i32();
/// value *= 2;
/// results[0] = value.into();
/// Ok(())
/// }
/// }
///
/// # fn main() -> anyhow::Result<()> { /// # fn main() -> anyhow::Result<()> {
/// let store = Store::default(); /// let store = Store::default();
/// ///
@@ -122,7 +109,12 @@ use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
/// Box::new([wasmtime::ValType::I32]), /// Box::new([wasmtime::ValType::I32]),
/// Box::new([wasmtime::ValType::I32]) /// Box::new([wasmtime::ValType::I32])
/// ); /// );
/// let double = Func::new(&store, double_type, Rc::new(Double)); /// let double = Func::new(&store, double_type, |_, params, results| {
/// let mut value = params[0].unwrap_i32();
/// value *= 2;
/// results[0] = value.into();
/// Ok(())
/// });
/// ///
/// let module = Module::new( /// let module = Module::new(
/// &store, /// &store,
@@ -144,8 +136,10 @@ use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
#[derive(Clone)] #[derive(Clone)]
pub struct Func { pub struct Func {
store: Store, store: Store,
callable: Rc<dyn WrappedCallable + 'static>, instance: InstanceHandle,
export: ExportFunction,
ty: FuncType, ty: FuncType,
trampoline: VMTrampoline,
} }
macro_rules! getters { macro_rules! getters {
@@ -213,15 +207,76 @@ impl Func {
/// * `ty` - the signature of this function, used to indicate what the /// * `ty` - the signature of this function, used to indicate what the
/// inputs and outputs are, which must be WebAssembly types. /// inputs and outputs are, which must be WebAssembly types.
/// ///
/// * `callable` - a type implementing the [`Callable`] trait which /// * `func` - the native code invoked whenever this `Func` will be called.
/// is the implementation of this `Func` value. /// This closure is provided a [`Caller`] as its first argument to learn
/// information about the caller, and then it's passed a list of
/// parameters as a slice along with a mutable slice of where to write
/// results.
/// ///
/// Note that the implementation of `callable` must adhere to the `ty` /// Note that the implementation of `func` must adhere to the `ty`
/// signature given, error or traps may occur if it does not respect the /// signature given, error or traps may occur if it does not respect the
/// `ty` signature. /// `ty` signature.
pub fn new(store: &Store, ty: FuncType, callable: Rc<dyn Callable + 'static>) -> Self { ///
let callable = Rc::new(NativeCallable::new(callable, &ty, &store)); /// Additionally note that this is quite a dynamic function since signatures
Func::from_wrapped(store, ty, callable) /// are not statically known. For a more performant `Func` it's recommended
/// to use [`Func::wrap`] if you can because with statically known
/// signatures the engine can optimize the implementation much more.
pub fn new(
store: &Store,
ty: FuncType,
func: impl Fn(Caller<'_>, &[Val], &mut [Val]) -> Result<(), Trap> + 'static,
) -> Self {
let store_clone = store.clone();
let ty_clone = ty.clone();
// Create our actual trampoline function which translates from a bunch
// of bit patterns on the stack to actual instances of `Val` being
// passed to the given function.
let func = Box::new(move |caller_vmctx, values_vec: *mut u128| {
// We have a dynamic guarantee that `values_vec` has the right
// number of arguments and the right types of arguments. As a result
// we should be able to safely run through them all and read them.
let mut args = Vec::with_capacity(ty_clone.params().len());
for (i, ty) in ty_clone.params().iter().enumerate() {
unsafe {
args.push(Val::read_value_from(values_vec.add(i), ty));
}
}
let mut returns = vec![Val::null(); ty_clone.results().len()];
func(
Caller {
store: &store_clone,
caller_vmctx,
},
&args,
&mut returns,
)?;
// Unlike our arguments we need to dynamically check that the return
// values produced are correct. There could be a bug in `func` that
// produces the wrong number or wrong types of values, and we need
// to catch that here.
for (i, (ret, ty)) in returns.iter_mut().zip(ty_clone.results()).enumerate() {
if ret.ty() != *ty {
return Err(Trap::new(
"function attempted to return an incompatible value",
));
}
unsafe {
ret.write_value_to(values_vec.add(i));
}
}
Ok(())
});
let (instance, export, trampoline) =
crate::trampoline::generate_func_export(&ty, func, store).expect("generated func");
Func {
store: store.clone(),
ty,
instance,
export,
trampoline,
}
} }
/// Creates a new `Func` from the given Rust closure. /// Creates a new `Func` from the given Rust closure.
@@ -414,18 +469,6 @@ impl Func {
func.into_func(store) func.into_func(store)
} }
fn from_wrapped(
store: &Store,
ty: FuncType,
callable: Rc<dyn WrappedCallable + 'static>,
) -> Func {
Func {
store: store.clone(),
callable,
ty,
}
}
/// Returns the underlying wasm type that this `Func` has. /// Returns the underlying wasm type that this `Func` has.
pub fn ty(&self) -> &FuncType { pub fn ty(&self) -> &FuncType {
&self.ty &self.ty
@@ -451,26 +494,71 @@ impl Func {
/// This function should not panic unless the underlying function itself /// This function should not panic unless the underlying function itself
/// initiates a panic. /// initiates a panic.
pub fn call(&self, params: &[Val]) -> Result<Box<[Val]>, Trap> { pub fn call(&self, params: &[Val]) -> Result<Box<[Val]>, Trap> {
for param in params { // We need to perform a dynamic check that the arguments given to us
if !param.comes_from_same_store(&self.store) { // match the signature of this function and are appropriate to pass to
// this function. This involves checking to make sure we have the right
// number and types of arguments as well as making sure everything is
// from the same `Store`.
if self.ty.params().len() != params.len() {
return Err(Trap::new(format!(
"expected {} arguments, got {}",
self.ty.params().len(),
params.len()
)));
}
let mut values_vec = vec![0; max(params.len(), self.ty.results().len())];
// Store the argument values into `values_vec`.
let param_tys = self.ty.params().iter();
for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) {
if arg.ty() != *ty {
return Err(Trap::new("argument type mismatch"));
}
if !arg.comes_from_same_store(&self.store) {
return Err(Trap::new( return Err(Trap::new(
"cross-`Store` values are not currently supported", "cross-`Store` values are not currently supported",
)); ));
} }
unsafe {
arg.write_value_to(slot);
}
} }
let mut results = vec![Val::null(); self.result_arity()];
self.callable.call(params, &mut results)?; // Call the trampoline.
Ok(results.into_boxed_slice()) if let Err(error) = unsafe {
wasmtime_runtime::catch_traps(self.export.vmctx, || {
(self.trampoline)(
self.export.vmctx,
ptr::null_mut(),
self.export.address,
values_vec.as_mut_ptr(),
)
})
} {
return Err(Trap::from_jit(error));
}
// Load the return values out of `values_vec`.
let mut results = Vec::with_capacity(self.ty.results().len());
for (index, ty) in self.ty.results().iter().enumerate() {
unsafe {
let ptr = values_vec.as_ptr().add(index);
results.push(Val::read_value_from(ptr, ty));
}
}
Ok(results.into())
} }
pub(crate) fn wasmtime_function(&self) -> &wasmtime_runtime::ExportFunction { pub(crate) fn wasmtime_function(&self) -> &wasmtime_runtime::ExportFunction {
self.callable.wasmtime_function() &self.export
} }
pub(crate) fn from_wasmtime_function( pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::ExportFunction, export: wasmtime_runtime::ExportFunction,
store: &Store, store: &Store,
instance_handle: InstanceHandle, instance: InstanceHandle,
) -> Self { ) -> Self {
// Signatures should always be registered in the store's registry of // Signatures should always be registered in the store's registry of
// shared signatures, so we should be able to unwrap safely here. // shared signatures, so we should be able to unwrap safely here.
@@ -489,12 +577,17 @@ impl Func {
// Each function signature in a module should have a trampoline stored // Each function signature in a module should have a trampoline stored
// on that module as well, so unwrap the result here since otherwise // on that module as well, so unwrap the result here since otherwise
// it's a bug in wasmtime. // it's a bug in wasmtime.
let trampoline = instance_handle let trampoline = instance
.trampoline(export.signature) .trampoline(export.signature)
.expect("failed to retrieve trampoline from module"); .expect("failed to retrieve trampoline from module");
let callable = WasmtimeFn::new(store, instance_handle, export, trampoline); Func {
Func::from_wrapped(store, ty, Rc::new(callable)) instance,
export,
trampoline,
ty,
store: store.clone(),
}
} }
getters! { getters! {
@@ -998,8 +1091,13 @@ macro_rules! impl_into_func {
Box::new((self, store_clone)), Box::new((self, store_clone)),
) )
.expect("failed to generate export"); .expect("failed to generate export");
let callable = Rc::new(WasmtimeFn::new(store, instance, export, trampoline)); Func {
Func::from_wrapped(store, ty, callable) store: store.clone(),
ty,
instance,
export,
trampoline,
}
} }
} }
} }

View File

@@ -8,7 +8,6 @@
#![deny(missing_docs, intra_doc_link_resolution_failure)] #![deny(missing_docs, intra_doc_link_resolution_failure)]
mod callable;
mod externals; mod externals;
mod frame_info; mod frame_info;
mod func; mod func;
@@ -21,7 +20,6 @@ mod trap;
mod types; mod types;
mod values; mod values;
pub use crate::callable::Callable;
pub use crate::externals::*; pub use crate::externals::*;
pub use crate::frame_info::FrameInfo; pub use crate::frame_info::FrameInfo;
pub use crate::func::*; pub use crate::func::*;

View File

@@ -1,18 +1,15 @@
//! Support for a calling of an imported function. //! Support for a calling of an imported function.
use super::create_handle::create_handle; use super::create_handle::create_handle;
use crate::{Callable, FuncType, Store, Trap, Val}; use crate::{FuncType, Store, Trap};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::any::Any; use std::any::Any;
use std::cmp; use std::cmp;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem; use std::mem;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::rc::Rc; use wasmtime_environ::entity::PrimaryMap;
use wasmtime_environ::entity::{EntityRef, PrimaryMap};
use wasmtime_environ::ir::types;
use wasmtime_environ::isa::TargetIsa; use wasmtime_environ::isa::TargetIsa;
use wasmtime_environ::wasm::FuncIndex;
use wasmtime_environ::{ use wasmtime_environ::{
ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module, ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module,
}; };
@@ -26,22 +23,15 @@ use wasmtime_jit::{native, CodeMemory};
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody, VMTrampoline}; use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody, VMTrampoline};
struct TrampolineState { struct TrampolineState {
func: Rc<dyn Callable + 'static>, func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
#[allow(dead_code)] #[allow(dead_code)]
code_memory: CodeMemory, code_memory: CodeMemory,
} }
impl TrampolineState {
fn new(func: Rc<dyn Callable + 'static>, code_memory: CodeMemory) -> Self {
TrampolineState { func, code_memory }
}
}
unsafe extern "C" fn stub_fn( unsafe extern "C" fn stub_fn(
vmctx: *mut VMContext, vmctx: *mut VMContext,
_caller_vmctx: *mut VMContext, caller_vmctx: *mut VMContext,
call_id: u32, values_vec: *mut u128,
values_vec: *mut i128,
) { ) {
// Here we are careful to use `catch_unwind` to ensure Rust panics don't // 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 // unwind past us. The primary reason for this is that Rust considers it UB
@@ -56,7 +46,9 @@ unsafe extern "C" fn stub_fn(
// below will trigger a longjmp, which won't run local destructors if we // below will trigger a longjmp, which won't run local destructors if we
// have any. To prevent leaks we avoid having any local destructors by // have any. To prevent leaks we avoid having any local destructors by
// avoiding local variables. // avoiding local variables.
let result = panic::catch_unwind(AssertUnwindSafe(|| call_stub(vmctx, call_id, values_vec))); let result = panic::catch_unwind(AssertUnwindSafe(|| {
call_stub(vmctx, caller_vmctx, values_vec)
}));
match result { match result {
Ok(Ok(())) => {} Ok(Ok(())) => {}
@@ -76,54 +68,23 @@ unsafe extern "C" fn stub_fn(
unsafe fn call_stub( unsafe fn call_stub(
vmctx: *mut VMContext, vmctx: *mut VMContext,
call_id: u32, caller_vmctx: *mut VMContext,
values_vec: *mut i128, values_vec: *mut u128,
) -> Result<(), Trap> { ) -> Result<(), Trap> {
let instance = InstanceHandle::from_vmctx(vmctx); let instance = InstanceHandle::from_vmctx(vmctx);
let (args, returns_len) = {
let module = instance.module_ref();
let signature =
&module.local.signatures[module.local.functions[FuncIndex::new(call_id as usize)]];
let mut args = Vec::new();
for i in 2..signature.params.len() {
args.push(Val::read_value_from(
values_vec.offset(i as isize - 2),
signature.params[i].value_type,
))
}
(args, signature.returns.len())
};
let mut returns = vec![Val::null(); returns_len];
let state = &instance let state = &instance
.host_state() .host_state()
.downcast_ref::<TrampolineState>() .downcast_ref::<TrampolineState>()
.expect("state"); .expect("state");
state.func.call(&args, &mut returns)?; (state.func)(caller_vmctx, values_vec)
let module = instance.module_ref();
let signature =
&module.local.signatures[module.local.functions[FuncIndex::new(call_id as usize)]];
for (i, ret) in returns.iter_mut().enumerate() {
if ret.ty().get_wasmtime_type() != Some(signature.returns[i].value_type) {
return Err(Trap::new(
"`Callable` attempted to return an incompatible value",
));
}
ret.write_value_to(values_vec.add(i));
}
Ok(())
} }
} }
/// Create a trampoline for invoking a Callable. /// Create a trampoline for invoking a function.
fn make_trampoline( fn make_trampoline(
isa: &dyn TargetIsa, isa: &dyn TargetIsa,
code_memory: &mut CodeMemory, code_memory: &mut CodeMemory,
fn_builder_ctx: &mut FunctionBuilderContext, fn_builder_ctx: &mut FunctionBuilderContext,
call_id: u32,
signature: &ir::Signature, signature: &ir::Signature,
) -> *mut [VMFunctionBody] { ) -> *mut [VMFunctionBody] {
// Mostly reverse copy of the similar method from wasmtime's // Mostly reverse copy of the similar method from wasmtime's
@@ -140,9 +101,6 @@ fn make_trampoline(
// Add the caller `vmctx` parameter. // Add the caller `vmctx` parameter.
stub_sig.params.push(ir::AbiParam::new(pointer_type)); stub_sig.params.push(ir::AbiParam::new(pointer_type));
// Add the `call_id` parameter.
stub_sig.params.push(ir::AbiParam::new(types::I32));
// Add the `values_vec` parameter. // Add the `values_vec` parameter.
stub_sig.params.push(ir::AbiParam::new(pointer_type)); stub_sig.params.push(ir::AbiParam::new(pointer_type));
@@ -188,14 +146,8 @@ fn make_trampoline(
let block_params = builder.func.dfg.block_params(block0); let block_params = builder.func.dfg.block_params(block0);
let vmctx_ptr_val = block_params[0]; let vmctx_ptr_val = block_params[0];
let caller_vmctx_ptr_val = block_params[1]; let caller_vmctx_ptr_val = block_params[1];
let call_id_val = builder.ins().iconst(types::I32, call_id as i64);
let callee_args = vec![ let callee_args = vec![vmctx_ptr_val, caller_vmctx_ptr_val, values_vec_ptr_val];
vmctx_ptr_val,
caller_vmctx_ptr_val,
call_id_val,
values_vec_ptr_val,
];
let new_sig = builder.import_signature(stub_sig); let new_sig = builder.import_signature(stub_sig);
@@ -249,9 +201,9 @@ fn make_trampoline(
pub fn create_handle_with_function( pub fn create_handle_with_function(
ft: &FuncType, ft: &FuncType,
func: &Rc<dyn Callable + 'static>, func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
store: &Store, store: &Store,
) -> Result<InstanceHandle> { ) -> Result<(InstanceHandle, VMTrampoline)> {
let isa = { let isa = {
let isa_builder = native::builder(); let isa_builder = native::builder();
let flag_builder = settings::builder(); let flag_builder = settings::builder();
@@ -277,13 +229,7 @@ pub fn create_handle_with_function(
module module
.exports .exports
.insert("trampoline".to_string(), Export::Function(func_id)); .insert("trampoline".to_string(), Export::Function(func_id));
let trampoline = make_trampoline( let trampoline = make_trampoline(isa.as_ref(), &mut code_memory, &mut fn_builder_ctx, &sig);
isa.as_ref(),
&mut code_memory,
&mut fn_builder_ctx,
func_id.index() as u32,
&sig,
);
finished_functions.push(trampoline); finished_functions.push(trampoline);
// ... and then we also need a trampoline with the standard "trampoline ABI" // ... and then we also need a trampoline with the standard "trampoline ABI"
@@ -304,7 +250,7 @@ pub fn create_handle_with_function(
// code memory (makes it executable) and ensuring all our various bits of // code memory (makes it executable) and ensuring all our various bits of
// state make it into the instance constructors. // state make it into the instance constructors.
code_memory.publish(); code_memory.publish();
let trampoline_state = TrampolineState::new(func.clone(), code_memory); let trampoline_state = TrampolineState { func, code_memory };
create_handle( create_handle(
module, module,
store, store,
@@ -312,6 +258,7 @@ pub fn create_handle_with_function(
trampolines, trampolines,
Box::new(trampoline_state), Box::new(trampoline_state),
) )
.map(|instance| (instance, trampoline))
} }
pub unsafe fn create_handle_with_raw_function( pub unsafe fn create_handle_with_raw_function(

View File

@@ -10,23 +10,23 @@ use self::func::create_handle_with_function;
use self::global::create_global; use self::global::create_global;
use self::memory::create_handle_with_memory; use self::memory::create_handle_with_memory;
use self::table::create_handle_with_table; use self::table::create_handle_with_table;
use super::{Callable, FuncType, GlobalType, MemoryType, Store, TableType, Val}; use crate::{FuncType, GlobalType, MemoryType, Store, TableType, Trap, Val};
use anyhow::Result; use anyhow::Result;
use std::any::Any; use std::any::Any;
use std::rc::Rc; use wasmtime_runtime::{VMContext, VMFunctionBody, VMTrampoline};
use wasmtime_runtime::{VMFunctionBody, VMTrampoline};
pub fn generate_func_export( pub fn generate_func_export(
ft: &FuncType, ft: &FuncType,
func: &Rc<dyn Callable + 'static>, func: Box<dyn Fn(*mut VMContext, *mut u128) -> Result<(), Trap>>,
store: &Store, store: &Store,
) -> Result<( ) -> Result<(
wasmtime_runtime::InstanceHandle, wasmtime_runtime::InstanceHandle,
wasmtime_runtime::ExportFunction, wasmtime_runtime::ExportFunction,
VMTrampoline,
)> { )> {
let instance = create_handle_with_function(ft, func, store)?; let (instance, trampoline) = create_handle_with_function(ft, func, store)?;
match instance.lookup("trampoline").expect("trampoline export") { match instance.lookup("trampoline").expect("trampoline export") {
wasmtime_runtime::Export::Function(f) => Ok((instance, f)), wasmtime_runtime::Export::Function(f) => Ok((instance, f, trampoline)),
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@@ -2,7 +2,6 @@ use crate::r#ref::AnyRef;
use crate::{Func, Store, ValType}; use crate::{Func, Store, ValType};
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use std::ptr; use std::ptr;
use wasmtime_environ::ir;
/// Possible runtime values that a WebAssembly module can either consume or /// Possible runtime values that a WebAssembly module can either consume or
/// produce. /// produce.
@@ -81,7 +80,7 @@ impl Val {
} }
} }
pub(crate) unsafe fn write_value_to(&self, p: *mut i128) { pub(crate) unsafe fn write_value_to(&self, p: *mut u128) {
match self { match self {
Val::I32(i) => ptr::write(p as *mut i32, *i), Val::I32(i) => ptr::write(p as *mut i32, *i),
Val::I64(i) => ptr::write(p as *mut i64, *i), Val::I64(i) => ptr::write(p as *mut i64, *i),
@@ -92,13 +91,13 @@ impl Val {
} }
} }
pub(crate) unsafe fn read_value_from(p: *const i128, ty: ir::Type) -> Val { pub(crate) unsafe fn read_value_from(p: *const u128, ty: &ValType) -> Val {
match ty { match ty {
ir::types::I32 => Val::I32(ptr::read(p as *const i32)), ValType::I32 => Val::I32(ptr::read(p as *const i32)),
ir::types::I64 => Val::I64(ptr::read(p as *const i64)), ValType::I64 => Val::I64(ptr::read(p as *const i64)),
ir::types::F32 => Val::F32(ptr::read(p as *const u32)), ValType::F32 => Val::F32(ptr::read(p as *const u32)),
ir::types::F64 => Val::F64(ptr::read(p as *const u64)), ValType::F64 => Val::F64(ptr::read(p as *const u64)),
ir::types::I8X16 => Val::V128(ptr::read(p as *const u128)), ValType::V128 => Val::V128(ptr::read(p as *const u128)),
_ => unimplemented!("Val::read_value_from"), _ => unimplemented!("Val::read_value_from"),
} }
} }

View File

@@ -1,5 +1,4 @@
use anyhow::Result; use anyhow::Result;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use wasmtime::*; use wasmtime::*;
@@ -238,21 +237,15 @@ fn get_from_wrapper() {
#[test] #[test]
fn get_from_signature() { fn get_from_signature() {
struct Foo;
impl Callable for Foo {
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
panic!()
}
}
let store = Store::default(); let store = Store::default();
let ty = FuncType::new(Box::new([]), Box::new([])); let ty = FuncType::new(Box::new([]), Box::new([]));
let f = Func::new(&store, ty, Rc::new(Foo)); let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_ok()); assert!(f.get0::<()>().is_ok());
assert!(f.get0::<i32>().is_err()); assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.get1::<i32, ()>().is_err());
let ty = FuncType::new(Box::new([ValType::I32]), Box::new([ValType::F64])); let ty = FuncType::new(Box::new([ValType::I32]), Box::new([ValType::F64]));
let f = Func::new(&store, ty, Rc::new(Foo)); let f = Func::new(&store, ty, |_, _, _| panic!());
assert!(f.get0::<()>().is_err()); assert!(f.get0::<()>().is_err());
assert!(f.get0::<i32>().is_err()); assert!(f.get0::<i32>().is_err());
assert!(f.get1::<i32, ()>().is_err()); assert!(f.get1::<i32, ()>().is_err());
@@ -392,3 +385,16 @@ fn caller_memory() -> anyhow::Result<()> {
Instance::new(&module, &[f.into()])?; Instance::new(&module, &[f.into()])?;
Ok(()) Ok(())
} }
#[test]
fn func_write_nothing() -> anyhow::Result<()> {
let store = Store::default();
let ty = FuncType::new(Box::new([]), Box::new([ValType::I32]));
let f = Func::new(&store, ty, |_, _, _| Ok(()));
let err = f.call(&[]).unwrap_err();
assert_eq!(
err.message(),
"function attempted to return an incompatible value"
);
Ok(())
}

View File

@@ -1,4 +1,3 @@
use std::rc::Rc;
use wasmtime::*; use wasmtime::*;
#[test] #[test]
@@ -15,28 +14,6 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
) )
"#; "#;
struct Ret1;
impl Callable for Ret1 {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
assert!(params.is_empty());
assert_eq!(results.len(), 1);
results[0] = 1i32.into();
Ok(())
}
}
struct Ret2;
impl Callable for Ret2 {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
assert!(params.is_empty());
assert_eq!(results.len(), 1);
results[0] = 2.0f32.into();
Ok(())
}
}
let store = Store::default(); let store = Store::default();
let module = Module::new(&store, WAT)?; let module = Module::new(&store, WAT)?;
@@ -44,13 +21,23 @@ fn same_import_names_still_distinct() -> anyhow::Result<()> {
Func::new( Func::new(
&store, &store,
FuncType::new(Box::new([]), Box::new([ValType::I32])), FuncType::new(Box::new([]), Box::new([ValType::I32])),
Rc::new(Ret1), |_, params, results| {
assert!(params.is_empty());
assert_eq!(results.len(), 1);
results[0] = 1i32.into();
Ok(())
},
) )
.into(), .into(),
Func::new( Func::new(
&store, &store,
FuncType::new(Box::new([]), Box::new([ValType::F32])), FuncType::new(Box::new([]), Box::new([ValType::F32])),
Rc::new(Ret2), |_, params, results| {
assert!(params.is_empty());
assert_eq!(results.len(), 1);
results[0] = 2.0f32.into();
Ok(())
},
) )
.into(), .into(),
]; ];

View File

@@ -15,33 +15,24 @@ fn test_import_calling_export() {
) )
"#; "#;
struct Callback { let store = Store::default();
pub other: RefCell<Option<Func>>, let module = Module::new(&store, WAT).expect("failed to create module");
}
impl Callable for Callback { let other = Rc::new(RefCell::new(None::<Func>));
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> { let other2 = other.clone();
self.other
let callback_func = Func::new(
&store,
FuncType::new(Box::new([]), Box::new([])),
move |_, _, _| {
other2
.borrow() .borrow()
.as_ref() .as_ref()
.expect("expected a function ref") .expect("expected a function ref")
.call(&[]) .call(&[])
.expect("expected function not to trap"); .expect("expected function not to trap");
Ok(()) Ok(())
} },
}
let store = Store::default();
let module = Module::new(&store, WAT).expect("failed to create module");
let callback = Rc::new(Callback {
other: RefCell::new(None),
});
let callback_func = Func::new(
&store,
FuncType::new(Box::new([]), Box::new([])),
callback.clone(),
); );
let imports = vec![callback_func.into()]; let imports = vec![callback_func.into()];
@@ -55,7 +46,7 @@ fn test_import_calling_export() {
.func() .func()
.expect("expected a run func in the module"); .expect("expected a run func in the module");
*callback.other.borrow_mut() = Some( *other.borrow_mut() = Some(
exports[1] exports[1]
.func() .func()
.expect("expected an other func in the module") .expect("expected an other func in the module")
@@ -76,25 +67,17 @@ fn test_returns_incorrect_type() {
) )
"#; "#;
struct EvilCallback;
impl Callable for EvilCallback {
fn call(&self, _params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
// Evil! Returns I64 here instead of promised in the signature I32.
results[0] = Val::I64(228);
Ok(())
}
}
let store = Store::default(); let store = Store::default();
let module = Module::new(&store, WAT).expect("failed to create module"); let module = Module::new(&store, WAT).expect("failed to create module");
let callback = Rc::new(EvilCallback);
let callback_func = Func::new( let callback_func = Func::new(
&store, &store,
FuncType::new(Box::new([]), Box::new([ValType::I32])), FuncType::new(Box::new([]), Box::new([ValType::I32])),
callback.clone(), |_, _, results| {
// Evil! Returns I64 here instead of promised in the signature I32.
results[0] = Val::I64(228);
Ok(())
},
); );
let imports = vec![callback_func.into()]; let imports = vec![callback_func.into()];
@@ -111,6 +94,6 @@ fn test_returns_incorrect_type() {
let trap = run_func.call(&[]).expect_err("the execution should fail"); let trap = run_func.call(&[]).expect_err("the execution should fail");
assert_eq!( assert_eq!(
trap.message(), trap.message(),
"`Callable` attempted to return an incompatible value" "function attempted to return an incompatible value"
); );
} }

View File

@@ -1,18 +1,9 @@
use anyhow::Result; use anyhow::Result;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::rc::Rc;
use wasmtime::*; use wasmtime::*;
#[test] #[test]
fn test_trap_return() -> Result<()> { fn test_trap_return() -> Result<()> {
struct HelloCallback;
impl Callable for HelloCallback {
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
Err(Trap::new("test 123"))
}
}
let store = Store::default(); let store = Store::default();
let wat = r#" let wat = r#"
(module (module
@@ -23,7 +14,7 @@ fn test_trap_return() -> Result<()> {
let module = Module::new(&store, wat)?; let module = Module::new(&store, wat)?;
let hello_type = FuncType::new(Box::new([]), Box::new([])); let hello_type = FuncType::new(Box::new([]), Box::new([]));
let hello_func = Func::new(&store, hello_type, Rc::new(HelloCallback)); let hello_func = Func::new(&store, hello_type, |_, _, _| Err(Trap::new("test 123")));
let instance = Instance::new(&module, &[hello_func.into()])?; let instance = Instance::new(&module, &[hello_func.into()])?;
let run_func = instance.exports()[0] let run_func = instance.exports()[0]
@@ -74,14 +65,6 @@ fn test_trap_trace() -> Result<()> {
#[test] #[test]
fn test_trap_trace_cb() -> Result<()> { fn test_trap_trace_cb() -> Result<()> {
struct ThrowCallback;
impl Callable for ThrowCallback {
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
Err(Trap::new("cb throw"))
}
}
let store = Store::default(); let store = Store::default();
let wat = r#" let wat = r#"
(module $hello_mod (module $hello_mod
@@ -92,7 +75,7 @@ fn test_trap_trace_cb() -> Result<()> {
"#; "#;
let fn_type = FuncType::new(Box::new([]), Box::new([])); let fn_type = FuncType::new(Box::new([]), Box::new([]));
let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback)); let fn_func = Func::new(&store, fn_type, |_, _, _| Err(Trap::new("cb throw")));
let module = Module::new(&store, wat)?; let module = Module::new(&store, wat)?;
let instance = Instance::new(&module, &[fn_func.into()])?; let instance = Instance::new(&module, &[fn_func.into()])?;
@@ -223,14 +206,6 @@ wasm backtrace:
#[test] #[test]
fn trap_start_function_import() -> Result<()> { fn trap_start_function_import() -> Result<()> {
struct ReturnTrap;
impl Callable for ReturnTrap {
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
Err(Trap::new("user trap"))
}
}
let store = Store::default(); let store = Store::default();
let binary = wat::parse_str( let binary = wat::parse_str(
r#" r#"
@@ -243,7 +218,7 @@ fn trap_start_function_import() -> Result<()> {
let module = Module::new(&store, &binary)?; let module = Module::new(&store, &binary)?;
let sig = FuncType::new(Box::new([]), Box::new([])); let sig = FuncType::new(Box::new([]), Box::new([]));
let func = Func::new(&store, sig, Rc::new(ReturnTrap)); let func = Func::new(&store, sig, |_, _, _| Err(Trap::new("user trap")));
let err = Instance::new(&module, &[func.into()]).err().unwrap(); let err = Instance::new(&module, &[func.into()]).err().unwrap();
assert_eq!(err.downcast_ref::<Trap>().unwrap().message(), "user trap"); assert_eq!(err.downcast_ref::<Trap>().unwrap().message(), "user trap");
Ok(()) Ok(())
@@ -251,14 +226,6 @@ fn trap_start_function_import() -> Result<()> {
#[test] #[test]
fn rust_panic_import() -> Result<()> { fn rust_panic_import() -> Result<()> {
struct Panic;
impl Callable for Panic {
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
panic!("this is a panic");
}
}
let store = Store::default(); let store = Store::default();
let binary = wat::parse_str( let binary = wat::parse_str(
r#" r#"
@@ -273,7 +240,7 @@ fn rust_panic_import() -> Result<()> {
let module = Module::new(&store, &binary)?; let module = Module::new(&store, &binary)?;
let sig = FuncType::new(Box::new([]), Box::new([])); let sig = FuncType::new(Box::new([]), Box::new([]));
let func = Func::new(&store, sig, Rc::new(Panic)); let func = Func::new(&store, sig, |_, _, _| panic!("this is a panic"));
let instance = Instance::new( let instance = Instance::new(
&module, &module,
&[ &[
@@ -302,14 +269,6 @@ fn rust_panic_import() -> Result<()> {
#[test] #[test]
fn rust_panic_start_function() -> Result<()> { fn rust_panic_start_function() -> Result<()> {
struct Panic;
impl Callable for Panic {
fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> {
panic!("this is a panic");
}
}
let store = Store::default(); let store = Store::default();
let binary = wat::parse_str( let binary = wat::parse_str(
r#" r#"
@@ -322,7 +281,7 @@ fn rust_panic_start_function() -> Result<()> {
let module = Module::new(&store, &binary)?; let module = Module::new(&store, &binary)?;
let sig = FuncType::new(Box::new([]), Box::new([])); let sig = FuncType::new(Box::new([]), Box::new([]));
let func = Func::new(&store, sig, Rc::new(Panic)); let func = Func::new(&store, sig, |_, _, _| panic!("this is a panic"));
let err = panic::catch_unwind(AssertUnwindSafe(|| { let err = panic::catch_unwind(AssertUnwindSafe(|| {
drop(Instance::new(&module, &[func.into()])); drop(Instance::new(&module, &[func.into()]));
})) }))

View File

@@ -7,12 +7,11 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::panic::{self, AssertUnwindSafe}; use std::panic::{self, AssertUnwindSafe};
use std::rc::Rc;
use std::{mem, ptr, slice}; use std::{mem, ptr, slice};
use wasmtime::{ use wasmtime::{
AnyRef, Callable, Config, Engine, ExportType, Extern, ExternType, Func, FuncType, Global, AnyRef, Config, Engine, ExportType, Extern, ExternType, Func, FuncType, Global, GlobalType,
GlobalType, HostInfo, HostRef, ImportType, Instance, Limits, Memory, MemoryType, Module, Store, HostInfo, HostRef, ImportType, Instance, Limits, Memory, MemoryType, Module, Store, Table,
Table, TableType, Trap, Val, ValType, TableType, Trap, Val, ValType,
}; };
mod ext; mod ext;
@@ -622,66 +621,6 @@ impl wasm_val_t {
} }
} }
struct Callback {
callback: wasm_func_callback_t,
}
impl Callable for Callback {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
let params = params
.iter()
.map(|p| wasm_val_t::from_val(p))
.collect::<Vec<_>>();
let mut out_results = vec![wasm_val_t::default(); results.len()];
let func = self.callback.expect("wasm_func_callback_t fn");
let out = unsafe { func(params.as_ptr(), out_results.as_mut_ptr()) };
if !out.is_null() {
let trap: Box<wasm_trap_t> = unsafe { Box::from_raw(out) };
return Err(trap.trap.borrow().clone());
}
for i in 0..results.len() {
results[i] = out_results[i].val();
}
Ok(())
}
}
struct CallbackWithEnv {
callback: wasm_func_callback_with_env_t,
env: *mut std::ffi::c_void,
finalizer: std::option::Option<unsafe extern "C" fn(env: *mut std::ffi::c_void)>,
}
impl Callable for CallbackWithEnv {
fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
let params = params
.iter()
.map(|p| wasm_val_t::from_val(p))
.collect::<Vec<_>>();
let mut out_results = vec![wasm_val_t::default(); results.len()];
let func = self.callback.expect("wasm_func_callback_with_env_t fn");
let out = unsafe { func(self.env, params.as_ptr(), out_results.as_mut_ptr()) };
if !out.is_null() {
let trap: Box<wasm_trap_t> = unsafe { Box::from_raw(out) };
return Err(trap.trap.borrow().clone());
}
for i in 0..results.len() {
results[i] = out_results[i].val();
}
Ok(())
}
}
impl Drop for CallbackWithEnv {
fn drop(&mut self) {
if let Some(finalizer) = self.finalizer {
unsafe {
finalizer(self.env);
}
}
}
}
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasm_func_new( pub unsafe extern "C" fn wasm_func_new(
store: *mut wasm_store_t, store: *mut wasm_store_t,
@@ -690,10 +629,26 @@ pub unsafe extern "C" fn wasm_func_new(
) -> *mut wasm_func_t { ) -> *mut wasm_func_t {
let store = &(*store).store.borrow(); let store = &(*store).store.borrow();
let ty = (*ty).functype.clone(); let ty = (*ty).functype.clone();
let callback = Rc::new(Callback { callback }); let func = Func::new(store, ty, move |_, params, results| {
let params = params
.iter()
.map(|p| wasm_val_t::from_val(p))
.collect::<Vec<_>>();
let mut out_results = vec![wasm_val_t::default(); results.len()];
let func = callback.expect("wasm_func_callback_t fn");
let out = func(params.as_ptr(), out_results.as_mut_ptr());
if !out.is_null() {
let trap: Box<wasm_trap_t> = Box::from_raw(out);
return Err(trap.trap.borrow().clone());
}
for i in 0..results.len() {
results[i] = out_results[i].val();
}
Ok(())
});
let func = Box::new(wasm_func_t { let func = Box::new(wasm_func_t {
ext: wasm_extern_t { ext: wasm_extern_t {
which: ExternHost::Func(HostRef::new(Func::new(store, ty, callback))), which: ExternHost::Func(HostRef::new(func)),
}, },
}); });
Box::into_raw(func) Box::into_raw(func)
@@ -925,18 +880,48 @@ pub unsafe extern "C" fn wasm_func_new_with_env(
ty: *const wasm_functype_t, ty: *const wasm_functype_t,
callback: wasm_func_callback_with_env_t, callback: wasm_func_callback_with_env_t,
env: *mut std::ffi::c_void, env: *mut std::ffi::c_void,
finalizer: std::option::Option<unsafe extern "C" fn(arg1: *mut std::ffi::c_void)>, finalizer: Option<unsafe extern "C" fn(arg1: *mut std::ffi::c_void)>,
) -> *mut wasm_func_t { ) -> *mut wasm_func_t {
let store = &(*store).store.borrow(); let store = &(*store).store.borrow();
let ty = (*ty).functype.clone(); let ty = (*ty).functype.clone();
let callback = Rc::new(CallbackWithEnv {
callback, // Create a small object which will run the finalizer when it's dropped, and
env, // then we move this `run_finalizer` object into the closure below (via the
finalizer, // `drop(&run_finalizer)` statement so it's all dropped when the closure is
// dropped.
struct RunOnDrop<F: FnMut()>(F);
impl<F: FnMut()> Drop for RunOnDrop<F> {
fn drop(&mut self) {
(self.0)();
}
}
let run_finalizer = RunOnDrop(move || {
if let Some(finalizer) = finalizer {
finalizer(env);
}
}); });
let func = Func::new(store, ty, move |_, params, results| {
drop(&run_finalizer);
let params = params
.iter()
.map(|p| wasm_val_t::from_val(p))
.collect::<Vec<_>>();
let mut out_results = vec![wasm_val_t::default(); results.len()];
let func = callback.expect("wasm_func_callback_with_env_t fn");
let out = func(env, params.as_ptr(), out_results.as_mut_ptr());
if !out.is_null() {
let trap: Box<wasm_trap_t> = Box::from_raw(out);
return Err(trap.trap.borrow().clone());
}
for i in 0..results.len() {
results[i] = out_results[i].val();
}
Ok(())
});
let func = Box::new(wasm_func_t { let func = Box::new(wasm_func_t {
ext: wasm_extern_t { ext: wasm_extern_t {
which: ExternHost::Func(HostRef::new(Func::new(store, ty, callback))), which: ExternHost::Func(HostRef::new(func)),
}, },
}); });
Box::into_raw(func) Box::into_raw(func)

View File

@@ -1,9 +1,8 @@
//! Dummy implementations of things that a Wasm module can import. //! Dummy implementations of things that a Wasm module can import.
use std::rc::Rc;
use wasmtime::{ use wasmtime::{
Callable, Extern, ExternType, Func, FuncType, Global, GlobalType, ImportType, Memory, Extern, ExternType, Func, FuncType, Global, GlobalType, ImportType, Memory, MemoryType, Store,
MemoryType, Store, Table, TableType, Trap, Val, ValType, Table, TableType, Trap, Val, ValType,
}; };
/// Create a set of dummy functions/globals/etc for the given imports. /// Create a set of dummy functions/globals/etc for the given imports.
@@ -11,7 +10,7 @@ pub fn dummy_imports(store: &Store, import_tys: &[ImportType]) -> Result<Vec<Ext
let mut imports = Vec::with_capacity(import_tys.len()); let mut imports = Vec::with_capacity(import_tys.len());
for imp in import_tys { for imp in import_tys {
imports.push(match imp.ty() { imports.push(match imp.ty() {
ExternType::Func(func_ty) => Extern::Func(DummyFunc::new(&store, func_ty.clone())), ExternType::Func(func_ty) => Extern::Func(dummy_func(&store, func_ty.clone())),
ExternType::Global(global_ty) => { ExternType::Global(global_ty) => {
Extern::Global(dummy_global(&store, global_ty.clone())?) Extern::Global(dummy_global(&store, global_ty.clone())?)
} }
@@ -22,27 +21,14 @@ pub fn dummy_imports(store: &Store, import_tys: &[ImportType]) -> Result<Vec<Ext
Ok(imports) Ok(imports)
} }
/// A function that doesn't do anything but return the default (zero) value for /// Construct a dummy function for the given function type
/// the function's type. pub fn dummy_func(store: &Store, ty: FuncType) -> Func {
#[derive(Debug)] Func::new(store, ty.clone(), move |_, _, results| {
pub struct DummyFunc(FuncType); for (ret_ty, result) in ty.results().iter().zip(results) {
impl DummyFunc {
/// Construct a new dummy `Func`.
pub fn new(store: &Store, ty: FuncType) -> Func {
let callable = DummyFunc(ty.clone());
Func::new(store, ty, Rc::new(callable) as _)
}
}
impl Callable for DummyFunc {
fn call(&self, _params: &[Val], results: &mut [Val]) -> Result<(), Trap> {
for (ret_ty, result) in self.0.results().iter().zip(results) {
*result = dummy_value(ret_ty)?; *result = dummy_value(ret_ty)?;
} }
Ok(()) Ok(())
} })
} }
/// Construct a dummy value for the given value type. /// Construct a dummy value for the given value type.

View File

@@ -4,7 +4,6 @@ use crate::value::{pyobj_to_value, value_to_pyobj};
use pyo3::exceptions::Exception; use pyo3::exceptions::Exception;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyAny, PyDict, PyTuple}; use pyo3::types::{PyAny, PyDict, PyTuple};
use std::rc::Rc;
// TODO support non-export functions // TODO support non-export functions
#[pyclass] #[pyclass]
@@ -52,69 +51,6 @@ fn parse_annotation_type(s: &str) -> wasmtime::ValType {
_ => panic!("unknown type in annotations"), _ => panic!("unknown type in annotations"),
} }
} }
struct WrappedFn {
func: PyObject,
returns_types: Vec<wasmtime::ValType>,
}
impl WrappedFn {
pub fn new(func: PyObject, returns_types: Vec<wasmtime::ValType>) -> Self {
WrappedFn {
func,
returns_types,
}
}
}
impl wasmtime::Callable for WrappedFn {
fn call(
&self,
params: &[wasmtime::Val],
returns: &mut [wasmtime::Val],
) -> Result<(), wasmtime::Trap> {
let gil = Python::acquire_gil();
let py = gil.python();
let params = params
.iter()
.map(|p| match p {
wasmtime::Val::I32(i) => i.clone().into_py(py),
wasmtime::Val::I64(i) => i.clone().into_py(py),
_ => {
panic!();
}
})
.collect::<Vec<PyObject>>();
let result = self
.func
.call(py, PyTuple::new(py, params), None)
.expect("TODO: convert result to trap");
let result = if let Ok(t) = result.cast_as::<PyTuple>(py) {
t
} else {
if result.is_none() {
PyTuple::empty(py)
} else {
PyTuple::new(py, &[result])
}
};
for (i, ty) in self.returns_types.iter().enumerate() {
let result_item = result.get_item(i);
returns[i] = match ty {
wasmtime::ValType::I32 => wasmtime::Val::I32(result_item.extract::<i32>().unwrap()),
wasmtime::ValType::I64 => wasmtime::Val::I64(result_item.extract::<i64>().unwrap()),
_ => {
panic!();
}
};
}
Ok(())
}
}
pub fn wrap_into_pyfunction(store: &wasmtime::Store, callable: &PyAny) -> PyResult<wasmtime::Func> { pub fn wrap_into_pyfunction(store: &wasmtime::Store, callable: &PyAny) -> PyResult<wasmtime::Func> {
if !callable.hasattr("__annotations__")? { if !callable.hasattr("__annotations__")? {
// TODO support calls without annotations? // TODO support calls without annotations?
@@ -140,6 +76,45 @@ pub fn wrap_into_pyfunction(store: &wasmtime::Store, callable: &PyAny) -> PyResu
); );
let gil = Python::acquire_gil(); let gil = Python::acquire_gil();
let wrapped = WrappedFn::new(callable.to_object(gil.python()), returns); let func = callable.to_object(gil.python());
Ok(wasmtime::Func::new(store, ft, Rc::new(wrapped))) Ok(wasmtime::Func::new(store, ft, move |_, params, results| {
let gil = Python::acquire_gil();
let py = gil.python();
let params = params
.iter()
.map(|p| match p {
wasmtime::Val::I32(i) => i.clone().into_py(py),
wasmtime::Val::I64(i) => i.clone().into_py(py),
wasmtime::Val::F32(i) => i.clone().into_py(py),
wasmtime::Val::F64(i) => i.clone().into_py(py),
_ => panic!(),
})
.collect::<Vec<PyObject>>();
let result = func
.call(py, PyTuple::new(py, params), None)
.expect("TODO: convert result to trap");
let result = if let Ok(t) = result.cast_as::<PyTuple>(py) {
t
} else {
if result.is_none() {
PyTuple::empty(py)
} else {
PyTuple::new(py, &[result])
}
};
for (i, ty) in returns.iter().enumerate() {
let result_item = result.get_item(i);
results[i] = match ty {
wasmtime::ValType::I32 => wasmtime::Val::I32(result_item.extract::<i32>().unwrap()),
wasmtime::ValType::I64 => wasmtime::Val::I64(result_item.extract::<i64>().unwrap()),
_ => {
panic!();
}
};
}
Ok(())
}))
} }

View File

@@ -45,9 +45,7 @@ pub use crate::sig_registry::SignatureRegistry;
pub use crate::table::Table; pub use crate::table::Table;
pub use crate::trap_registry::{TrapDescription, TrapRegistration, TrapRegistry}; pub use crate::trap_registry::{TrapDescription, TrapRegistration, TrapRegistry};
pub use crate::traphandlers::resume_panic; pub use crate::traphandlers::resume_panic;
pub use crate::traphandlers::{ pub use crate::traphandlers::{catch_traps, raise_lib_trap, raise_user_trap, Trap};
catch_traps, raise_lib_trap, raise_user_trap, wasmtime_call_trampoline, Trap,
};
pub use crate::vmcontext::{ pub use crate::vmcontext::{
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition,
VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex,

View File

@@ -3,13 +3,12 @@
use crate::instance::{InstanceHandle, SignalHandler}; use crate::instance::{InstanceHandle, SignalHandler};
use crate::trap_registry::TrapDescription; use crate::trap_registry::TrapDescription;
use crate::vmcontext::{VMContext, VMFunctionBody, VMTrampoline}; use crate::vmcontext::VMContext;
use backtrace::Backtrace; use backtrace::Backtrace;
use std::any::Any; use std::any::Any;
use std::cell::Cell; use std::cell::Cell;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::mem;
use std::ptr; use std::ptr;
use wasmtime_environ::ir; use wasmtime_environ::ir;
@@ -62,13 +61,13 @@ cfg_if::cfg_if! {
/// ///
/// This function performs as-if a wasm trap was just executed, only the trap /// This function performs as-if a wasm trap was just executed, only the trap
/// has a dynamic payload associated with it which is user-provided. This trap /// has a dynamic payload associated with it which is user-provided. This trap
/// payload is then returned from `wasmtime_call` an `wasmtime_call_trampoline` /// payload is then returned from `catch_traps` below.
/// below.
/// ///
/// # Safety /// # Safety
/// ///
/// Only safe to call when wasm code is on the stack, aka `wasmtime_call` or /// Only safe to call when wasm code is on the stack, aka `catch_traps` must
/// `wasmtime_call_trampoline` must have been previously called. /// have been previously called. Additionally no Rust destructors can be on the
/// stack. They will be skipped and not executed.
pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! { pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data))) tls::with(|info| info.unwrap().unwind_with(UnwindReason::UserTrap(data)))
} }
@@ -76,13 +75,13 @@ pub unsafe fn raise_user_trap(data: Box<dyn Error + Send + Sync>) -> ! {
/// Raises a trap from inside library code immediately. /// Raises a trap from inside library code immediately.
/// ///
/// This function performs as-if a wasm trap was just executed. This trap /// This function performs as-if a wasm trap was just executed. This trap
/// payload is then returned from `wasmtime_call` and `wasmtime_call_trampoline` /// payload is then returned from `catch_traps` below.
/// below.
/// ///
/// # Safety /// # Safety
/// ///
/// Only safe to call when wasm code is on the stack, aka `wasmtime_call` or /// Only safe to call when wasm code is on the stack, aka `catch_traps` must
/// `wasmtime_call_trampoline` must have been previously called. /// have been previously called. Additionally no Rust destructors can be on the
/// stack. They will be skipped and not executed.
pub unsafe fn raise_lib_trap(trap: Trap) -> ! { pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap))) tls::with(|info| info.unwrap().unwind_with(UnwindReason::LibTrap(trap)))
} }
@@ -92,8 +91,9 @@ pub unsafe fn raise_lib_trap(trap: Trap) -> ! {
/// ///
/// # Safety /// # Safety
/// ///
/// Only safe to call when wasm code is on the stack, aka `wasmtime_call` or /// Only safe to call when wasm code is on the stack, aka `catch_traps` must
/// `wasmtime_call_trampoline` must have been previously called. /// have been previously called. Additionally no Rust destructors can be on the
/// stack. They will be skipped and not executed.
pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! { pub unsafe fn resume_panic(payload: Box<dyn Any + Send>) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload))) tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(payload)))
} }
@@ -154,34 +154,6 @@ impl Trap {
} }
} }
/// Call the wasm function pointed to by `callee`.
///
/// * `vmctx` - the callee vmctx argument
/// * `caller_vmctx` - the caller vmctx argument
/// * `trampoline` - the jit-generated trampoline whose ABI takes 4 values, the
/// callee vmctx, the caller vmctx, the `callee` argument below, and then the
/// `values_vec` argument.
/// * `callee` - the third argument to the `trampoline` function
/// * `values_vec` - points to a buffer which holds the incoming arguments, and to
/// which the outgoing return values will be written.
///
/// Wildly unsafe because it calls raw function pointers and reads/writes raw
/// function pointers.
pub unsafe fn wasmtime_call_trampoline(
vmctx: *mut VMContext,
caller_vmctx: *mut VMContext,
trampoline: VMTrampoline,
callee: *const VMFunctionBody,
values_vec: *mut u8,
) -> Result<(), Trap> {
catch_traps(vmctx, || {
mem::transmute::<
_,
extern "C" fn(*mut VMContext, *mut VMContext, *const VMFunctionBody, *mut u8),
>(trampoline)(vmctx, caller_vmctx, callee, values_vec)
})
}
/// Catches any wasm traps that happen within the execution of `closure`, /// Catches any wasm traps that happen within the execution of `closure`,
/// returning them as a `Result`. /// returning them as a `Result`.
/// ///

View File

@@ -8,22 +8,8 @@
// You can execute this example with `cargo run --example multi` // You can execute this example with `cargo run --example multi`
use anyhow::{format_err, Result}; use anyhow::{format_err, Result};
use std::rc::Rc;
use wasmtime::*; use wasmtime::*;
struct Callback;
impl Callable for Callback {
fn call(&self, args: &[Val], results: &mut [Val]) -> Result<(), Trap> {
println!("Calling back...");
println!("> {} {}", args[0].unwrap_i32(), args[1].unwrap_i64());
results[0] = Val::I64(args[1].unwrap_i64() + 1);
results[1] = Val::I32(args[0].unwrap_i32() + 1);
Ok(())
}
}
fn main() -> Result<()> { fn main() -> Result<()> {
// Configure our `Store`, but be sure to use a `Config` that enables the // Configure our `Store`, but be sure to use a `Config` that enables the
// wasm multi-value feature since it's not stable yet. // wasm multi-value feature since it's not stable yet.
@@ -41,7 +27,14 @@ fn main() -> Result<()> {
Box::new([ValType::I32, ValType::I64]), Box::new([ValType::I32, ValType::I64]),
Box::new([ValType::I64, ValType::I32]), Box::new([ValType::I64, ValType::I32]),
); );
let callback_func = Func::new(&store, callback_type, Rc::new(Callback)); let callback_func = Func::new(&store, callback_type, |_, args, results| {
println!("Calling back...");
println!("> {} {}", args[0].unwrap_i32(), args[1].unwrap_i64());
results[0] = Val::I64(args[1].unwrap_i64() + 1);
results[1] = Val::I32(args[0].unwrap_i32() + 1);
Ok(())
});
// Instantiate. // Instantiate.
println!("Instantiating module..."); println!("Instantiating module...");