use crate::runtime::StoreInner;
use crate::trampoline::StoreInstanceHandle;
use crate::{Extern, FuncType, Memory, Store, Trap, Val, ValType};
use anyhow::{bail, ensure, Context as _, Result};
use std::cmp::max;
use std::fmt;
use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::ptr;
use std::rc::Weak;
use wasmtime_runtime::{raise_user_trap, ExportFunction, VMTrampoline};
use wasmtime_runtime::{Export, InstanceHandle, VMContext, VMFunctionBody};
#[derive(Clone)]
pub struct Func {
instance: StoreInstanceHandle,
export: ExportFunction,
trampoline: VMTrampoline,
}
macro_rules! getters {
($(
$(#[$doc:meta])*
($name:ident $(,$args:ident)*)
)*) => ($(
$(#[$doc])*
#[allow(non_snake_case)]
pub fn $name<$($args,)* R>(&self)
-> anyhow::Result<impl Fn($($args,)*) -> Result<R, Trap>>
where
$($args: WasmTy,)*
R: WasmTy,
{
let ty = self.ty();
let mut params = ty.params().iter().cloned();
let n = 0;
$(
let n = n + 1;
$args::matches(&mut params)
.with_context(|| format!("Type mismatch in argument {}", n))?;
)*
ensure!(params.next().is_none(), "Type mismatch: too many arguments (expected {})", n);
let mut results = ty.results().iter().cloned();
R::matches(&mut results)
.context("Type mismatch in return type")?;
ensure!(results.next().is_none(), "Type mismatch: too many return values (expected 1)");
let instance = self.instance.clone();
let export = self.export.clone();
Ok(move |$($args: $args),*| -> Result<R, Trap> {
unsafe {
let fnptr = mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(
*mut VMContext,
*mut VMContext,
$($args,)*
) -> R,
>(export.address);
let mut ret = None;
$(let $args = $args.into_abi();)*
catch_traps(export.vmctx, &instance.store, || {
ret = Some(fnptr(export.vmctx, ptr::null_mut(), $($args,)*));
})?;
Ok(ret.unwrap())
}
})
}
)*)
}
impl Func {
pub fn new(
store: &Store,
ty: FuncType,
func: impl Fn(Caller<'_>, &[Val], &mut [Val]) -> Result<(), Trap> + 'static,
) -> Self {
let store_weak = store.weak();
let ty_clone = ty.clone();
let func = Box::new(move |caller_vmctx, values_vec: *mut u128| {
let mut args = Vec::with_capacity(ty_clone.params().len());
let store = Store::upgrade(&store_weak).unwrap();
for (i, ty) in ty_clone.params().iter().enumerate() {
unsafe {
args.push(Val::read_value_from(&store, values_vec.add(i), ty));
}
}
let mut returns = vec![Val::null(); ty_clone.results().len()];
func(
Caller {
store: &store_weak,
caller_vmctx,
},
&args,
&mut returns,
)?;
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 {
instance,
export,
trampoline,
}
}
pub fn wrap<Params, Results>(store: &Store, func: impl IntoFunc<Params, Results>) -> Func {
func.into_func(store)
}
pub fn ty(&self) -> FuncType {
let sig = self.instance.store.lookup_signature(self.export.signature);
FuncType::from_wasm_func_type(&sig).expect("core wasm signature should be supported")
}
pub fn param_arity(&self) -> usize {
let sig = self.instance.store.lookup_signature(self.export.signature);
sig.params.len()
}
pub fn result_arity(&self) -> usize {
let sig = self.instance.store.lookup_signature(self.export.signature);
sig.returns.len()
}
pub fn call(&self, params: &[Val]) -> Result<Box<[Val]>> {
let my_ty = self.ty();
if my_ty.params().len() != params.len() {
bail!(
"expected {} arguments, got {}",
my_ty.params().len(),
params.len()
);
}
let mut values_vec = vec![0; max(params.len(), my_ty.results().len())];
let param_tys = my_ty.params().iter();
for ((arg, slot), ty) in params.iter().zip(&mut values_vec).zip(param_tys) {
if arg.ty() != *ty {
bail!(
"argument type mismatch: found {} but expected {}",
arg.ty(),
ty
);
}
if !arg.comes_from_same_store(&self.instance.store) {
bail!("cross-`Store` values are not currently supported");
}
unsafe {
arg.write_value_to(slot);
}
}
catch_traps(self.export.vmctx, &self.instance.store, || unsafe {
(self.trampoline)(
self.export.vmctx,
ptr::null_mut(),
self.export.address,
values_vec.as_mut_ptr(),
)
})?;
let mut results = Vec::with_capacity(my_ty.results().len());
for (index, ty) in my_ty.results().iter().enumerate() {
unsafe {
let ptr = values_vec.as_ptr().add(index);
results.push(Val::read_value_from(&self.instance.store, ptr, ty));
}
}
Ok(results.into())
}
pub(crate) fn wasmtime_function(&self) -> &wasmtime_runtime::ExportFunction {
&self.export
}
pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::ExportFunction,
instance: StoreInstanceHandle,
) -> Self {
let trampoline = instance
.trampoline(export.signature)
.expect("failed to retrieve trampoline from module");
Func {
instance,
export,
trampoline,
}
}
getters! {
(get0)
(get1, A1)
(get2, A1, A2)
(get3, A1, A2, A3)
(get4, A1, A2, A3, A4)
(get5, A1, A2, A3, A4, A5)
(get6, A1, A2, A3, A4, A5, A6)
(get7, A1, A2, A3, A4, A5, A6, A7)
(get8, A1, A2, A3, A4, A5, A6, A7, A8)
(get9, A1, A2, A3, A4, A5, A6, A7, A8, A9)
(get10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10)
(get11, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11)
(get12, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12)
(get13, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13)
(get14, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14)
(get15, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15)
}
pub fn store(&self) -> &Store {
&self.instance.store
}
}
impl fmt::Debug for Func {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Func")
}
}
pub(crate) fn catch_traps(
vmctx: *mut VMContext,
store: &Store,
closure: impl FnMut(),
) -> Result<(), Trap> {
let signalhandler = store.signal_handler();
unsafe {
wasmtime_runtime::catch_traps(
vmctx,
store.engine().config().max_wasm_stack,
|addr| store.is_in_jit_code(addr),
signalhandler.as_deref(),
closure,
)
.map_err(Trap::from_runtime)
}
}
pub unsafe trait WasmTy: Copy {
#[doc(hidden)]
fn push(dst: &mut Vec<ValType>);
#[doc(hidden)]
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
#[doc(hidden)]
unsafe fn load(ptr: &mut *const u128) -> Self;
#[doc(hidden)]
unsafe fn store(abi: Self, ptr: *mut u128);
}
unsafe impl WasmTy for () {
fn push(_dst: &mut Vec<ValType>) {}
fn matches(_tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
Ok(())
}
#[inline]
unsafe fn load(_ptr: &mut *const u128) -> Self {}
#[inline]
unsafe fn store(_abi: Self, _ptr: *mut u128) {}
}
unsafe impl WasmTy for i32 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::I32);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::I32),
"Type mismatch, expected i32, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = **ptr as Self;
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi as u128;
}
}
unsafe impl WasmTy for u32 {
fn push(dst: &mut Vec<ValType>) {
<i32 as WasmTy>::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<i32 as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
<i32 as WasmTy>::load(ptr) as Self
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
<i32 as WasmTy>::store(abi as i32, ptr)
}
}
unsafe impl WasmTy for i64 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::I64);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::I64),
"Type mismatch, expected i64, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = **ptr as Self;
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi as u128;
}
}
unsafe impl WasmTy for u64 {
fn push(dst: &mut Vec<ValType>) {
<i64 as WasmTy>::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
<i64 as WasmTy>::matches(tys)
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
<i64 as WasmTy>::load(ptr) as Self
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
<i64 as WasmTy>::store(abi as i64, ptr)
}
}
unsafe impl WasmTy for f32 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::F32);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::F32),
"Type mismatch, expected f32, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = f32::from_bits(**ptr as u32);
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi.to_bits() as u128;
}
}
unsafe impl WasmTy for f64 {
fn push(dst: &mut Vec<ValType>) {
dst.push(ValType::F64);
}
fn matches(mut tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
let next = tys.next();
ensure!(
next == Some(ValType::F64),
"Type mismatch, expected f64, got {:?}",
next
);
Ok(())
}
#[inline]
unsafe fn load(ptr: &mut *const u128) -> Self {
let ret = f64::from_bits(**ptr as u64);
*ptr = (*ptr).add(1);
return ret;
}
#[inline]
unsafe fn store(abi: Self, ptr: *mut u128) {
*ptr = abi.to_bits() as u128;
}
}
pub unsafe trait WasmRet {
#[doc(hidden)]
type Abi;
#[doc(hidden)]
fn push(dst: &mut Vec<ValType>);
#[doc(hidden)]
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()>;
#[doc(hidden)]
fn into_abi(self) -> Self::Abi;
#[doc(hidden)]
unsafe fn store(abi: Self::Abi, ptr: *mut u128);
}
unsafe impl<T: WasmTy> WasmRet for T {
type Abi = T;
fn push(dst: &mut Vec<ValType>) {
T::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
T::matches(tys)
}
#[inline]
fn into_abi(self) -> Self::Abi {
self
}
#[inline]
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
T::store(abi, ptr);
}
}
unsafe impl<T: WasmTy> WasmRet for Result<T, Trap> {
type Abi = T;
fn push(dst: &mut Vec<ValType>) {
T::push(dst)
}
fn matches(tys: impl Iterator<Item = ValType>) -> anyhow::Result<()> {
T::matches(tys)
}
#[inline]
fn into_abi(self) -> Self::Abi {
match self {
Ok(val) => return T::into_abi(val),
Err(trap) => handle_trap(trap),
}
fn handle_trap(trap: Trap) -> ! {
unsafe { raise_user_trap(trap.into()) }
}
}
#[inline]
unsafe fn store(abi: Self::Abi, ptr: *mut u128) {
T::store(abi, ptr);
}
}
pub trait IntoFunc<Params, Results> {
#[doc(hidden)]
fn into_func(self, store: &Store) -> Func;
}
pub struct Caller<'a> {
store: &'a Weak<StoreInner>,
caller_vmctx: *mut VMContext,
}
impl Caller<'_> {
pub fn get_export(&self, name: &str) -> Option<Extern> {
unsafe {
if self.caller_vmctx.is_null() {
return None;
}
let instance = InstanceHandle::from_vmctx(self.caller_vmctx);
let export = match instance.lookup(name) {
Some(Export::Memory(m)) => m,
_ => return None,
};
debug_assert!(self.store.upgrade().is_some());
let handle =
Store::from_inner(self.store.upgrade()?).existing_instance_handle(instance);
let mem = Memory::from_wasmtime_memory(export, handle);
Some(Extern::Memory(mem))
}
}
pub fn store(&self) -> Store {
Store::upgrade(&self.store).unwrap()
}
}
macro_rules! impl_into_func {
($(
($($args:ident)*)
)*) => ($(
// Implement for functions without a leading `&Caller` parameter,
// delegating to the implementation below which does have the leading
// `Caller` parameter.
impl<F, $($args,)* R> IntoFunc<($($args,)*), R> for F
where
F: Fn($($args),*) -> R + 'static,
$($args: WasmTy,)*
R: WasmRet,
{
#[allow(non_snake_case)]
fn into_func(self, store: &Store) -> Func {
Func::wrap(store, move |_: Caller<'_>, $($args:$args),*| {
self($($args),*)
})
}
}
#[allow(non_snake_case)]
impl<F, $($args,)* R> IntoFunc<(Caller<'_>, $($args,)*), R> for F
where
F: Fn(Caller<'_>, $($args),*) -> R + 'static,
$($args: WasmTy,)*
R: WasmRet,
{
fn into_func(self, store: &Store) -> Func {
unsafe extern "C" fn shim<F, $($args,)* R>(
vmctx: *mut VMContext,
caller_vmctx: *mut VMContext,
$($args: $args,)*
) -> R::Abi
where
F: Fn(Caller<'_>, $($args),*) -> R + 'static,
$($args: WasmTy,)*
R: WasmRet,
{
let ret = {
let state = (*vmctx).host_state();
debug_assert!(state.is::<(F, Weak<StoreInner>)>());
let (func, store) = &*(state as *const _ as *const (F, Weak<StoreInner>));
panic::catch_unwind(AssertUnwindSafe(|| {
func(
Caller { store, caller_vmctx },
$($args,)*
)
}))
};
match ret {
Ok(ret) => ret.into_abi(),
Err(panic) => wasmtime_runtime::resume_panic(panic),
}
}
unsafe extern "C" fn trampoline<$($args,)* R>(
callee_vmctx: *mut VMContext,
caller_vmctx: *mut VMContext,
ptr: *const VMFunctionBody,
args: *mut u128,
)
where
$($args: WasmTy,)*
R: WasmRet,
{
let ptr = mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(
*mut VMContext,
*mut VMContext,
$($args,)*
) -> R::Abi,
>(ptr);
let mut _next = args as *const u128;
$(let $args = $args::load(&mut _next);)*
let ret = ptr(callee_vmctx, caller_vmctx, $($args),*);
R::store(ret, args);
}
let mut _args = Vec::new();
$($args::push(&mut _args);)*
let mut ret = Vec::new();
R::push(&mut ret);
let ty = FuncType::new(_args.into(), ret.into());
let store_weak = store.weak();
let trampoline = trampoline::<$($args,)* R>;
let (instance, export) = unsafe {
crate::trampoline::generate_raw_func_export(
&ty,
std::slice::from_raw_parts_mut(
shim::<F, $($args,)* R> as *mut _,
0,
),
trampoline,
store,
Box::new((self, store_weak)),
)
.expect("failed to generate export")
};
Func {
instance,
export,
trampoline,
}
}
}
)*)
}
impl_into_func! {
()
(A1)
(A1 A2)
(A1 A2 A3)
(A1 A2 A3 A4)
(A1 A2 A3 A4 A5)
(A1 A2 A3 A4 A5 A6)
(A1 A2 A3 A4 A5 A6 A7)
(A1 A2 A3 A4 A5 A6 A7 A8)
(A1 A2 A3 A4 A5 A6 A7 A8 A9)
(A1 A2 A3 A4 A5 A6 A7 A8 A9 A10)
(A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11)
(A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12)
(A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13)
(A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14)
(A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15)
}
#[test]
fn wasm_ty_roundtrip() -> Result<(), anyhow::Error> {
use crate::*;
let store = Store::default();
let debug = Func::wrap(&store, |a: i32, b: u32, c: f32, d: i64, e: u64, f: f64| {
assert_eq!(a, -1);
assert_eq!(b, 1);
assert_eq!(c, 2.0);
assert_eq!(d, -3);
assert_eq!(e, 3);
assert_eq!(f, 4.0);
});
let module = Module::new(
store.engine(),
r#"
(module
(import "" "" (func $debug (param i32 i32 f32 i64 i64 f64)))
(func (export "foo") (param i32 i32 f32 i64 i64 f64)
(if (i32.ne (local.get 0) (i32.const -1))
(then unreachable)
)
(if (i32.ne (local.get 1) (i32.const 1))
(then unreachable)
)
(if (f32.ne (local.get 2) (f32.const 2))
(then unreachable)
)
(if (i64.ne (local.get 3) (i64.const -3))
(then unreachable)
)
(if (i64.ne (local.get 4) (i64.const 3))
(then unreachable)
)
(if (f64.ne (local.get 5) (f64.const 4))
(then unreachable)
)
local.get 0
local.get 1
local.get 2
local.get 3
local.get 4
local.get 5
call $debug
)
)
"#,
)?;
let instance = Instance::new(&store, &module, &[debug.into()])?;
let foo = instance
.get_func("foo")
.unwrap()
.get6::<i32, u32, f32, i64, u64, f64, ()>()?;
foo(-1, 1, 2.0, -3, 3, 4.0)?;
Ok(())
}