use crate::store::{StoreData, StoreOpaque, StoreOpaqueSend, Stored};
use crate::{
AsContext, AsContextMut, Engine, Extern, FuncType, InterruptHandle, StoreContext,
StoreContextMut, Trap, Val, ValType,
};
use anyhow::{bail, Context as _, Result};
use smallvec::{smallvec, SmallVec};
use std::cmp::max;
use std::error::Error;
use std::fmt;
use std::future::Future;
use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::pin::Pin;
use std::ptr::NonNull;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::Arc;
use wasmtime_environ::wasm::{EntityIndex, FuncIndex};
use wasmtime_runtime::{
raise_user_trap, ExportFunction, InstanceAllocator, InstanceHandle, OnDemandInstanceAllocator,
VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMSharedSignatureIndex,
VMTrampoline,
};
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] pub struct Func(Stored<FuncData>);
pub(crate) enum FuncData {
StoreOwned {
trampoline: VMTrampoline,
export: ExportFunction,
},
SharedHost(Arc<HostFunc>),
Host(HostFunc),
}
macro_rules! for_each_function_signature {
($mac:ident) => {
$mac!(0);
$mac!(1 A1);
$mac!(2 A1 A2);
$mac!(3 A1 A2 A3);
$mac!(4 A1 A2 A3 A4);
$mac!(5 A1 A2 A3 A4 A5);
$mac!(6 A1 A2 A3 A4 A5 A6);
$mac!(7 A1 A2 A3 A4 A5 A6 A7);
$mac!(8 A1 A2 A3 A4 A5 A6 A7 A8);
$mac!(9 A1 A2 A3 A4 A5 A6 A7 A8 A9);
$mac!(10 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10);
$mac!(11 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11);
$mac!(12 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12);
$mac!(13 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13);
$mac!(14 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14);
$mac!(15 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15);
$mac!(16 A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 A11 A12 A13 A14 A15 A16);
};
}
mod typed;
pub use typed::*;
macro_rules! generate_wrap_async_func {
($num:tt $($args:ident)*) => (paste::paste!{
#[allow(non_snake_case)]
#[cfg(feature = "async")]
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
pub fn [<wrap $num _async>]<T, $($args,)* R>(
store: impl AsContextMut<Data = T>,
func: impl for<'a> Fn(Caller<'a, T>, $($args),*) -> Box<dyn Future<Output = R> + Send + 'a> + Send + Sync + 'static,
) -> Func
where
$($args: WasmTy,)*
R: WasmRet,
{
assert!(store.as_context().async_support(), concat!("cannot use `wrap", $num, "_async` without enabling async support on the config"));
Func::wrap(store, move |mut caller: Caller<'_, T>, $($args: $args),*| {
let async_cx = caller.store.as_context_mut().opaque().async_cx();
let mut future = Pin::from(func(caller, $($args),*));
match unsafe { async_cx.block_on(future.as_mut()) } {
Ok(ret) => ret.into_fallible(),
Err(e) => R::fallible_from_trap(e),
}
})
}
})
}
impl Func {
pub fn new<T>(
mut store: impl AsContextMut<Data = T>,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static,
) -> Self {
let mut store = store.as_context_mut().opaque();
unsafe {
let host = HostFunc::new(store.engine(), ty, func);
host.into_func(&mut store)
}
}
#[cfg(feature = "async")]
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
pub fn new_async<T, F>(store: impl AsContextMut<Data = T>, ty: FuncType, func: F) -> Func
where
F: for<'a> Fn(
Caller<'a, T>,
&'a [Val],
&'a mut [Val],
) -> Box<dyn Future<Output = Result<(), Trap>> + Send + 'a>
+ Send
+ Sync
+ 'static,
{
assert!(
store.as_context().async_support(),
"cannot use `new_async` without enabling async support in the config"
);
Func::new(store, ty, move |mut caller, params, results| {
let async_cx = caller.store.as_context_mut().opaque().async_cx();
let mut future = Pin::from(func(caller, params, results));
match unsafe { async_cx.block_on(future.as_mut()) } {
Ok(Ok(())) => Ok(()),
Ok(Err(trap)) | Err(trap) => Err(trap),
}
})
}
pub(crate) unsafe fn from_caller_checked_anyfunc(
store: &mut StoreOpaque,
anyfunc: *mut VMCallerCheckedAnyfunc,
) -> Option<Self> {
let anyfunc = NonNull::new(anyfunc)?;
debug_assert!(anyfunc.as_ref().type_index != VMSharedSignatureIndex::default());
let export = ExportFunction { anyfunc };
Some(Func::from_wasmtime_function(export, store))
}
pub fn wrap<T, Params, Results>(
mut store: impl AsContextMut<Data = T>,
func: impl IntoFunc<T, Params, Results>,
) -> Func {
let mut store = store.as_context_mut().opaque();
unsafe {
let host = HostFunc::wrap(store.engine(), func);
host.into_func(&mut store)
}
}
for_each_function_signature!(generate_wrap_async_func);
pub fn ty(&self, store: impl AsContext) -> FuncType {
let store = store.as_context();
let sig_index = unsafe { store[self.0].export().anyfunc.as_ref().type_index };
FuncType::from_wasm_func_type(
store
.engine()
.signatures()
.lookup_type(sig_index)
.expect("signature should be registered"),
)
}
pub(crate) fn sig_index(&self, data: &StoreData) -> VMSharedSignatureIndex {
unsafe { data[self.0].export().anyfunc.as_ref().type_index }
}
pub fn call(&self, mut store: impl AsContextMut, params: &[Val]) -> Result<Box<[Val]>> {
assert!(
!store.as_context().async_support(),
"must use `call_async` when async support is enabled on the config",
);
let my_ty = self.ty(&store);
store.as_context_mut().0.exiting_native_hook()?;
let r = self.call_impl(&mut store.as_context_mut().opaque(), my_ty, params);
store.as_context_mut().0.entering_native_hook()?;
r
}
#[cfg(feature = "async")]
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
pub async fn call_async<T>(
&self,
mut store: impl AsContextMut<Data = T>,
params: &[Val],
) -> Result<Box<[Val]>>
where
T: Send,
{
let my_ty = self.ty(&store);
store.as_context_mut().0.exiting_native_hook()?;
let r = self
._call_async(store.as_context_mut().opaque_send(), my_ty, params)
.await;
store.as_context_mut().0.entering_native_hook()?;
r
}
#[cfg(feature = "async")]
async fn _call_async(
&self,
mut store: StoreOpaqueSend<'_>,
my_ty: FuncType,
params: &[Val],
) -> Result<Box<[Val]>> {
assert!(
store.async_support(),
"cannot use `call_async` without enabling async support in the config",
);
let result = store
.on_fiber(|store| self.call_impl(store, my_ty, params))
.await??;
Ok(result)
}
fn call_impl(
&self,
store: &mut StoreOpaque<'_>,
my_ty: FuncType,
params: &[Val],
) -> Result<Box<[Val]>> {
let data = &store[self.0];
let trampoline = data.trampoline();
let anyfunc = data.export().anyfunc;
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();
for ((arg, slot), ty) in params.iter().cloned().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(store) {
bail!("cross-`Store` values are not currently supported");
}
unsafe {
arg.write_value_to(store, slot);
}
}
unsafe {
let anyfunc = anyfunc.as_ref();
invoke_wasm_and_catch_traps(store, |callee| {
trampoline(
anyfunc.vmctx,
callee,
anyfunc.func_ptr.as_ptr(),
values_vec.as_mut_ptr(),
)
})?;
}
let mut results = Vec::with_capacity(my_ty.results().len());
for (index, ty) in my_ty.results().enumerate() {
unsafe {
let ptr = values_vec.as_ptr().add(index);
results.push(Val::read_value_from(store, ptr, ty));
}
}
Ok(results.into())
}
#[inline]
pub(crate) fn caller_checked_anyfunc(
&self,
store: &StoreOpaque,
) -> NonNull<VMCallerCheckedAnyfunc> {
store[self.0].export().anyfunc
}
pub(crate) unsafe fn from_wasmtime_function(
export: ExportFunction,
store: &mut StoreOpaque,
) -> Self {
let anyfunc = export.anyfunc.as_ref();
let trampoline = store.lookup_trampoline(&*anyfunc);
let data = FuncData::StoreOwned { trampoline, export };
Func(store.store_data_mut().insert(data))
}
pub(crate) fn vmimport(&self, store: &mut StoreOpaque<'_>) -> VMFunctionImport {
unsafe {
let f = self.caller_checked_anyfunc(store);
VMFunctionImport {
body: f.as_ref().func_ptr,
vmctx: f.as_ref().vmctx,
}
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.store_data().contains(self.0)
}
fn invoke<T>(
mut caller: Caller<'_, T>,
ty: &FuncType,
values_vec: *mut u128,
func: &dyn Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap>,
) -> Result<(), Trap> {
caller.store.0.entering_native_hook()?;
const STACK_ARGS: usize = 4;
const STACK_RETURNS: usize = 2;
let mut args: SmallVec<[Val; STACK_ARGS]> = SmallVec::with_capacity(ty.params().len());
let mut store = caller.store.as_context_mut().opaque();
for (i, ty) in ty.params().enumerate() {
unsafe {
let val = Val::read_value_from(&mut store, values_vec.add(i), ty);
args.push(val);
}
}
let mut returns: SmallVec<[Val; STACK_RETURNS]> =
smallvec![Val::null(); ty.results().len()];
func(caller.sub_caller(), &args, &mut returns)?;
let mut store = caller.store.as_context_mut().opaque();
for (i, (ret, ty)) in returns.into_iter().zip(ty.results()).enumerate() {
if ret.ty() != ty {
return Err(Trap::new(
"function attempted to return an incompatible value",
));
}
if !ret.comes_from_same_store(&store) {
return Err(Trap::new(
"cross-`Store` values are not currently supported",
));
}
unsafe {
ret.write_value_to(&mut store, values_vec.add(i));
}
}
caller.store.0.exiting_native_hook()?;
Ok(())
}
pub fn typed<Params, Results, S>(&self, store: S) -> Result<TypedFunc<Params, Results>>
where
Params: WasmParams,
Results: WasmResults,
S: AsContext,
{
let ty = self.ty(store);
Params::typecheck(ty.params()).context("type mismatch with parameters")?;
Results::typecheck(ty.results()).context("type mismatch with results")?;
unsafe { Ok(TypedFunc::new_unchecked(*self)) }
}
}
#[inline]
pub(crate) fn invoke_wasm_and_catch_traps(
store: &mut StoreOpaque<'_>,
closure: impl FnMut(*mut VMContext),
) -> Result<(), Trap> {
unsafe {
let exit = if store.externref_activations_table().stack_canary().is_some() {
false
} else {
enter_wasm(store)?;
true
};
let result = wasmtime_runtime::catch_traps(
store.vminterrupts(),
store.signal_handler(),
store.default_callee(),
closure,
);
if exit {
exit_wasm(store);
}
result.map_err(Trap::from_runtime)
}
}
#[inline]
fn enter_wasm(store: &mut StoreOpaque<'_>) -> Result<(), Trap> {
let stack_pointer = psm::stack_pointer() as usize;
let wasm_stack_limit = stack_pointer - store.engine().config().max_wasm_stack;
let interrupts = store.interrupts();
match interrupts.stack_limit.swap(wasm_stack_limit, Relaxed) {
wasmtime_environ::INTERRUPTED => {
interrupts.stack_limit.store(usize::max_value(), Relaxed);
return Err(Trap::new_wasm(
None,
wasmtime_environ::ir::TrapCode::Interrupt,
backtrace::Backtrace::new_unresolved(),
));
}
n => debug_assert_eq!(usize::max_value(), n),
}
store
.externref_activations_table()
.set_stack_canary(Some(stack_pointer));
Ok(())
}
#[inline]
fn exit_wasm(store: &mut StoreOpaque<'_>) {
store.externref_activations_table().set_stack_canary(None);
store
.interrupts()
.stack_limit
.store(usize::max_value(), Relaxed);
}
pub unsafe trait WasmRet {
#[doc(hidden)]
type Abi: Copy;
#[doc(hidden)]
type Retptr: Copy;
#[doc(hidden)]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool;
#[doc(hidden)]
unsafe fn into_abi_for_ret(
self,
store: &mut StoreOpaque,
ptr: Self::Retptr,
) -> Result<Self::Abi, Trap>;
#[doc(hidden)]
fn func_type(params: impl Iterator<Item = ValType>) -> FuncType;
#[doc(hidden)]
unsafe fn wrap_trampoline(ptr: *mut u128, f: impl FnOnce(Self::Retptr) -> Self::Abi);
#[doc(hidden)]
type Fallible: WasmRet<Abi = Self::Abi, Retptr = Self::Retptr>;
#[doc(hidden)]
fn into_fallible(self) -> Self::Fallible;
#[doc(hidden)]
fn fallible_from_trap(trap: Trap) -> Self::Fallible;
}
unsafe impl<T> WasmRet for T
where
T: WasmTy,
{
type Abi = <T as WasmTy>::Abi;
type Retptr = ();
type Fallible = Result<T, Trap>;
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
<Self as WasmTy>::compatible_with_store(self, store)
}
unsafe fn into_abi_for_ret(
self,
store: &mut StoreOpaque,
_retptr: (),
) -> Result<Self::Abi, Trap> {
Ok(<Self as WasmTy>::into_abi(self, store))
}
fn func_type(params: impl Iterator<Item = ValType>) -> FuncType {
FuncType::new(params, Some(<Self as WasmTy>::valtype()))
}
unsafe fn wrap_trampoline(ptr: *mut u128, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
*ptr.cast::<Self::Abi>() = f(());
}
fn into_fallible(self) -> Result<T, Trap> {
Ok(self)
}
fn fallible_from_trap(trap: Trap) -> Result<T, Trap> {
Err(trap)
}
}
unsafe impl<T> WasmRet for Result<T, Trap>
where
T: WasmRet,
{
type Abi = <T as WasmRet>::Abi;
type Retptr = <T as WasmRet>::Retptr;
type Fallible = Self;
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
match self {
Ok(x) => <T as WasmRet>::compatible_with_store(x, store),
Err(_) => true,
}
}
unsafe fn into_abi_for_ret(
self,
store: &mut StoreOpaque,
retptr: Self::Retptr,
) -> Result<Self::Abi, Trap> {
self.and_then(|val| val.into_abi_for_ret(store, retptr))
}
fn func_type(params: impl Iterator<Item = ValType>) -> FuncType {
T::func_type(params)
}
unsafe fn wrap_trampoline(ptr: *mut u128, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
T::wrap_trampoline(ptr, f)
}
fn into_fallible(self) -> Result<T, Trap> {
self
}
fn fallible_from_trap(trap: Trap) -> Result<T, Trap> {
Err(trap)
}
}
macro_rules! impl_wasm_host_results {
($n:tt $($t:ident)*) => (
#[allow(non_snake_case)]
unsafe impl<$($t),*> WasmRet for ($($t,)*)
where
$($t: WasmTy,)*
($($t::Abi,)*): HostAbi,
{
type Abi = <($($t::Abi,)*) as HostAbi>::Abi;
type Retptr = <($($t::Abi,)*) as HostAbi>::Retptr;
type Fallible = Result<Self, Trap>;
#[inline]
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
let ($($t,)*) = self;
$( $t.compatible_with_store(_store) && )* true
}
#[inline]
unsafe fn into_abi_for_ret(self, _store: &mut StoreOpaque, ptr: Self::Retptr) -> Result<Self::Abi, Trap> {
let ($($t,)*) = self;
let abi = ($($t.into_abi(_store),)*);
Ok(<($($t::Abi,)*) as HostAbi>::into_abi(abi, ptr))
}
fn func_type(params: impl Iterator<Item = ValType>) -> FuncType {
FuncType::new(
params,
std::array::IntoIter::new([$($t::valtype(),)*]),
)
}
#[allow(unused_assignments)]
unsafe fn wrap_trampoline(mut _ptr: *mut u128, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
let ($($t,)*) = <($($t::Abi,)*) as HostAbi>::call(f);
$(
*_ptr.cast() = $t;
_ptr = _ptr.add(1);
)*
}
#[inline]
fn into_fallible(self) -> Result<Self, Trap> {
Ok(self)
}
#[inline]
fn fallible_from_trap(trap: Trap) -> Result<Self, Trap> {
Err(trap)
}
}
)
}
for_each_function_signature!(impl_wasm_host_results);
#[doc(hidden)]
pub trait HostAbi {
type Abi: Copy;
type Retptr: Copy;
unsafe fn into_abi(self, ptr: Self::Retptr) -> Self::Abi;
unsafe fn call(f: impl FnOnce(Self::Retptr) -> Self::Abi) -> Self;
}
macro_rules! impl_host_abi {
(0) => {
impl HostAbi for () {
type Abi = ();
type Retptr = ();
#[inline]
unsafe fn into_abi(self, _ptr: Self::Retptr) -> Self::Abi {}
#[inline]
unsafe fn call(f: impl FnOnce(Self::Retptr) -> Self::Abi) -> Self {
f(())
}
}
};
(1 $a:ident) => {
impl<$a: Copy> HostAbi for ($a,) {
type Abi = $a;
type Retptr = ();
unsafe fn into_abi(self, _ptr: Self::Retptr) -> Self::Abi {
self.0
}
unsafe fn call(f: impl FnOnce(Self::Retptr) -> Self::Abi) -> Self {
(f(()),)
}
}
};
($n:tt $t:ident $($u:ident)*) => {paste::paste!{
#[doc(hidden)]
#[allow(non_snake_case)]
#[repr(C)]
#[cfg(not(feature = "old-x86-backend"))]
pub struct [<TupleRet $n>]<$($u,)*> {
$($u: $u,)*
}
#[cfg(not(feature = "old-x86-backend"))]
#[allow(non_snake_case, unused_assignments)]
impl<$t: Copy, $($u: Copy,)*> HostAbi for ($t, $($u,)*) {
type Abi = $t;
type Retptr = *mut [<TupleRet $n>]<$($u,)*>;
unsafe fn into_abi(self, ptr: Self::Retptr) -> Self::Abi {
let ($t, $($u,)*) = self;
$((*ptr).$u = $u;)*
$t
}
unsafe fn call(f: impl FnOnce(Self::Retptr) -> Self::Abi) -> Self {
let mut space = std::mem::MaybeUninit::uninit();
let t = f(space.as_mut_ptr());
let space = space.assume_init();
(t, $(space.$u,)*)
}
}
}};
}
for_each_function_signature!(impl_host_abi);
pub trait IntoFunc<T, Params, Results>: Send + Sync + 'static {
#[doc(hidden)]
fn into_func(self, engine: &Engine) -> (InstanceHandle, VMTrampoline);
}
pub struct Caller<'a, T> {
pub(crate) store: StoreContextMut<'a, T>,
caller: &'a InstanceHandle,
}
impl<T> Caller<'_, T> {
unsafe fn with<R>(caller: *mut VMContext, f: impl FnOnce(Caller<'_, T>) -> R) -> R {
assert!(!caller.is_null());
let instance = InstanceHandle::from_vmctx(caller);
let store = StoreContextMut::from_raw(instance.store());
f(Caller {
store,
caller: &instance,
})
}
fn sub_caller(&mut self) -> Caller<'_, T> {
Caller {
store: self.store.as_context_mut(),
caller: self.caller,
}
}
pub fn get_export(&mut self, name: &str) -> Option<Extern> {
unsafe {
let index = self.caller.module().exports.get(name)?;
match index {
EntityIndex::Memory(_) | EntityIndex::Function(_) => {
Some(Extern::from_wasmtime_export(
self.caller.lookup_by_declaration(&index),
&mut self.store.as_context_mut().opaque(),
))
}
_ => None,
}
}
}
pub fn data(&self) -> &T {
self.store.data()
}
pub fn data_mut(&mut self) -> &mut T {
self.store.data_mut()
}
pub fn engine(&self) -> &Engine {
self.store.engine()
}
pub fn interrupt_handle(&self) -> Result<InterruptHandle> {
self.store.interrupt_handle()
}
pub fn gc(&mut self) {
self.store.gc()
}
pub fn fuel_consumed(&self) -> Option<u64> {
self.store.fuel_consumed()
}
pub fn add_fuel(&mut self, fuel: u64) -> Result<()> {
self.store.add_fuel(fuel)
}
pub fn out_of_fuel_trap(&mut self) {
self.store.out_of_fuel_trap()
}
pub fn out_of_fuel_async_yield(&mut self, injection_count: u32, fuel_to_inject: u64) {
self.store
.out_of_fuel_async_yield(injection_count, fuel_to_inject)
}
}
impl<T> AsContext for Caller<'_, T> {
type Data = T;
fn as_context(&self) -> StoreContext<'_, T> {
self.store.as_context()
}
}
impl<T> AsContextMut for Caller<'_, T> {
fn as_context_mut(&mut self) -> StoreContextMut<'_, T> {
self.store.as_context_mut()
}
}
fn cross_store_trap() -> Box<dyn Error + Send + Sync> {
#[derive(Debug)]
struct CrossStoreError;
impl Error for CrossStoreError {}
impl fmt::Display for CrossStoreError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"host function attempted to return cross-`Store` \
value to Wasm",
)
}
}
Box::new(CrossStoreError)
}
macro_rules! impl_into_func {
($num:tt $($args:ident)*) => {
#[allow(non_snake_case)]
impl<T, F, $($args,)* R> IntoFunc<T, ($($args,)*), R> for F
where
F: Fn($($args),*) -> R + Send + Sync + 'static,
$($args: WasmTy,)*
R: WasmRet,
{
fn into_func(self, engine: &Engine) -> (InstanceHandle, VMTrampoline) {
let f = move |_: Caller<'_, T>, $($args:$args),*| {
self($($args),*)
};
f.into_func(engine)
}
}
#[allow(non_snake_case)]
impl<T, F, $($args,)* R> IntoFunc<T, (Caller<'_, T>, $($args,)*), R> for F
where
F: Fn(Caller<'_, T>, $($args),*) -> R + Send + Sync + 'static,
$($args: WasmTy,)*
R: WasmRet,
{
fn into_func(self, engine: &Engine) -> (InstanceHandle, VMTrampoline) {
unsafe extern "C" fn wasm_to_host_shim<T, F, $($args,)* R>(
vmctx: *mut VMContext,
caller_vmctx: *mut VMContext,
$( $args: $args::Abi, )*
retptr: R::Retptr,
) -> R::Abi
where
F: Fn(Caller<'_, T>, $( $args ),*) -> R + 'static,
$( $args: WasmTy, )*
R: WasmRet,
{
enum CallResult<U> {
Ok(U),
Trap(Box<dyn Error + Send + Sync>),
Panic(Box<dyn std::any::Any + Send>),
}
let result = Caller::with(caller_vmctx, |mut caller| {
let state = (*vmctx).host_state();
debug_assert!(state.is::<F>());
let func = &*(state as *const _ as *const F);
let ret = {
panic::catch_unwind(AssertUnwindSafe(|| {
if let Err(trap) = caller.store.0.entering_native_hook() {
return R::fallible_from_trap(trap);
}
let mut _store = caller.sub_caller().store.opaque();
$(let $args = $args::from_abi($args, &mut _store);)*
let r = func(
caller.sub_caller(),
$( $args, )*
);
if let Err(trap) = caller.store.0.exiting_native_hook() {
return R::fallible_from_trap(trap);
}
r.into_fallible()
}))
};
match ret {
Err(panic) => CallResult::Panic(panic),
Ok(ret) => {
let mut store = caller.store.opaque();
if !ret.compatible_with_store(&store) {
CallResult::Trap(cross_store_trap())
} else {
match ret.into_abi_for_ret(&mut store, retptr) {
Ok(val) => CallResult::Ok(val),
Err(trap) => CallResult::Trap(trap.into()),
}
}
}
}
});
match result {
CallResult::Ok(val) => val,
CallResult::Trap(trap) => raise_user_trap(trap),
CallResult::Panic(panic) => wasmtime_runtime::resume_panic(panic),
}
}
unsafe extern "C" fn host_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::Abi, )*
R::Retptr,
) -> R::Abi,
>(ptr);
let mut _n = 0;
$(
let $args = *args.add(_n).cast::<$args::Abi>();
_n += 1;
)*
R::wrap_trampoline(args, |retptr| {
ptr(callee_vmctx, caller_vmctx, $( $args, )* retptr)
});
}
let ty = R::func_type(
None::<ValType>.into_iter()
$(.chain(Some($args::valtype())))*
);
let shared_signature_id = engine.signatures().register(ty.as_wasm_func_type());
let trampoline = host_trampoline::<$($args,)* R>;
let instance = unsafe {
crate::trampoline::create_raw_function(
std::slice::from_raw_parts_mut(
wasm_to_host_shim::<T, F, $($args,)* R> as *mut _,
0,
),
shared_signature_id,
Box::new(self),
)
.expect("failed to create raw function")
};
(instance, trampoline)
}
}
}
}
for_each_function_signature!(impl_into_func);
pub(crate) struct HostFunc {
instance: InstanceHandle,
trampoline: VMTrampoline,
export: ExportFunction,
engine: Engine,
}
impl HostFunc {
pub fn new<T>(
engine: &Engine,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static,
) -> Self {
let ty_clone = ty.clone();
let func = Box::new(move |caller_vmctx, values_vec: *mut u128| unsafe {
Caller::with(caller_vmctx, |caller| {
Func::invoke(caller, &ty_clone, values_vec, &func)
})
});
let (instance, trampoline) = crate::trampoline::create_function(&ty, func, engine)
.expect("failed to create function");
HostFunc::_new(engine, instance, trampoline)
}
pub fn wrap<T, Params, Results>(
engine: &Engine,
func: impl IntoFunc<T, Params, Results>,
) -> Self {
let (instance, trampoline) = func.into_func(engine);
HostFunc::_new(engine, instance, trampoline)
}
fn _new(engine: &Engine, instance: InstanceHandle, trampoline: VMTrampoline) -> Self {
let idx = EntityIndex::Function(FuncIndex::from_u32(0));
let export = match instance.lookup_by_declaration(&idx) {
wasmtime_runtime::Export::Function(f) => f,
_ => unreachable!(),
};
HostFunc {
instance,
trampoline,
export,
engine: engine.clone(),
}
}
pub unsafe fn to_func(self: &Arc<Self>, store: &mut StoreOpaque<'_>) -> Func {
self.register_trampoline(store);
let me = self.clone();
Func(store.store_data_mut().insert(FuncData::SharedHost(me)))
}
unsafe fn into_func(self, store: &mut StoreOpaque<'_>) -> Func {
self.register_trampoline(store);
Func(store.store_data_mut().insert(FuncData::Host(self)))
}
unsafe fn register_trampoline(&self, store: &mut StoreOpaque<'_>) {
let idx = self.export.anyfunc.as_ref().type_index;
store.register_host_trampoline(idx, self.trampoline);
}
pub(crate) fn sig_index(&self) -> VMSharedSignatureIndex {
unsafe { self.export.anyfunc.as_ref().type_index }
}
}
impl Drop for HostFunc {
fn drop(&mut self) {
unsafe {
self.engine
.signatures()
.unregister(self.export.anyfunc.as_ref().type_index);
OnDemandInstanceAllocator::default().deallocate(&self.instance);
}
}
}
impl FuncData {
fn trampoline(&self) -> VMTrampoline {
match self {
FuncData::StoreOwned { trampoline, .. } => *trampoline,
FuncData::SharedHost(host) => host.trampoline,
FuncData::Host(host) => host.trampoline,
}
}
#[inline]
fn export(&self) -> &ExportFunction {
match self {
FuncData::StoreOwned { export, .. } => export,
FuncData::SharedHost(host) => &host.export,
FuncData::Host(host) => &host.export,
}
}
}