use super::outcome::Callee;
use super::{CallReq, Execution, ResolvedCall, StepOutcome};
use crate::canon::{CanonicalTypeId, RefKind};
use crate::error::ir_get;
use crate::func::Func;
use crate::instance::Instance;
use crate::store::{FuncEntity, StoreInner};
use crate::trap::Trap;
use crate::value::{FuncType, Ref};
use crate::Result;
#[derive(Clone, Copy)]
pub(super) enum CallKind {
Nested(u32),
Tail,
}
#[inline]
pub(super) fn resolve(inner: &StoreInner, f: Func) -> Result<ResolvedCall> {
Ok(match inner.func(f) {
FuncEntity::Wasm {
instance,
func_index,
} => {
let module = &inner.instance(*instance).module;
let (index, func) =
module
.defined(*func_index)
.ok_or(crate::error::InternalError::FrameStack(
"call target is not a defined function",
))?;
ResolvedCall::Wasm(Callee {
instance: *instance,
func_index: *func_index,
index,
key: module.key(),
n_params: func.n_params,
type_idx: func.type_idx,
})
}
FuncEntity::Host { .. } => ResolvedCall::Host(f),
#[cfg(feature = "async")]
FuncEntity::HostAsync { .. } => ResolvedCall::HostAsync(f),
})
}
impl Execution {
#[inline]
pub(super) fn do_call_indirect(
&mut self,
inner: &StoreInner,
instance: Instance,
(type_idx, table): (u32, u32),
kind: CallKind,
at: usize,
) -> Result<StepOutcome> {
let handle = *ir_get(&inner.instance(instance).tables, table)?;
let idx = self.index_at(at, inner.table(handle).ty.is_64())?;
let f = match inner.table(handle).get(idx) {
Some(Ref::Func(Some(f))) => f,
Some(Ref::Func(None)) => return Err(Trap::IndirectCallToNull.into()),
Some(_) => return Err(Trap::BadSignature.into()),
None => return Err(Trap::TableOutOfBounds.into()),
};
let expected_module = inner.instance(instance).module.clone();
let expected_id = expected_module.inner().canonical_type_id(type_idx)?;
let resolved = resolve(inner, f)?;
match &resolved {
ResolvedCall::Wasm(callee) => {
let actual_id = inner
.instance(callee.instance)
.module
.inner()
.canonical_type_id(callee.type_idx)?;
if !inner.engine().is_subtype(actual_id, expected_id) {
return Err(Trap::BadSignature.into());
}
}
ResolvedCall::Host(func) => host_sig_ok(inner, *func, expected_id)?,
#[cfg(feature = "async")]
ResolvedCall::HostAsync(func) => host_sig_ok(inner, *func, expected_id)?,
}
let args_at = args_below(inner, &resolved, at)?;
Ok(call_outcome(inner, resolved, instance, kind, args_at))
}
#[inline]
pub(super) fn do_call_ref(
&mut self,
inner: &StoreInner,
instance: Instance,
kind: CallKind,
at: usize,
) -> Result<StepOutcome> {
let f = match self.take_ref(at, RefKind::Func)?.to_ref() {
Ref::Func(Some(f)) => f,
Ref::Func(None) => return Err(Trap::NullReference.into()),
_ => return Err(Trap::BadSignature.into()),
};
let resolved = resolve(inner, f)?;
let args_at = args_below(inner, &resolved, at)?;
Ok(call_outcome(inner, resolved, instance, kind, args_at))
}
}
#[inline]
pub(super) fn call_outcome(
inner: &StoreInner,
resolved: ResolvedCall,
instance: Instance,
kind: CallKind,
args_at: usize,
) -> StepOutcome {
match resolved {
ResolvedCall::Wasm(callee) => {
let return_ip = match kind {
CallKind::Tail => 0,
CallKind::Nested(return_ip) => return_ip,
};
let req = CallReq {
return_ip,
args_at,
callee,
};
match kind {
CallKind::Tail => StepOutcome::TailCall(req),
CallKind::Nested(_) => StepOutcome::Call(req),
}
}
ResolvedCall::Host(func) => match kind {
CallKind::Tail => StepOutcome::TailHostCall {
func,
instance,
args_at,
n_params: host_n_params(inner, func),
},
CallKind::Nested(return_ip) => StepOutcome::HostCall {
func,
instance,
top: args_at.saturating_add(host_n_params(inner, func)),
return_ip,
},
},
#[cfg(feature = "async")]
ResolvedCall::HostAsync(func) => match kind {
CallKind::Tail => StepOutcome::TailHostAsyncCall {
func,
instance,
args_at,
n_params: host_n_params(inner, func),
},
CallKind::Nested(return_ip) => StepOutcome::HostAsyncCall {
func,
instance,
top: args_at.saturating_add(host_n_params(inner, func)),
return_ip,
},
},
}
}
fn host_sig_ok(inner: &StoreInner, func: Func, expected_id: CanonicalTypeId) -> Result<()> {
if inner
.engine()
.is_subtype(host_ty(inner, func).canonical_id(), expected_id)
{
Ok(())
} else {
Err(Trap::BadSignature.into())
}
}
fn args_below(inner: &StoreInner, resolved: &ResolvedCall, operand_at: usize) -> Result<usize> {
let n_params = match resolved {
ResolvedCall::Wasm(callee) => callee.n_params as usize,
ResolvedCall::Host(f) => host_n_params(inner, *f),
#[cfg(feature = "async")]
ResolvedCall::HostAsync(f) => host_n_params(inner, *f),
};
operand_at.checked_sub(n_params).ok_or_else(|| {
crate::error::InternalError::OperandStack("call arguments below the frame").into()
})
}
fn host_n_params(inner: &StoreInner, f: Func) -> usize {
match inner.func(f) {
FuncEntity::Host { sig, .. } => sig.params.len(),
#[cfg(feature = "async")]
FuncEntity::HostAsync { sig, .. } => sig.params.len(),
FuncEntity::Wasm { .. } => 0,
}
}
fn host_ty(inner: &StoreInner, f: crate::func::Func) -> &FuncType {
match inner.func(f) {
FuncEntity::Host { ty, .. } => ty,
#[cfg(feature = "async")]
FuncEntity::HostAsync { ty, .. } => ty,
FuncEntity::Wasm { .. } => unreachable!("resolve returned a host func"),
}
}