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:
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()]));
|
||||||
}))
|
}))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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(())
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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`.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -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...");
|
||||||
|
|||||||
Reference in New Issue
Block a user