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 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 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)
}