use crate::prelude::*;
use crate::runtime::vm::{
ExportFunction, SendSyncPtr, StoreBox, VMArrayCallHostFuncContext, VMContext, VMFuncRef,
VMFunctionImport, VMOpaqueContext,
};
use crate::runtime::Uninhabited;
use crate::store::{AutoAssertNoGc, StoreData, StoreOpaque, Stored};
use crate::type_registry::RegisteredType;
use crate::{
AsContext, AsContextMut, CallHook, Engine, Extern, FuncType, Instance, Module, Ref,
StoreContext, StoreContextMut, Val, ValRaw, ValType,
};
use alloc::sync::Arc;
use anyhow::{bail, Context as _, Error, Result};
use core::ffi::c_void;
use core::future::Future;
use core::mem::{self, MaybeUninit};
use core::num::NonZeroUsize;
use core::pin::Pin;
use core::ptr::{self, NonNull};
use wasmtime_environ::VMSharedTypeIndex;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct NoFunc {
_inner: Uninhabited,
}
impl NoFunc {
#[inline]
pub fn null() -> Option<NoFunc> {
None
}
#[inline]
pub fn null_ref() -> Ref {
Ref::Func(None)
}
#[inline]
pub fn null_val() -> Val {
Val::FuncRef(None)
}
}
#[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::*;
impl Func {
pub fn new<T>(
store: impl AsContextMut<Data = T>,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
) -> Self {
assert!(ty.comes_from_same_engine(store.as_context().engine()));
let ty_clone = ty.clone();
unsafe {
Func::new_unchecked(store, ty, move |caller, values| {
Func::invoke_host_func_for_wasm(caller, &ty_clone, values, &func)
})
}
}
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 {
assert!(ty.comes_from_same_engine(store.as_context().engine()));
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"))]
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"
);
assert!(ty.comes_from_same_engine(store.as_context().engine()));
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_vm_func_ref(
store: &mut StoreOpaque,
raw: *mut VMFuncRef,
) -> Option<Func> {
let func_ref = NonNull::new(raw)?;
debug_assert!(func_ref.as_ref().type_index != VMSharedTypeIndex::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)
}
}
fn wrap_inner<F, T, Params, Results>(mut store: impl AsContextMut<Data = T>, func: F) -> Func
where
F: Fn(Caller<'_, T>, Params) -> Results + Send + Sync + 'static,
Params: WasmTyList,
Results: WasmRet,
{
let store = store.as_context_mut().0;
unsafe {
let host = HostFunc::wrap_inner(store.engine(), func);
host.into_func(store)
}
}
#[cfg(feature = "async")]
pub fn wrap_async<T, F, P, R>(store: impl AsContextMut<Data = T>, func: F) -> Func
where
F: for<'a> Fn(Caller<'a, T>, P) -> Box<dyn Future<Output = R> + Send + 'a>
+ Send
+ Sync
+ 'static,
P: WasmTyList,
R: WasmRet,
{
assert!(
store.as_context().async_support(),
concat!("cannot use `wrap_async` without enabling async support on the config")
);
Func::wrap_inner(store, move |mut caller: Caller<'_, T>, 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),
}
})
}
pub fn ty(&self, store: impl AsContext) -> FuncType {
self.load_ty(&store.as_context().0)
}
pub(crate) fn load_ty(&self, store: &StoreOpaque) -> FuncType {
assert!(self.comes_from_same_store(store));
FuncType::from_shared_type_index(store.engine(), self.type_index(store.store_data()))
}
pub fn matches_ty(&self, store: impl AsContext, func_ty: &FuncType) -> bool {
self._matches_ty(store.as_context().0, func_ty)
}
pub(crate) fn _matches_ty(&self, store: &StoreOpaque, func_ty: &FuncType) -> bool {
let actual_ty = self.load_ty(store);
actual_ty.matches(func_ty)
}
pub(crate) fn ensure_matches_ty(&self, store: &StoreOpaque, func_ty: &FuncType) -> Result<()> {
if !self.comes_from_same_store(store) {
bail!("function used with wrong store");
}
if self._matches_ty(store, func_ty) {
Ok(())
} else {
let actual_ty = self.load_ty(store);
bail!("type mismatch: expected {func_ty}, found {actual_ty}")
}
}
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 type_index(&self, data: &StoreData) -> VMSharedTypeIndex {
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",
);
let mut store = store.as_context_mut();
let need_gc = self.call_impl_check_args(&mut store, params, results)?;
if need_gc {
store.0.gc();
}
unsafe { self.call_impl_do_call(&mut store, 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> {
Self::_from_raw(store.as_context_mut().0, raw)
}
pub(crate) unsafe fn _from_raw(store: &mut StoreOpaque, raw: *mut c_void) -> Option<Func> {
Func::from_vm_func_ref(store, 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")]
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 need_gc = self.call_impl_check_args(&mut store, params, results)?;
if need_gc {
store.0.gc_async().await;
}
let result = store
.on_fiber(|store| unsafe { self.call_impl_do_call(store, params, results) })
.await??;
Ok(result)
}
fn call_impl_check_args<T>(
&self,
store: &mut StoreContextMut<'_, T>,
params: &[Val],
results: &mut [Val],
) -> Result<bool> {
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) {
arg.ensure_matches_ty(opaque, &ty)
.context("argument type mismatch")?;
if !arg.comes_from_same_store(opaque) {
bail!("cross-`Store` values are not currently supported");
}
}
#[cfg(feature = "gc")]
{
let num_gc_refs = ty.as_wasm_func_type().non_i31_gc_ref_params_count();
if let Some(num_gc_refs) = NonZeroUsize::new(num_gc_refs) {
return Ok(opaque
.gc_store()?
.gc_heap
.need_gc_before_entering_wasm(num_gc_refs));
}
}
Ok(false)
}
unsafe fn call_impl_do_call<T>(
&self,
store: &mut StoreContextMut<'_, T>,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let (ty, _) = self.ty_ref(store.0);
let values_vec_size = params.len().max(ty.results().len());
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::v128(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];
let func_ref = func_data.export().func_ref;
if unsafe { func_ref.as_ref().wasm_call.is_some() } {
return func_ref;
}
if let Some(in_store) = func_data.in_store_func_ref {
in_store.as_non_null()
} else {
unsafe {
self.copy_func_ref_into_store_and_fill(store, func_ref)
}
}
}
unsafe fn copy_func_ref_into_store_and_fill(
&self,
store: &mut StoreOpaque,
func_ref: NonNull<VMFuncRef>,
) -> NonNull<VMFuncRef> {
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
}
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 _ = VMArrayCallHostFuncContext::from_opaque(f.as_ref().vmctx);
let sig = self.type_index(store.store_data());
module.runtime_info().wasm_to_array_trampoline(sig).expect(
"if the wasm is importing a function of a given type, it must have the \
type's trampoline",
)
},
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_host_func_for_wasm<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_func_ref()));
let (params, results) = val_vec.split_at_mut(nparams);
func(caller.sub_caller(), params, results)?;
for (i, (ret, ty)) in results.iter().zip(ty.results()).enumerate() {
ret.ensure_matches_ty(caller.store.0, &ty)
.context("function attempted to return an incompatible value")?;
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 store = store.as_context().0;
let ty = self.load_ty(store);
Params::typecheck(store.engine(), ty.params(), TypeCheckPosition::Param)
.context("type mismatch with parameters")?;
Results::typecheck(store.engine(), ty.results(), TypeCheckPosition::Result)
.context("type mismatch with results")?;
unsafe { Ok(TypedFunc::_new_unchecked(store, *self)) }
}
#[allow(dead_code)] pub(crate) fn hash_key(&self, store: &mut StoreOpaque) -> impl core::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 = crate::runtime::vm::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 = crate::runtime::vm::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)]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool;
#[doc(hidden)]
unsafe fn store(
self,
store: &mut AutoAssertNoGc<'_>,
ptr: &mut [MaybeUninit<ValRaw>],
) -> Result<()>;
#[doc(hidden)]
fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType;
#[doc(hidden)]
type Fallible: WasmRet;
#[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 Fallible = Result<T>;
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
<Self as WasmTy>::compatible_with_store(self, store)
}
unsafe fn store(
self,
store: &mut AutoAssertNoGc<'_>,
ptr: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
debug_assert!(ptr.len() > 0);
<Self as WasmTy>::store(self, store, ptr.get_unchecked_mut(0))
}
fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
FuncType::new(engine, params, Some(<Self as WasmTy>::valtype()))
}
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 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 store(
self,
store: &mut AutoAssertNoGc<'_>,
ptr: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
self.and_then(|val| val.store(store, ptr))
}
fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
T::func_type(engine, params)
}
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,)*
{
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 store(
self,
_store: &mut AutoAssertNoGc<'_>,
_ptr: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
let ($($t,)*) = self;
let mut _cur = 0;
$(
debug_assert!(_cur < _ptr.len());
let val = _ptr.get_unchecked_mut(_cur);
_cur += 1;
WasmTy::store($t, _store, val)?;
)*
Ok(())
}
fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
FuncType::new(
engine,
params,
IntoIterator::into_iter([$($t::valtype(),)*]),
)
}
#[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);
pub trait IntoFunc<T, Params, Results>: Send + Sync + 'static {
#[doc(hidden)]
fn into_func(self, engine: &Engine) -> HostContext;
}
macro_rules! impl_into_func {
($num:tt $arg:ident) => {
#[allow(non_snake_case)]
impl<T, F, $arg, R> IntoFunc<T, $arg, R> for F
where
F: Fn($arg) -> R + Send + Sync + 'static,
$arg: WasmTy,
R: WasmRet,
{
fn into_func(self, engine: &Engine) -> HostContext {
let f = move |_: Caller<'_, T>, $arg: $arg| {
self($arg)
};
f.into_func(engine)
}
}
#[allow(non_snake_case)]
impl<T, F, $arg, R> IntoFunc<T, (Caller<'_, T>, $arg), R> for F
where
F: Fn(Caller<'_, T>, $arg) -> R + Send + Sync + 'static,
$arg: WasmTy,
R: WasmRet,
{
fn into_func(self, engine: &Engine) -> HostContext {
HostContext::from_closure(engine, move |caller: Caller<'_, T>, ($arg,)| {
self(caller, $arg)
})
}
}
};
($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 {
HostContext::from_closure(engine, move |caller: Caller<'_, T>, ( $( $args ),* )| {
self(caller, $( $args ),* )
})
}
}
}
}
for_each_function_signature!(impl_into_func);
pub unsafe trait WasmTyList {
fn valtypes() -> impl Iterator<Item = ValType>;
#[doc(hidden)]
unsafe fn load(store: &mut AutoAssertNoGc<'_>, values: &mut [MaybeUninit<ValRaw>]) -> Self;
}
macro_rules! impl_wasm_ty_list {
($num:tt $($args:ident)*) => (paste::paste!{
#[allow(non_snake_case)]
unsafe impl<$($args),*> WasmTyList for ($($args,)*)
where
$($args: WasmTy,)*
{
fn valtypes() -> impl Iterator<Item = ValType> {
IntoIterator::into_iter([$($args::valtype(),)*])
}
unsafe fn load(_store: &mut AutoAssertNoGc<'_>, _values: &mut [MaybeUninit<ValRaw>]) -> Self {
let mut _cur = 0;
($({
debug_assert!(_cur < _values.len());
let ptr = _values.get_unchecked(_cur).assume_init_ref();
_cur += 1;
$args::load(_store, ptr)
},)*)
}
}
});
}
for_each_function_signature!(impl_wasm_ty_list);
pub struct Caller<'a, T> {
pub(crate) store: StoreContextMut<'a, T>,
caller: &'a crate::runtime::vm::Instance,
}
impl<T> Caller<'_, T> {
unsafe fn with<F, R>(caller: *mut VMContext, f: F) -> R
where
F: for<'a> FnOnce(Caller<'a, T>) -> R,
R: 'static,
{
assert!(!caller.is_null());
crate::runtime::vm::Instance::from_vmctx(caller, |instance| {
let store = StoreContextMut::from_raw(instance.store());
let gc_lifo_scope = store.0.gc_roots().enter_lifo_scope();
let ret = f(Caller {
store,
caller: &instance,
});
let store = StoreContextMut::<T>::from_raw(instance.store());
store.0.exit_gc_lifo_scope(gc_lifo_scope);
ret
})
}
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()
}
#[cfg(feature = "gc")]
pub fn gc(&mut self) {
self.store.gc()
}
#[cfg(all(feature = "async", feature = "gc"))]
pub async fn gc_async(&mut self)
where
T: Send,
{
self.store.gc_async().await;
}
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()
}
}
struct HostFuncState<F> {
func: F,
#[allow(dead_code)]
ty: RegisteredType,
}
#[doc(hidden)]
pub enum HostContext {
Array(StoreBox<VMArrayCallHostFuncContext>),
}
impl From<StoreBox<VMArrayCallHostFuncContext>> for HostContext {
fn from(ctx: StoreBox<VMArrayCallHostFuncContext>) -> Self {
HostContext::Array(ctx)
}
}
impl HostContext {
fn from_closure<F, T, P, R>(engine: &Engine, func: F) -> Self
where
F: Fn(Caller<'_, T>, P) -> R + Send + Sync + 'static,
P: WasmTyList,
R: WasmRet,
{
let ty = R::func_type(engine, None::<ValType>.into_iter().chain(P::valtypes()));
let type_index = ty.type_index();
let array_call = Self::array_call_trampoline::<T, F, P, R>;
let ctx = unsafe {
VMArrayCallHostFuncContext::new(
VMFuncRef {
array_call,
wasm_call: None,
type_index,
vmctx: ptr::null_mut(),
},
Box::new(HostFuncState {
func,
ty: ty.into_registered_type(),
}),
)
};
ctx.into()
}
unsafe extern "C" fn array_call_trampoline<T, F, P, R>(
callee_vmctx: *mut VMOpaqueContext,
caller_vmctx: *mut VMOpaqueContext,
args: *mut ValRaw,
args_len: usize,
) where
F: Fn(Caller<'_, T>, P) -> R + 'static,
P: WasmTyList,
R: WasmRet,
{
let run = move |mut caller: Caller<'_, T>| {
let args =
core::slice::from_raw_parts_mut(args.cast::<MaybeUninit<ValRaw>>(), args_len);
let vmctx = VMArrayCallHostFuncContext::from_opaque(callee_vmctx);
let state = (*vmctx).host_state();
debug_assert!(state.is::<HostFuncState<F>>());
let state = &*(state as *const _ as *const HostFuncState<F>);
let func = &state.func;
let ret = 'ret: {
if let Err(trap) = caller.store.0.call_hook(CallHook::CallingHost) {
break 'ret R::fallible_from_error(trap);
}
let mut store = AutoAssertNoGc::new(caller.store.0);
let params = P::load(&mut store, args);
let _ = &mut store;
drop(store);
let r = func(caller.sub_caller(), params);
if let Err(trap) = caller.store.0.call_hook(CallHook::ReturningFromHost) {
break 'ret R::fallible_from_error(trap);
}
r.into_fallible()
};
if !ret.compatible_with_store(caller.store.0) {
bail!("host function attempted to return cross-`Store` value to Wasm")
} else {
let mut store = AutoAssertNoGc::new(&mut **caller.store.0);
let ret = ret.store(&mut store, args)?;
Ok(ret)
}
};
let result = crate::runtime::vm::catch_unwind_and_longjmp(move || {
let caller_vmctx = VMContext::from_opaque(caller_vmctx);
Caller::with(caller_vmctx, run)
});
match result {
Ok(val) => val,
Err(err) => crate::trap::raise(err),
}
}
}
pub(crate) struct HostFunc {
ctx: HostContext,
engine: Engine,
}
impl HostFunc {
pub fn new<T>(
engine: &Engine,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
) -> Self {
assert!(ty.comes_from_same_engine(engine));
let ty_clone = ty.clone();
unsafe {
HostFunc::new_unchecked(engine, ty, move |caller, values| {
Func::invoke_host_func_for_wasm(caller, &ty_clone, values, &func)
})
}
}
pub unsafe fn new_unchecked<T>(
engine: &Engine,
ty: FuncType,
func: impl Fn(Caller<'_, T>, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
) -> Self {
assert!(ty.comes_from_same_engine(engine));
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)
.expect("failed to create function");
HostFunc::_new(engine, ctx.into())
}
pub fn wrap_inner<F, T, Params, Results>(engine: &Engine, func: F) -> Self
where
F: Fn(Caller<'_, T>, Params) -> Results + Send + Sync + 'static,
Params: WasmTyList,
Results: WasmRet,
{
let ctx = HostContext::from_closure(engine, func);
HostFunc::_new(engine, ctx)
}
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::Array(_)));
}
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) -> VMSharedTypeIndex {
self.func_ref().type_index
}
pub(crate) fn func_ref(&self) -> &VMFuncRef {
match &self.ctx {
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 FuncData {
#[inline]
fn export(&self) -> ExportFunction {
self.kind.export()
}
pub(crate) fn sig_index(&self) -> VMSharedTypeIndex {
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 super::HostFunc;
use crate::runtime::vm::{SendSyncPtr, VMFuncRef};
use alloc::sync::Arc;
use core::ptr::NonNull;
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::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(())
}
}