use super::{invoke_wasm_and_catch_traps, HostAbi};
use crate::store::{AutoAssertNoGc, StoreOpaque};
use crate::{AsContextMut, ExternRef, Func, FuncType, StoreContextMut, ValRaw, ValType};
use anyhow::{bail, Result};
use std::marker;
use std::mem::{self, MaybeUninit};
use std::ptr;
use wasmtime_runtime::{
VMCallerCheckedFuncRef, VMContext, VMFunctionBody, VMOpaqueContext, VMSharedSignatureIndex,
};
#[repr(transparent)] pub struct TypedFunc<Params, Results> {
_a: marker::PhantomData<fn(Params) -> Results>,
func: Func,
}
impl<Params, Results> Copy for TypedFunc<Params, Results> {}
impl<Params, Results> Clone for TypedFunc<Params, Results> {
fn clone(&self) -> TypedFunc<Params, Results> {
*self
}
}
impl<Params, Results> TypedFunc<Params, Results>
where
Params: WasmParams,
Results: WasmResults,
{
pub unsafe fn new_unchecked(func: Func) -> TypedFunc<Params, Results> {
TypedFunc {
_a: marker::PhantomData,
func,
}
}
pub fn func(&self) -> &Func {
&self.func
}
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results> {
let mut store = store.as_context_mut();
assert!(
!store.0.async_support(),
"must use `call_async` with async stores"
);
let func = self.func.caller_checked_anyfunc(store.0);
unsafe { Self::call_raw(&mut store, func, params) }
}
#[cfg(feature = "async")]
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
pub async fn call_async<T>(
&self,
mut store: impl AsContextMut<Data = T>,
params: Params,
) -> Result<Results>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"must use `call` with non-async stores"
);
store
.on_fiber(|store| {
let func = self.func.caller_checked_anyfunc(store.0);
unsafe { Self::call_raw(store, func, params) }
})
.await?
}
pub(crate) unsafe fn call_raw<T>(
store: &mut StoreContextMut<'_, T>,
func: ptr::NonNull<VMCallerCheckedFuncRef>,
params: Params,
) -> Result<Results> {
if cfg!(debug_assertions) {
Self::debug_typecheck(store.0, func.as_ref().type_index);
}
if params.externrefs_count()
> store
.0
.externref_activations_table()
.bump_capacity_remaining()
{
store.gc();
}
let params = {
let mut store = AutoAssertNoGc::new(&mut **store.as_context_mut().0);
match params.into_abi(&mut store) {
Some(abi) => abi,
None => {
bail!("attempt to pass cross-`Store` value to Wasm as function argument")
}
}
};
let mut captures = (func, MaybeUninit::uninit(), params, false);
let result = invoke_wasm_and_catch_traps(store, |caller| {
let (anyfunc, ret, params, returned) = &mut captures;
let anyfunc = anyfunc.as_ref();
let result = Params::invoke::<Results>(
anyfunc.func_ptr.as_ptr(),
anyfunc.vmctx,
caller,
*params,
);
ptr::write(ret.as_mut_ptr(), result);
*returned = true
});
let (_, ret, _, returned) = captures;
debug_assert_eq!(result.is_ok(), returned);
result?;
Ok(Results::from_abi(store.0, ret.assume_init()))
}
fn debug_typecheck(store: &StoreOpaque, func: VMSharedSignatureIndex) {
let ty = FuncType::from_wasm_func_type(
store
.engine()
.signatures()
.lookup_type(func)
.expect("signature should be registered"),
);
Params::typecheck(ty.params()).expect("params should match");
Results::typecheck(ty.results()).expect("results should match");
}
}
pub unsafe trait WasmTy: Send {
#[doc(hidden)]
type Abi: Copy;
#[doc(hidden)]
#[inline]
fn typecheck(ty: crate::ValType) -> Result<()> {
if ty == Self::valtype() {
Ok(())
} else {
bail!("expected {} found {}", Self::valtype(), ty)
}
}
#[doc(hidden)]
fn valtype() -> ValType;
#[doc(hidden)]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool;
#[doc(hidden)]
fn is_externref(&self) -> bool;
#[doc(hidden)]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi;
#[doc(hidden)]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw);
#[doc(hidden)]
fn into_abi(self, store: &mut StoreOpaque) -> Self::Abi;
#[doc(hidden)]
unsafe fn from_abi(abi: Self::Abi, store: &mut StoreOpaque) -> Self;
}
macro_rules! integers {
($($primitive:ident/$get_primitive:ident => $ty:ident in $raw:ident)*) => ($(
unsafe impl WasmTy for $primitive {
type Abi = $primitive;
#[inline]
fn valtype() -> ValType {
ValType::$ty
}
#[inline]
fn compatible_with_store(&self, _: &StoreOpaque) -> bool {
true
}
#[inline]
fn is_externref(&self) -> bool {
false
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> $primitive {
(*raw).$get_primitive()
}
#[inline]
unsafe fn abi_into_raw(abi: $primitive, raw: *mut ValRaw) {
*raw = ValRaw::$primitive(abi);
}
#[inline]
fn into_abi(self, _store: &mut StoreOpaque) -> Self::Abi {
self
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &mut StoreOpaque) -> Self {
abi
}
}
)*)
}
integers! {
i32/get_i32 => I32 in i32
i64/get_i64 => I64 in i64
u32/get_u32 => I32 in i32
u64/get_u64 => I64 in i64
}
macro_rules! floats {
($($float:ident/$int:ident/$get_float:ident => $ty:ident)*) => ($(
unsafe impl WasmTy for $float {
type Abi = $float;
#[inline]
fn valtype() -> ValType {
ValType::$ty
}
#[inline]
fn compatible_with_store(&self, _: &StoreOpaque) -> bool {
true
}
#[inline]
fn is_externref(&self) -> bool {
false
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> $float {
$float::from_bits((*raw).$get_float())
}
#[inline]
unsafe fn abi_into_raw(abi: $float, raw: *mut ValRaw) {
*raw = ValRaw::$float(abi.to_bits());
}
#[inline]
fn into_abi(self, _store: &mut StoreOpaque) -> Self::Abi {
self
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &mut StoreOpaque) -> Self {
abi
}
}
)*)
}
floats! {
f32/u32/get_f32 => F32
f64/u64/get_f64 => F64
}
unsafe impl WasmTy for Option<ExternRef> {
type Abi = *mut u8;
#[inline]
fn valtype() -> ValType {
ValType::ExternRef
}
#[inline]
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
true
}
#[inline]
fn is_externref(&self) -> bool {
true
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> *mut u8 {
(*raw).get_externref() as *mut u8
}
#[inline]
unsafe fn abi_into_raw(abi: *mut u8, raw: *mut ValRaw) {
*raw = ValRaw::externref(abi as usize);
}
#[inline]
fn into_abi(self, store: &mut StoreOpaque) -> Self::Abi {
if let Some(x) = self {
let abi = x.inner.as_raw();
unsafe {
let mut store = AutoAssertNoGc::new(store);
store.insert_vmexternref_without_gc(x.inner);
}
abi
} else {
ptr::null_mut()
}
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &mut StoreOpaque) -> Self {
if abi.is_null() {
None
} else {
Some(ExternRef {
inner: wasmtime_runtime::VMExternRef::clone_from_raw(abi),
})
}
}
}
unsafe impl WasmTy for Option<Func> {
type Abi = *mut wasmtime_runtime::VMCallerCheckedFuncRef;
#[inline]
fn valtype() -> ValType {
ValType::FuncRef
}
#[inline]
fn compatible_with_store<'a>(&self, store: &StoreOpaque) -> bool {
if let Some(f) = self {
store.store_data().contains(f.0)
} else {
true
}
}
#[inline]
fn is_externref(&self) -> bool {
false
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
(*raw).get_funcref() as Self::Abi
}
#[inline]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
*raw = ValRaw::funcref(abi as usize);
}
#[inline]
fn into_abi(self, store: &mut StoreOpaque) -> Self::Abi {
if let Some(f) = self {
f.caller_checked_anyfunc(store).as_ptr()
} else {
ptr::null_mut()
}
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut StoreOpaque) -> Self {
Func::from_caller_checked_anyfunc(store, abi)
}
}
pub unsafe trait WasmParams: Send {
#[doc(hidden)]
type Abi: Copy;
#[doc(hidden)]
fn typecheck(params: impl ExactSizeIterator<Item = crate::ValType>) -> Result<()>;
#[doc(hidden)]
fn externrefs_count(&self) -> usize;
#[doc(hidden)]
fn into_abi(self, store: &mut StoreOpaque) -> Option<Self::Abi>;
#[doc(hidden)]
unsafe fn invoke<R: WasmResults>(
func: *const VMFunctionBody,
vmctx1: *mut VMOpaqueContext,
vmctx2: *mut VMContext,
abi: Self::Abi,
) -> R::ResultAbi;
}
unsafe impl<T> WasmParams for T
where
T: WasmTy,
{
type Abi = <(T,) as WasmParams>::Abi;
fn typecheck(params: impl ExactSizeIterator<Item = crate::ValType>) -> Result<()> {
<(T,) as WasmParams>::typecheck(params)
}
#[inline]
fn externrefs_count(&self) -> usize {
T::is_externref(self) as usize
}
#[inline]
fn into_abi(self, store: &mut StoreOpaque) -> Option<Self::Abi> {
<(T,) as WasmParams>::into_abi((self,), store)
}
unsafe fn invoke<R: WasmResults>(
func: *const VMFunctionBody,
vmctx1: *mut VMOpaqueContext,
vmctx2: *mut VMContext,
abi: Self::Abi,
) -> R::ResultAbi {
<(T,) as WasmParams>::invoke::<R>(func, vmctx1, vmctx2, abi)
}
}
macro_rules! impl_wasm_params {
($n:tt $($t:ident)*) => {
#[allow(non_snake_case)]
unsafe impl<$($t: WasmTy,)*> WasmParams for ($($t,)*) {
type Abi = ($($t::Abi,)*);
fn typecheck(mut params: impl ExactSizeIterator<Item = crate::ValType>) -> Result<()> {
let mut _n = 0;
$(
match params.next() {
Some(t) => {
_n += 1;
$t::typecheck(t)?
},
None => bail!("expected {} types, found {}", $n, params.len() + _n),
}
)*
match params.next() {
None => Ok(()),
Some(_) => {
_n += 1;
bail!("expected {} types, found {}", $n, params.len() + _n)
},
}
}
#[inline]
fn externrefs_count(&self) -> usize {
let ($(ref $t,)*) = self;
0 $(
+ $t.is_externref() as usize
)*
}
#[inline]
fn into_abi(self, _store: &mut StoreOpaque) -> Option<Self::Abi> {
let ($($t,)*) = self;
$(
let $t = if $t.compatible_with_store(_store) {
$t.into_abi(_store)
} else {
return None;
};
)*
Some(($($t,)*))
}
unsafe fn invoke<R: WasmResults>(
func: *const VMFunctionBody,
vmctx1: *mut VMOpaqueContext,
vmctx2: *mut VMContext,
abi: Self::Abi,
) -> R::ResultAbi {
let fnptr = mem::transmute::<
*const VMFunctionBody,
unsafe extern "C" fn(
*mut VMOpaqueContext,
*mut VMContext,
$($t::Abi,)*
<R::ResultAbi as HostAbi>::Retptr,
) -> <R::ResultAbi as HostAbi>::Abi,
>(func);
let ($($t,)*) = abi;
<R::ResultAbi as HostAbi>::call(|retptr| {
let fnptr = wasmtime_runtime::prepare_host_to_wasm_trampoline(vmctx2, fnptr);
fnptr(vmctx1, vmctx2, $($t,)* retptr)
})
}
}
};
}
for_each_function_signature!(impl_wasm_params);
pub unsafe trait WasmResults: WasmParams {
#[doc(hidden)]
type ResultAbi: HostAbi;
#[doc(hidden)]
unsafe fn from_abi(store: &mut StoreOpaque, abi: Self::ResultAbi) -> Self;
}
unsafe impl<T: WasmTy> WasmResults for T
where
(T::Abi,): HostAbi,
{
type ResultAbi = <(T,) as WasmResults>::ResultAbi;
unsafe fn from_abi(store: &mut StoreOpaque, abi: Self::ResultAbi) -> Self {
<(T,) as WasmResults>::from_abi(store, abi).0
}
}
macro_rules! impl_wasm_results {
($n:tt $($t:ident)*) => {
#[allow(non_snake_case, unused_variables)]
unsafe impl<$($t: WasmTy,)*> WasmResults for ($($t,)*)
where ($($t::Abi,)*): HostAbi
{
type ResultAbi = ($($t::Abi,)*);
#[inline]
unsafe fn from_abi(store: &mut StoreOpaque, abi: Self::ResultAbi) -> Self {
let ($($t,)*) = abi;
($($t::from_abi($t, store),)*)
}
}
};
}
for_each_function_signature!(impl_wasm_results);