use super::{invoke_wasm_and_catch_traps, HostAbi};
use crate::store::{AutoAssertNoGc, StoreOpaque};
use crate::{
AsContext, AsContextMut, Engine, Func, FuncType, HeapType, NoFunc, RefType, StoreContextMut,
ValRaw, ValType,
};
use anyhow::{bail, Context, Result};
use std::marker;
use std::mem::{self, MaybeUninit};
use std::num::NonZeroUsize;
use std::os::raw::c_void;
use std::ptr::{self, NonNull};
use wasmtime_runtime::{
VMContext, VMFuncRef, VMNativeCallFunction, VMOpaqueContext, VMSharedTypeIndex,
};
pub struct TypedFunc<Params, Results> {
_a: marker::PhantomData<fn(Params) -> Results>,
ty: FuncType,
func: Func,
}
impl<Params, Results> Clone for TypedFunc<Params, Results> {
fn clone(&self) -> TypedFunc<Params, Results> {
Self {
_a: marker::PhantomData,
ty: self.ty.clone(),
func: self.func,
}
}
}
impl<Params, Results> TypedFunc<Params, Results>
where
Params: WasmParams,
Results: WasmResults,
{
pub unsafe fn new_unchecked(store: impl AsContext, func: Func) -> TypedFunc<Params, Results> {
let store = store.as_context().0;
Self::_new_unchecked(store, func)
}
pub(crate) unsafe fn _new_unchecked(
store: &StoreOpaque,
func: Func,
) -> TypedFunc<Params, Results> {
let ty = func.load_ty(store);
TypedFunc {
_a: marker::PhantomData,
ty,
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"
);
if Self::need_gc_before_call_raw(store.0, ¶ms) {
store.0.gc();
}
let func = self.func.vm_func_ref(store.0);
unsafe { Self::call_raw(&mut store, &self.ty, func, params) }
}
#[cfg(feature = "async")]
#[cfg_attr(docsrs, 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"
);
if Self::need_gc_before_call_raw(store.0, ¶ms) {
store.0.gc_async().await;
}
store
.on_fiber(|store| {
let func = self.func.vm_func_ref(store.0);
unsafe { Self::call_raw(store, &self.ty, func, params) }
})
.await?
}
#[inline]
pub(crate) fn need_gc_before_call_raw(_store: &StoreOpaque, _params: &Params) -> bool {
#[cfg(feature = "gc")]
{
let num_gc_refs = _params.non_i31_gc_refs_count();
if let Some(num_gc_refs) = NonZeroUsize::new(num_gc_refs) {
return _store
.unwrap_gc_store()
.gc_heap
.need_gc_before_entering_wasm(num_gc_refs);
}
}
false
}
pub(crate) unsafe fn call_raw<T>(
store: &mut StoreContextMut<'_, T>,
ty: &FuncType,
func: ptr::NonNull<VMFuncRef>,
params: Params,
) -> Result<Results> {
if cfg!(debug_assertions) {
Self::debug_typecheck(store.0, func.as_ref().type_index);
}
let params = {
let mut store = AutoAssertNoGc::new(store.0);
params.into_abi(&mut store, ty)?
};
let mut captures = (func, MaybeUninit::uninit(), params, false);
let result = invoke_wasm_and_catch_traps(store, |caller| {
let (func_ref, ret, params, returned) = &mut captures;
let func_ref = func_ref.as_ref();
let result =
Params::invoke::<Results>(func_ref.native_call, func_ref.vmctx, caller, *params);
ptr::write(ret.as_mut_ptr(), result);
*returned = true
});
let (_, ret, _, returned) = captures;
debug_assert_eq!(result.is_ok(), returned);
result?;
let mut store = AutoAssertNoGc::new(store.0);
Ok(Results::from_abi(&mut store, ret.assume_init()))
}
fn debug_typecheck(store: &StoreOpaque, func: VMSharedTypeIndex) {
let ty = FuncType::from_shared_type_index(store.engine(), func);
Params::typecheck(store.engine(), ty.params(), TypeCheckPosition::Param)
.expect("params should match");
Results::typecheck(store.engine(), ty.results(), TypeCheckPosition::Result)
.expect("results should match");
}
}
#[doc(hidden)]
#[derive(Copy, Clone)]
pub enum TypeCheckPosition {
Param,
Result,
}
pub unsafe trait WasmTy: Send {
#[doc(hidden)]
type Abi: 'static + Copy;
#[doc(hidden)]
#[inline]
fn typecheck(engine: &Engine, actual: ValType, position: TypeCheckPosition) -> Result<()> {
let expected = Self::valtype();
debug_assert!(expected.comes_from_same_engine(engine));
debug_assert!(actual.comes_from_same_engine(engine));
match position {
TypeCheckPosition::Result => actual.ensure_matches(engine, &expected),
TypeCheckPosition::Param => match (expected.as_ref(), actual.as_ref()) {
(Some(expected_ref), Some(actual_ref)) if actual_ref.heap_type().is_concrete() => {
expected_ref
.heap_type()
.top(engine)
.ensure_matches(engine, &actual_ref.heap_type().top(engine))
}
_ => expected.ensure_matches(engine, &actual),
},
}
}
#[doc(hidden)]
fn valtype() -> ValType;
#[doc(hidden)]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool;
#[doc(hidden)]
fn dynamic_concrete_type_check(
&self,
store: &StoreOpaque,
nullable: bool,
actual: &FuncType,
) -> Result<()>;
#[doc(hidden)]
fn is_non_i31_gc_ref(&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 AutoAssertNoGc<'_>) -> Result<Self::Abi>;
#[doc(hidden)]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self;
}
macro_rules! integers {
($($primitive:ident/$get_primitive:ident => $ty: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 dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_non_i31_gc_ref(&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 AutoAssertNoGc<'_>) -> Result<Self::Abi>
{
Ok(self)
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &mut AutoAssertNoGc<'_>) -> Self {
abi
}
}
)*)
}
integers! {
i32/get_i32 => I32
i64/get_i64 => I64
u32/get_u32 => I32
u64/get_u64 => 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 dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
unreachable!()
}
#[inline]
fn is_non_i31_gc_ref(&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 AutoAssertNoGc<'_>) -> Result<Self::Abi>
{
Ok(self)
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, _store: &mut AutoAssertNoGc<'_>) -> Self {
abi
}
}
)*)
}
floats! {
f32/u32/get_f32 => F32
f64/u64/get_f64 => F64
}
unsafe impl WasmTy for NoFunc {
type Abi = NoFunc;
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(false, HeapType::NoFunc))
}
#[inline]
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
match self._inner {}
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
match self._inner {}
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
match self._inner {}
}
#[inline]
unsafe fn abi_from_raw(_raw: *mut ValRaw) -> Self::Abi {
unreachable!("NoFunc is uninhabited")
}
#[inline]
unsafe fn abi_into_raw(_abi: Self::Abi, _raw: *mut ValRaw) {
unreachable!("NoFunc is uninhabited")
}
#[inline]
fn into_abi(self, _store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
unreachable!("NoFunc is uninhabited")
}
#[inline]
unsafe fn from_abi(_abi: Self::Abi, _store: &mut AutoAssertNoGc<'_>) -> Self {
unreachable!("NoFunc is uninhabited")
}
}
unsafe impl WasmTy for Option<NoFunc> {
type Abi = *mut NoFunc;
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(true, HeapType::NoFunc))
}
#[inline]
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
true
}
#[inline]
fn dynamic_concrete_type_check(
&self,
_: &StoreOpaque,
nullable: bool,
func_ty: &FuncType,
) -> Result<()> {
if nullable {
Ok(())
} else {
bail!("argument type mismatch: expected (ref {func_ty}), found null reference")
}
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
false
}
#[inline]
unsafe fn abi_from_raw(_raw: *mut ValRaw) -> Self::Abi {
ptr::null_mut()
}
#[inline]
unsafe fn abi_into_raw(_abi: Self::Abi, raw: *mut ValRaw) {
*raw = ValRaw::funcref(ptr::null_mut());
}
#[inline]
fn into_abi(self, _store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
Ok(ptr::null_mut())
}
#[inline]
unsafe fn from_abi(_abi: Self::Abi, _store: &mut AutoAssertNoGc<'_>) -> Self {
None
}
}
unsafe impl WasmTy for Func {
type Abi = NonNull<wasmtime_runtime::VMFuncRef>;
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(false, HeapType::Func))
}
#[inline]
fn compatible_with_store<'a>(&self, store: &StoreOpaque) -> bool {
store.store_data().contains(self.0)
}
#[inline]
fn dynamic_concrete_type_check(
&self,
store: &StoreOpaque,
_nullable: bool,
actual: &FuncType,
) -> Result<()> {
self.ensure_matches_ty(store, actual)
.context("argument type mismatch for reference to concrete type")
}
#[inline]
fn is_non_i31_gc_ref(&self) -> bool {
false
}
#[inline]
unsafe fn abi_from_raw(raw: *mut ValRaw) -> Self::Abi {
let p = (*raw).get_funcref();
debug_assert!(!p.is_null());
NonNull::new_unchecked(p.cast::<wasmtime_runtime::VMFuncRef>())
}
#[inline]
unsafe fn abi_into_raw(abi: Self::Abi, raw: *mut ValRaw) {
*raw = ValRaw::funcref(abi.cast::<c_void>().as_ptr());
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
Ok(self.vm_func_ref(store))
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self {
Func::from_vm_func_ref(store, abi.as_ptr()).unwrap()
}
}
unsafe impl WasmTy for Option<Func> {
type Abi = *mut wasmtime_runtime::VMFuncRef;
#[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
}
}
fn dynamic_concrete_type_check(
&self,
store: &StoreOpaque,
nullable: bool,
func_ty: &FuncType,
) -> Result<()> {
if let Some(f) = self {
f.ensure_matches_ty(store, func_ty)
.context("argument type mismatch for reference to concrete type")
} else if nullable {
Ok(())
} else {
bail!("argument type mismatch: expected (ref {func_ty}), found null reference")
}
}
#[inline]
fn is_non_i31_gc_ref(&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.cast());
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>) -> Result<Self::Abi> {
Ok(if let Some(f) = self {
f.vm_func_ref(store).as_ptr()
} else {
ptr::null_mut()
})
}
#[inline]
unsafe fn from_abi(abi: Self::Abi, store: &mut AutoAssertNoGc<'_>) -> Self {
Func::from_vm_func_ref(store, abi)
}
}
pub unsafe trait WasmParams: Send {
#[doc(hidden)]
type Abi: Copy;
#[doc(hidden)]
fn typecheck(
engine: &Engine,
params: impl ExactSizeIterator<Item = crate::ValType>,
position: TypeCheckPosition,
) -> Result<()>;
#[doc(hidden)]
fn non_i31_gc_refs_count(&self) -> usize;
#[doc(hidden)]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>, func_ty: &FuncType) -> Result<Self::Abi>;
#[doc(hidden)]
unsafe fn invoke<R: WasmResults>(
func: NonNull<VMNativeCallFunction>,
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(
engine: &Engine,
params: impl ExactSizeIterator<Item = crate::ValType>,
position: TypeCheckPosition,
) -> Result<()> {
<(T,) as WasmParams>::typecheck(engine, params, position)
}
#[inline]
fn non_i31_gc_refs_count(&self) -> usize {
T::is_non_i31_gc_ref(self) as usize
}
#[inline]
fn into_abi(self, store: &mut AutoAssertNoGc<'_>, func_ty: &FuncType) -> Result<Self::Abi> {
<(T,) as WasmParams>::into_abi((self,), store, func_ty)
}
unsafe fn invoke<R: WasmResults>(
func: NonNull<VMNativeCallFunction>,
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(
_engine: &Engine,
mut params: impl ExactSizeIterator<Item = crate::ValType>,
_position: TypeCheckPosition,
) -> Result<()> {
let mut _n = 0;
$(
match params.next() {
Some(t) => {
_n += 1;
$t::typecheck(_engine, t, _position)?
},
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 non_i31_gc_refs_count(&self) -> usize {
let ($(ref $t,)*) = self;
0 $(
+ $t.is_non_i31_gc_ref() as usize
)*
}
#[inline]
fn into_abi(
self,
_store: &mut AutoAssertNoGc<'_>,
_func_ty: &FuncType,
) -> Result<Self::Abi> {
let ($($t,)*) = self;
let mut _i = 0;
$(
if !$t.compatible_with_store(_store) {
bail!("attempt to pass cross-`Store` value to Wasm as function argument");
}
if $t::valtype().is_ref() {
let p = _func_ty.param(_i).unwrap();
let r = p.unwrap_ref();
if let Some(c) = r.heap_type().as_concrete() {
$t.dynamic_concrete_type_check(_store, r.is_nullable(), c)?;
}
}
let $t = $t.into_abi(_store)?;
_i += 1;
)*
Ok(($($t,)*))
}
unsafe fn invoke<R: WasmResults>(
func: NonNull<VMNativeCallFunction>,
vmctx1: *mut VMOpaqueContext,
vmctx2: *mut VMContext,
abi: Self::Abi,
) -> R::ResultAbi {
let fnptr = mem::transmute::<
NonNull<VMNativeCallFunction>,
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| {
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 AutoAssertNoGc<'_>, 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 AutoAssertNoGc<'_>, 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 AutoAssertNoGc<'_>, abi: Self::ResultAbi) -> Self {
let ($($t,)*) = abi;
($($t::from_abi($t, store),)*)
}
}
};
}
for_each_function_signature!(impl_wasm_results);