use crate::store::{StoreData, StoreOpaque, Stored};
use crate::{
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, Module, StoreContext,
StoreContextMut, Val, ValRaw, ValType,
};
use anyhow::{bail, Context as _, Error, Result};
use std::ffi::c_void;
use std::future::Future;
use std::mem;
use std::panic::{self, AssertUnwindSafe};
use std::pin::Pin;
use std::ptr::{self, NonNull};
use std::sync::Arc;
use wasmtime_runtime::{
ExportFunction, SendSyncPtr, StoreBox, VMArrayCallHostFuncContext, VMContext, VMFuncRef,
VMFunctionImport, VMNativeCallHostFuncContext, VMOpaqueContext, VMSharedSignatureIndex,
};
#[derive(Copy, Clone, Debug)]
#[repr(transparent)] pub struct Func(Stored<FuncData>);
pub(crate) struct FuncData {
kind: FuncKind,
in_store_func_ref: Option<SendSyncPtr<VMFuncRef>>,
ty: Option<Box<FuncType>>,
}
enum FuncKind {
StoreOwned { export: ExportFunction },
SharedHost(Arc<HostFunc>),
Host(Box<HostFunc>),
RootedHost(RootedHostFunc),
}
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().0.async_cx().expect("Attempt to start async function on dying fiber");
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_error(e),
}
})
}
})
}
impl Func {
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub fn new<T>(
store: impl AsContextMut<Data = T>,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
) -> Self {
let ty_clone = ty.clone();
unsafe {
Func::new_unchecked(store, ty, move |caller, values| {
Func::invoke(caller, &ty_clone, values, &func)
})
}
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
#[cfg_attr(nightlydoc, doc(cfg(any(feature = "cranelift", feature = "winch"))))]
pub unsafe fn new_unchecked<T>(
mut store: impl AsContextMut<Data = T>,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
) -> Self {
let store = store.as_context_mut().0;
let host = HostFunc::new_unchecked(store.engine(), ty, func);
host.into_func(store)
}
#[cfg(all(feature = "async", feature = "cranelift"))]
#[cfg_attr(nightlydoc, doc(cfg(all(feature = "async", feature = "cranelift"))))]
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<()>> + 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()
.0
.async_cx()
.expect("Attempt to spawn new action on dying fiber");
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_func_ref(
store: &mut StoreOpaque,
raw: *mut VMFuncRef,
) -> Option<Func> {
let func_ref = NonNull::new(raw)?;
debug_assert!(func_ref.as_ref().type_index != VMSharedSignatureIndex::default());
let export = ExportFunction { func_ref };
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 store = store.as_context_mut().0;
unsafe {
let host = HostFunc::wrap(store.engine(), func);
host.into_func(store)
}
}
for_each_function_signature!(generate_wrap_async_func);
pub fn ty(&self, store: impl AsContext) -> FuncType {
self.load_ty(&store.as_context().0)
}
fn load_ty(&self, store: &StoreOpaque) -> FuncType {
FuncType::from_wasm_func_type(
store
.engine()
.signatures()
.lookup_type(self.sig_index(store.store_data()))
.expect("signature should be registered"),
)
}
fn ty_ref<'a>(&self, store: &'a mut StoreOpaque) -> (&'a FuncType, &'a StoreOpaque) {
if store.store_data()[self.0].ty.is_none() {
let ty = self.load_ty(store);
store.store_data_mut()[self.0].ty = Some(Box::new(ty));
}
(store.store_data()[self.0].ty.as_ref().unwrap(), store)
}
pub(crate) fn sig_index(&self, data: &StoreData) -> VMSharedSignatureIndex {
data[self.0].sig_index()
}
pub fn call(
&self,
mut store: impl AsContextMut,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
assert!(
!store.as_context().async_support(),
"must use `call_async` when async support is enabled on the config",
);
self.call_impl(&mut store.as_context_mut(), params, results)
}
pub unsafe fn call_unchecked(
&self,
mut store: impl AsContextMut,
params_and_returns: *mut ValRaw,
params_and_returns_capacity: usize,
) -> Result<()> {
let mut store = store.as_context_mut();
let data = &store.0.store_data()[self.0];
let func_ref = data.export().func_ref;
Self::call_unchecked_raw(
&mut store,
func_ref,
params_and_returns,
params_and_returns_capacity,
)
}
pub(crate) unsafe fn call_unchecked_raw<T>(
store: &mut StoreContextMut<'_, T>,
func_ref: NonNull<VMFuncRef>,
params_and_returns: *mut ValRaw,
params_and_returns_capacity: usize,
) -> Result<()> {
invoke_wasm_and_catch_traps(store, |caller| {
let func_ref = func_ref.as_ref();
(func_ref.array_call)(
func_ref.vmctx,
caller.cast::<VMOpaqueContext>(),
params_and_returns,
params_and_returns_capacity,
)
})
}
pub unsafe fn from_raw(mut store: impl AsContextMut, raw: *mut c_void) -> Option<Func> {
Func::from_caller_checked_func_ref(store.as_context_mut().0, raw.cast())
}
pub unsafe fn to_raw(&self, mut store: impl AsContextMut) -> *mut c_void {
self.vm_func_ref(store.as_context_mut().0).as_ptr().cast()
}
#[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],
results: &mut [Val],
) -> Result<()>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `call_async` without enabling async support in the config",
);
let result = store
.on_fiber(|store| self.call_impl(store, params, results))
.await??;
Ok(result)
}
fn call_impl<T>(
&self,
store: &mut StoreContextMut<'_, T>,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let (ty, opaque) = self.ty_ref(store.0);
if ty.params().len() != params.len() {
bail!(
"expected {} arguments, got {}",
ty.params().len(),
params.len()
);
}
if ty.results().len() != results.len() {
bail!(
"expected {} results, got {}",
ty.results().len(),
results.len()
);
}
for (ty, arg) in ty.params().zip(params) {
if arg.ty() != ty {
bail!(
"argument type mismatch: found {} but expected {}",
arg.ty(),
ty
);
}
if !arg.comes_from_same_store(opaque) {
bail!("cross-`Store` values are not currently supported");
}
}
let values_vec_size = params.len().max(ty.results().len());
if ty.as_wasm_func_type().externref_params_count()
> store
.0
.externref_activations_table()
.bump_capacity_remaining()
{
store.gc();
}
let mut values_vec = store.0.take_wasm_val_raw_storage();
debug_assert!(values_vec.is_empty());
values_vec.resize_with(values_vec_size, || ValRaw::i32(0));
for (arg, slot) in params.iter().cloned().zip(&mut values_vec) {
unsafe {
*slot = arg.to_raw(&mut *store);
}
}
unsafe {
self.call_unchecked(&mut *store, values_vec.as_mut_ptr(), values_vec_size)?;
}
for ((i, slot), val) in results.iter_mut().enumerate().zip(&values_vec) {
let ty = self.ty_ref(store.0).0.results().nth(i).unwrap();
*slot = unsafe { Val::from_raw(&mut *store, *val, ty) };
}
values_vec.truncate(0);
store.0.save_wasm_val_raw_storage(values_vec);
Ok(())
}
#[inline]
pub(crate) fn vm_func_ref(&self, store: &mut StoreOpaque) -> NonNull<VMFuncRef> {
let func_data = &mut store.store_data_mut()[self.0];
if let Some(in_store) = func_data.in_store_func_ref {
in_store.as_non_null()
} else {
let func_ref = func_data.export().func_ref;
unsafe {
if func_ref.as_ref().wasm_call.is_none() {
let func_ref = store.func_refs().push(func_ref.as_ref().clone());
store.store_data_mut()[self.0].in_store_func_ref =
Some(SendSyncPtr::new(func_ref));
store.fill_func_refs();
func_ref
} else {
func_ref
}
}
}
}
pub(crate) unsafe fn from_wasmtime_function(
export: ExportFunction,
store: &mut StoreOpaque,
) -> Self {
Func::from_func_kind(FuncKind::StoreOwned { export }, store)
}
fn from_func_kind(kind: FuncKind, store: &mut StoreOpaque) -> Self {
Func(store.store_data_mut().insert(FuncData {
kind,
in_store_func_ref: None,
ty: None,
}))
}
pub(crate) fn vmimport(&self, store: &mut StoreOpaque, module: &Module) -> VMFunctionImport {
unsafe {
let f = {
let func_data = &mut store.store_data_mut()[self.0];
if let Some(func_ref) = func_data.in_store_func_ref {
func_ref.as_non_null()
} else {
func_data.export().func_ref
}
};
VMFunctionImport {
wasm_call: if let Some(wasm_call) = f.as_ref().wasm_call {
wasm_call
} else {
let _ = VMNativeCallHostFuncContext::from_opaque(f.as_ref().vmctx);
let sig = self.sig_index(store.store_data());
module.runtime_info().wasm_to_native_trampoline(sig).expect(
"must have a wasm-to-native trampoline for this signature if the Wasm \
module is importing a function of this signature",
)
},
native_call: f.as_ref().native_call,
array_call: f.as_ref().array_call,
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 [ValRaw],
func: &dyn Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()>,
) -> Result<()> {
let mut val_vec = caller.store.0.take_hostcall_val_storage();
debug_assert!(val_vec.is_empty());
let nparams = ty.params().len();
val_vec.reserve(nparams + ty.results().len());
for (i, ty) in ty.params().enumerate() {
val_vec.push(unsafe { Val::from_raw(&mut caller.store, values_vec[i], ty) })
}
val_vec.extend((0..ty.results().len()).map(|_| Val::null()));
let (params, results) = val_vec.split_at_mut(nparams);
func(caller.sub_caller(), params, results)?;
if ty.as_wasm_func_type().externref_returns_count()
> caller
.store
.0
.externref_activations_table()
.bump_capacity_remaining()
{
caller.store.gc();
}
for (i, (ret, ty)) in results.iter().zip(ty.results()).enumerate() {
if ret.ty() != ty {
bail!("function attempted to return an incompatible value");
}
if !ret.comes_from_same_store(caller.store.0) {
bail!("cross-`Store` values are not currently supported");
}
unsafe {
values_vec[i] = ret.to_raw(&mut caller.store);
}
}
val_vec.truncate(0);
caller.store.0.save_hostcall_val_storage(val_vec);
Ok(())
}
pub fn typed<Params, Results>(
&self,
store: impl AsContext,
) -> Result<TypedFunc<Params, Results>>
where
Params: WasmParams,
Results: WasmResults,
{
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)) }
}
#[allow(dead_code)] pub(crate) fn hash_key(&self, store: &mut StoreOpaque) -> impl std::hash::Hash + Eq {
self.vm_func_ref(store).as_ptr() as usize
}
}
pub(crate) fn invoke_wasm_and_catch_traps<T>(
store: &mut StoreContextMut<'_, T>,
closure: impl FnMut(*mut VMContext),
) -> Result<()> {
unsafe {
let exit = enter_wasm(store);
if let Err(trap) = store.0.call_hook(CallHook::CallingWasm) {
exit_wasm(store, exit);
return Err(trap);
}
let result = wasmtime_runtime::catch_traps(
store.0.signal_handler(),
store.0.engine().config().wasm_backtrace,
store.0.engine().config().coredump_on_trap,
store.0.default_caller(),
closure,
);
exit_wasm(store, exit);
store.0.call_hook(CallHook::ReturningFromWasm)?;
result.map_err(|t| crate::trap::from_runtime_box(store.0, t))
}
}
fn enter_wasm<T>(store: &mut StoreContextMut<'_, T>) -> Option<usize> {
if unsafe { *store.0.runtime_limits().stack_limit.get() } != usize::MAX
&& !store.0.async_support()
{
return None;
}
if cfg!(miri) {
return None;
}
let stack_pointer = wasmtime_runtime::get_stack_pointer();
let wasm_stack_limit = stack_pointer - store.engine().config().max_wasm_stack;
let prev_stack = unsafe {
mem::replace(
&mut *store.0.runtime_limits().stack_limit.get(),
wasm_stack_limit,
)
};
Some(prev_stack)
}
fn exit_wasm<T>(store: &mut StoreContextMut<'_, T>, prev_stack: Option<usize>) {
let prev_stack = match prev_stack {
Some(stack) => stack,
None => return,
};
unsafe {
*store.0.runtime_limits().stack_limit.get() = prev_stack;
}
}
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>;
#[doc(hidden)]
fn func_type(params: impl Iterator<Item = ValType>) -> FuncType;
#[doc(hidden)]
unsafe fn wrap_trampoline(ptr: *mut ValRaw, 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_error(error: Error) -> Self::Fallible;
}
unsafe impl<T> WasmRet for T
where
T: WasmTy,
{
type Abi = <T as WasmTy>::Abi;
type Retptr = ();
type Fallible = Result<T>;
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> {
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 ValRaw, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
T::abi_into_raw(f(()), ptr);
}
fn into_fallible(self) -> Result<T> {
Ok(self)
}
fn fallible_from_error(error: Error) -> Result<T> {
Err(error)
}
}
unsafe impl<T> WasmRet for Result<T>
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> {
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 ValRaw, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
T::wrap_trampoline(ptr, f)
}
fn into_fallible(self) -> Result<T> {
self
}
fn fallible_from_error(error: Error) -> Result<T> {
Err(error)
}
}
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>;
#[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> {
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,
IntoIterator::into_iter([$($t::valtype(),)*]),
)
}
#[allow(unused_assignments)]
unsafe fn wrap_trampoline(mut _ptr: *mut ValRaw, f: impl FnOnce(Self::Retptr) -> Self::Abi) {
let ($($t,)*) = <($($t::Abi,)*) as HostAbi>::call(f);
$(
$t::abi_into_raw($t, _ptr);
_ptr = _ptr.add(1);
)*
}
#[inline]
fn into_fallible(self) -> Result<Self> {
Ok(self)
}
#[inline]
fn fallible_from_error(error: Error) -> Result<Self> {
Err(error)
}
}
)
}
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)]
pub struct [<TupleRet $n>]<$($u,)*> {
$($u: $u,)*
}
#[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) -> HostContext;
}
pub struct Caller<'a, T> {
pub(crate) store: StoreContextMut<'a, T>,
caller: &'a wasmtime_runtime::Instance,
}
impl<T> Caller<'_, T> {
unsafe fn with<R>(caller: *mut VMContext, f: impl FnOnce(Caller<'_, T>) -> R) -> R {
assert!(!caller.is_null());
wasmtime_runtime::Instance::from_vmctx(caller, |instance| {
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> {
self.caller
.host_state()
.downcast_ref::<Instance>()?
.get_export(&mut self.store, name)
}
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 gc(&mut self) {
self.store.gc()
}
pub fn get_fuel(&self) -> Result<u64> {
self.store.get_fuel()
}
pub fn set_fuel(&mut self, fuel: u64) -> Result<()> {
self.store.set_fuel(fuel)
}
pub fn fuel_async_yield_interval(&mut self, interval: Option<u64>) -> Result<()> {
self.store.fuel_async_yield_interval(interval)
}
}
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()
}
}
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) -> HostContext {
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) -> HostContext {
unsafe extern "C" fn native_call_shim<T, F, $($args,)* R>(
vmctx: *mut VMOpaqueContext,
caller_vmctx: *mut VMOpaqueContext,
$( $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(anyhow::Error),
Panic(Box<dyn std::any::Any + Send>),
}
let caller_vmctx = VMContext::from_opaque(caller_vmctx);
let result = Caller::with(caller_vmctx, |mut caller| {
let vmctx = VMNativeCallHostFuncContext::from_opaque(vmctx);
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.call_hook(CallHook::CallingHost) {
return R::fallible_from_error(trap);
}
$(let $args = $args::from_abi($args, caller.store.0);)*
let r = func(
caller.sub_caller(),
$( $args, )*
);
if let Err(trap) = caller.store.0.call_hook(CallHook::ReturningFromHost) {
return R::fallible_from_error(trap);
}
r.into_fallible()
}))
};
match ret {
Err(panic) => CallResult::Panic(panic),
Ok(ret) => {
if !ret.compatible_with_store(caller.store.0) {
CallResult::Trap(anyhow::anyhow!("host function attempted to return cross-`Store` value to Wasm"))
} else {
match ret.into_abi_for_ret(caller.store.0, retptr) {
Ok(val) => CallResult::Ok(val),
Err(trap) => CallResult::Trap(trap.into()),
}
}
}
}
});
match result {
CallResult::Ok(val) => val,
CallResult::Trap(err) => crate::trap::raise(err),
CallResult::Panic(panic) => wasmtime_runtime::resume_panic(panic),
}
}
unsafe extern "C" fn array_call_trampoline<T, F, $($args,)* R>(
callee_vmctx: *mut VMOpaqueContext,
caller_vmctx: *mut VMOpaqueContext,
args: *mut ValRaw,
_args_len: usize
)
where
F: Fn(Caller<'_, T>, $( $args ),*) -> R + 'static,
$($args: WasmTy,)*
R: WasmRet,
{
let mut _n = 0;
$(
debug_assert!(_n < _args_len);
let $args = $args::abi_from_raw(args.add(_n));
_n += 1;
)*
R::wrap_trampoline(args, |retptr| {
native_call_shim::<T, F, $( $args, )* R>(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 array_call = array_call_trampoline::<T, F, $($args,)* R>;
let native_call = NonNull::new(native_call_shim::<T, F, $($args,)* R> as *mut _).unwrap();
let ctx = unsafe {
VMNativeCallHostFuncContext::new(
VMFuncRef {
native_call,
array_call,
wasm_call: None,
type_index: shared_signature_id,
vmctx: ptr::null_mut(),
},
Box::new(self),
)
};
ctx.into()
}
}
}
}
for_each_function_signature!(impl_into_func);
#[doc(hidden)]
pub enum HostContext {
Native(StoreBox<VMNativeCallHostFuncContext>),
Array(StoreBox<VMArrayCallHostFuncContext>),
}
impl From<StoreBox<VMNativeCallHostFuncContext>> for HostContext {
fn from(ctx: StoreBox<VMNativeCallHostFuncContext>) -> Self {
HostContext::Native(ctx)
}
}
impl From<StoreBox<VMArrayCallHostFuncContext>> for HostContext {
fn from(ctx: StoreBox<VMArrayCallHostFuncContext>) -> Self {
HostContext::Array(ctx)
}
}
pub(crate) struct HostFunc {
ctx: HostContext,
engine: Engine,
}
impl HostFunc {
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn new<T>(
engine: &Engine,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
) -> Self {
let ty_clone = ty.clone();
unsafe {
HostFunc::new_unchecked(engine, ty, move |caller, values| {
Func::invoke(caller, &ty_clone, values, &func)
})
}
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub unsafe fn new_unchecked<T>(
engine: &Engine,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
) -> Self {
let func = move |caller_vmctx, values: &mut [ValRaw]| {
Caller::<T>::with(caller_vmctx, |mut caller| {
caller.store.0.call_hook(CallHook::CallingHost)?;
let result = func(caller.sub_caller(), values)?;
caller.store.0.call_hook(CallHook::ReturningFromHost)?;
Ok(result)
})
};
let ctx = crate::trampoline::create_array_call_function(&ty, func, engine)
.expect("failed to create function");
HostFunc::_new(engine, ctx.into())
}
pub fn wrap<T, Params, Results>(
engine: &Engine,
func: impl IntoFunc<T, Params, Results>,
) -> Self {
let ctx = func.into_func(engine);
HostFunc::_new(engine, ctx)
}
fn _new(engine: &Engine, ctx: HostContext) -> Self {
HostFunc {
ctx,
engine: engine.clone(),
}
}
pub unsafe fn to_func(self: &Arc<Self>, store: &mut StoreOpaque) -> Func {
self.validate_store(store);
let me = self.clone();
Func::from_func_kind(FuncKind::SharedHost(me), store)
}
pub unsafe fn to_func_store_rooted(
self: &Arc<Self>,
store: &mut StoreOpaque,
rooted_func_ref: Option<NonNull<VMFuncRef>>,
) -> Func {
self.validate_store(store);
if rooted_func_ref.is_some() {
debug_assert!(self.func_ref().wasm_call.is_none());
debug_assert!(matches!(self.ctx, HostContext::Native(_)));
}
Func::from_func_kind(
FuncKind::RootedHost(RootedHostFunc::new(self, rooted_func_ref)),
store,
)
}
unsafe fn into_func(self, store: &mut StoreOpaque) -> Func {
self.validate_store(store);
Func::from_func_kind(FuncKind::Host(Box::new(self)), store)
}
fn validate_store(&self, store: &mut StoreOpaque) {
assert!(
Engine::same(&self.engine, store.engine()),
"cannot use a store with a different engine than a linker was created with",
);
}
pub(crate) fn sig_index(&self) -> VMSharedSignatureIndex {
self.func_ref().type_index
}
pub(crate) fn func_ref(&self) -> &VMFuncRef {
match &self.ctx {
HostContext::Native(ctx) => unsafe { (*ctx.get()).func_ref() },
HostContext::Array(ctx) => unsafe { (*ctx.get()).func_ref() },
}
}
pub(crate) fn host_ctx(&self) -> &HostContext {
&self.ctx
}
fn export_func(&self) -> ExportFunction {
ExportFunction {
func_ref: NonNull::from(self.func_ref()),
}
}
}
impl Drop for HostFunc {
fn drop(&mut self) {
unsafe {
self.engine.signatures().unregister(self.sig_index());
}
}
}
impl FuncData {
#[inline]
fn export(&self) -> ExportFunction {
self.kind.export()
}
pub(crate) fn sig_index(&self) -> VMSharedSignatureIndex {
unsafe { self.export().func_ref.as_ref().type_index }
}
}
impl FuncKind {
#[inline]
fn export(&self) -> ExportFunction {
match self {
FuncKind::StoreOwned { export, .. } => *export,
FuncKind::SharedHost(host) => host.export_func(),
FuncKind::RootedHost(rooted) => ExportFunction {
func_ref: NonNull::from(rooted.func_ref()),
},
FuncKind::Host(host) => host.export_func(),
}
}
}
use self::rooted::*;
mod rooted {
use wasmtime_runtime::{SendSyncPtr, VMFuncRef};
use super::HostFunc;
use std::ptr::NonNull;
use std::sync::Arc;
pub(crate) struct RootedHostFunc {
func: SendSyncPtr<HostFunc>,
func_ref: Option<SendSyncPtr<VMFuncRef>>,
}
impl RootedHostFunc {
pub(crate) unsafe fn new(
func: &Arc<HostFunc>,
func_ref: Option<NonNull<VMFuncRef>>,
) -> RootedHostFunc {
RootedHostFunc {
func: NonNull::from(&**func).into(),
func_ref: func_ref.map(|p| p.into()),
}
}
pub(crate) fn func(&self) -> &HostFunc {
unsafe { self.func.as_ref() }
}
pub(crate) fn func_ref(&self) -> &VMFuncRef {
if let Some(f) = self.func_ref {
unsafe { f.as_ref() }
} else {
self.func().func_ref()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Instance, Module, Store};
#[test]
fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
let mut store = Store::<()>::default();
let module = Module::new(
store.engine(),
r#"
(module
(func (export "f")
nop
)
)
"#,
)?;
let instance = Instance::new(&mut store, &module, &[])?;
let f1 = instance.get_func(&mut store, "f").unwrap();
let f2 = instance.get_func(&mut store, "f").unwrap();
assert!(
f1.hash_key(&mut store.as_context_mut().0)
== f2.hash_key(&mut store.as_context_mut().0)
);
let instance2 = Instance::new(&mut store, &module, &[])?;
let f3 = instance2.get_func(&mut store, "f").unwrap();
assert!(
f1.hash_key(&mut store.as_context_mut().0)
!= f3.hash_key(&mut store.as_context_mut().0)
);
Ok(())
}
}