use super::state::{Freg32, Freg64, Inst, Ip, Ireg, Mem0Len, Mem0Ptr, Sp, VmState, mem0_bytes};
#[cfg(feature = "simd")]
use crate::core::simd::ImmLaneIdx;
use crate::{
Error,
Func,
Global,
Instance,
Memory,
Nullable,
RefType,
Table,
TrapCode,
V128,
core::{
CoreElementSegment,
CoreGlobal,
CoreMemory,
CoreTable,
RawVal,
ShiftAmount,
Sign,
WriteAs,
},
engine::{
DedupFuncType,
EngineFunc,
FuncEntry,
executor::{
LoadFromCellsByValue,
StoreToCells,
handler::{Break, Control, Done, DoneReason},
},
utils::unreachable_unchecked,
},
func::{FuncEntity, HostFuncEntity},
instance::InstanceEntity,
ir,
ir::{
Address,
BoundedSlotSpan,
BranchOffset,
Local,
Offset16,
Slot,
SlotAndReg,
SlotSpan,
index,
},
memory::{DataSegment, DataSegmentEntity},
store::{CallHooks, PrunedStore, StoreError, StoreInner},
table::ElementSegment,
};
use core::num::NonZero;
macro_rules! consume_fuel {
($state:expr, $ip:expr, $ireg:expr, $freg32:expr, $freg64:expr, $fuel:expr, $eval:expr $(,)? ) => {{
if let ::core::result::Result::Err($crate::errors::FuelError::OutOfFuel { required_fuel }) =
$fuel.consume_fuel_if($eval)
{
out_of_fuel!($state, $ip, $ireg, $freg32, $freg64, required_fuel)
}
}};
}
macro_rules! out_of_fuel {
($state:expr, $ip:expr, $ireg:expr, $freg32:expr, $freg64:expr, $required_fuel:expr) => {{
$state.stack.sync_ip($ip);
$state.stack.sync_regs($ireg, $freg32, $freg64);
done!(
$state,
$crate::engine::executor::handler::DoneReason::out_of_fuel($required_fuel),
)
}};
}
pub fn compile_or_get_func_entry(
state: &mut VmState,
func: &FuncEntry,
) -> Result<(Ip, u16, u16), Error> {
let fuel_mut = state.store.inner_mut().fuel_mut();
let features = state.code.features();
let compiled_func = func.get_or_compile(Some(fuel_mut), features)?;
let ip = Ip::from(compiled_func.ops());
let len_local_slots = compiled_func.len_local_slots();
let len_stack_slots = compiled_func.len_stack_slots();
Ok((ip, len_local_slots, len_stack_slots))
}
macro_rules! compile_or_get_func_entry {
($state:expr, $func:expr) => {{
match $crate::engine::executor::handler::utils::compile_or_get_func_entry($state, $func) {
Ok((ip, len_local_slots, len_stack_slots)) => (ip, len_local_slots, len_stack_slots),
Err(error) => done!($state, DoneReason::error(error)),
}
}};
}
pub fn compile_or_get_func(state: &mut VmState, func: EngineFunc) -> Result<(Ip, u16, u16), Error> {
let Some(func_entry) = state.code.entry(func) else {
unreachable!("missing function entry at: {func:?}")
};
compile_or_get_func_entry(state, func_entry)
}
macro_rules! compile_or_get_func {
($state:expr, $func:expr) => {{
match $crate::engine::executor::handler::utils::compile_or_get_func($state, $func) {
Ok((ip, len_local_slots, len_stack_slots)) => (ip, len_local_slots, len_stack_slots),
Err(error) => done!($state, DoneReason::error(error)),
}
}};
}
macro_rules! trap {
($trap_code:expr) => {{
return $crate::engine::executor::handler::Control::Break(
$crate::engine::executor::handler::Break::from($trap_code),
);
}};
}
macro_rules! done {
($state:expr, $reason:expr $(,)? ) => {{
$state.done_with(move || {
<_ as ::core::convert::Into<$crate::engine::executor::handler::DoneReason>>::into(
$reason,
)
});
return $crate::engine::executor::handler::dispatch::control_break();
}};
}
pub trait IntoControl {
type Value;
fn into_control(self) -> Control<Self::Value, Break>;
}
impl<T> IntoControl for Result<T, TrapCode> {
type Value = T;
fn into_control(self) -> Control<Self::Value, Break> {
match self {
Ok(value) => Control::Continue(value),
Err(trap_code) => Control::Break(Break::from(trap_code)),
}
}
}
macro_rules! impl_into_control {
( $($ty:ty),* $(,)? ) => {
$(
impl IntoControl for $ty {
type Value = Self;
fn into_control(self) -> Control<Self::Value, Break> {
Control::Continue(self)
}
}
)*
};
}
impl_into_control! {
bool,
u8, u16, u32, u64, usize,
i8, i16, i32, i64, isize,
f32, f64,
V128,
RawVal,
}
pub trait GetValue<T> {
fn get_value(src: Self, sp: Sp, ireg: Ireg, freg32: Freg32, freg64: Freg64) -> T;
}
macro_rules! impl_get_value {
( $($ty:ty),* $(,)? ) => {
$(
impl GetValue<$ty> for $ty {
#[inline(always)]
fn get_value(src: Self, _sp: Sp, _ireg: Ireg, _freg32: Freg32, _freg64: Freg64) -> $ty {
src
}
}
)*
};
}
impl_get_value!(
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
f32,
f64,
NonZero<i32>,
NonZero<i64>,
NonZero<u32>,
NonZero<u64>,
Sign<f32>,
Sign<f64>,
Address,
Offset16,
ShiftAmount,
V128,
);
#[cfg(feature = "simd")]
impl_get_value!([ImmLaneIdx<32>; 16]);
macro_rules! impl_get_value_for_ireg {
( $($prim:ty),* $(,)? ) => {
$(
impl GetValue<$prim> for ir::Reg<i64> {
#[inline]
fn get_value(_src: Self, _sp: Sp, ireg: Ireg, _freg32: Freg32, _freg64: Freg64) -> $prim {
<$prim as From<Ireg>>::from(ireg)
}
}
)*
};
}
impl_get_value_for_ireg!(bool, i8, i16, i32, i64, u8, u16, u32, u64, ShiftAmount);
impl From<Ireg> for ShiftAmount {
#[inline]
fn from(value: Ireg) -> Self {
Self::from(u8::from(value))
}
}
impl GetValue<f32> for ir::Reg<f32> {
#[inline]
fn get_value(_src: Self, _sp: Sp, _ireg: Ireg, freg32: Freg32, _freg64: Freg64) -> f32 {
f32::from(freg32)
}
}
impl GetValue<f64> for ir::Reg<f64> {
#[inline]
fn get_value(_src: Self, _sp: Sp, _ireg: Ireg, _freg32: Freg32, freg64: Freg64) -> f64 {
f64::from(freg64)
}
}
impl<T> GetValue<T> for Slot
where
T: LoadFromCellsByValue,
{
fn get_value(src: Self, sp: Sp, _ireg: Ireg, _freg32: Freg32, _freg64: Freg64) -> T {
unsafe { sp.get::<T>(src) }
}
}
#[inline]
pub fn get_slot_value<L>(src: Slot, sp: Sp) -> L
where
Slot: GetValue<L>,
{
<Slot as GetValue<L>>::get_value(
src,
sp,
Ireg::default(),
Freg32::default(),
Freg64::default(),
)
}
#[inline]
pub fn get_value<T, L>(src: T, sp: Sp, ireg: Ireg, freg32: Freg32, freg64: Freg64) -> L
where
T: GetValue<L>,
{
<T as GetValue<L>>::get_value(src, sp, ireg, freg32, freg64)
}
pub trait SetValue<T> {
#[must_use]
fn set_value(
dst: Self,
src: T,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64);
}
impl<T> SetValue<T> for ir::Reg<i64>
where
T: Into<Ireg>,
{
#[inline]
fn set_value(
_dst: Self,
src: T,
_sp: Sp,
_ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
(src.into(), freg32, freg64)
}
}
impl<T> SetValue<T> for ir::Reg<f32>
where
T: Into<Freg32>,
{
#[inline]
fn set_value(
_dst: Self,
src: T,
_sp: Sp,
ireg: Ireg,
_freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
(ireg, src.into(), freg64)
}
}
impl<T> SetValue<T> for ir::Reg<f64>
where
T: Into<Freg64>,
{
#[inline]
fn set_value(
_dst: Self,
src: T,
_sp: Sp,
ireg: Ireg,
freg32: Freg32,
_freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
(ireg, freg32, src.into())
}
}
impl<const N: u16, T> SetValue<T> for Local<N>
where
T: StoreToCells,
{
#[inline]
fn set_value(
_dst: Self,
src: T,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
<Slot as SetValue<T>>::set_value(Slot::from(N), src, sp, ireg, freg32, freg64)
}
}
impl<T> SetValue<T> for SlotAndReg<i64>
where
T: StoreToCells + Into<Ireg> + Copy,
{
#[inline]
fn set_value(
dst: Self,
src: T,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
let (ireg, freg32, freg64) = set_value(dst.slot, src, sp, ireg, freg32, freg64);
set_value(dst.reg, src, sp, ireg, freg32, freg64)
}
}
impl<T> SetValue<T> for SlotAndReg<f32>
where
T: StoreToCells + Into<Freg32> + Copy,
{
#[inline]
fn set_value(
dst: Self,
src: T,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
let (ireg, freg32, freg64) = set_value(dst.slot, src, sp, ireg, freg32, freg64);
set_value(dst.reg, src, sp, ireg, freg32, freg64)
}
}
impl<T> SetValue<T> for SlotAndReg<f64>
where
T: StoreToCells + Into<Freg64> + Copy,
{
#[inline]
fn set_value(
dst: Self,
src: T,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
let (ireg, freg32, freg64) = set_value(dst.slot, src, sp, ireg, freg32, freg64);
set_value(dst.reg, src, sp, ireg, freg32, freg64)
}
}
impl<T> SetValue<T> for Slot
where
T: StoreToCells,
{
#[inline]
fn set_value(
dst: Self,
src: T,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64) {
unsafe { sp.set::<T>(dst, src) };
(ireg, freg32, freg64)
}
}
#[inline]
pub fn set_slot_value<T, V>(dst: T, src: V, sp: Sp)
where
T: SetValue<V>,
{
_ = <T as SetValue<V>>::set_value(
dst,
src,
sp,
Ireg::default(),
Freg32::default(),
Freg64::default(),
);
}
#[inline]
#[must_use]
pub fn set_value<T, V>(
dst: T,
src: V,
sp: Sp,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> (Ireg, Freg32, Freg64)
where
T: SetValue<V>,
{
<T as SetValue<V>>::set_value(dst, src, sp, ireg, freg32, freg64)
}
macro_rules! set_value {
($dst:expr, $src:expr, $sp:expr, $ireg:ident, $freg32:ident, $freg64:ident) => {
let ($ireg, $freg32, $freg64): (
$crate::engine::executor::handler::state::Ireg,
$crate::engine::executor::handler::state::Freg32,
$crate::engine::executor::handler::state::Freg64,
) = {
$crate::engine::executor::handler::utils::set_value(
$dst, $src, $sp, $ireg, $freg32, $freg64,
)
};
};
}
#[expect(clippy::too_many_arguments)]
pub fn exec_return(
state: &mut VmState,
_ip: Ip,
sp: Sp,
mem0: Mem0Ptr,
mem0_len: Mem0Len,
instance: Inst,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> Done {
let Some((ip, sp, mem0, mem0_len, instance)) =
state.stack.pop_frame(state.store, mem0, mem0_len, instance)
else {
done!(state, DoneReason::Return(sp))
};
dispatch!(
state, ip, sp, mem0, mem0_len, instance, ireg, freg32, freg64
)
}
pub fn exec_copy_span(sp: Sp, dst: SlotSpan, src: SlotSpan, len: u16) {
debug_assert_ne!(dst, src);
debug_assert!(len > 0);
let op = match dst.head() <= src.head() {
true => exec_copy_span_asc,
false => exec_copy_span_des,
};
op(sp, dst, src, len)
}
pub fn exec_copy_span_asc(sp: Sp, dst: SlotSpan, src: SlotSpan, len: u16) {
debug_assert!(dst.head() < src.head());
debug_assert!(len > 0);
let mut dst = dst.head();
let mut src = src.head();
let dst_end = dst.next_n(len);
while dst != dst_end {
let value: u64 = get_slot_value(src, sp);
set_slot_value(dst, value, sp);
dst = dst.next();
src = src.next();
}
}
pub fn exec_copy_span_des(sp: Sp, dst: SlotSpan, src: SlotSpan, len: u16) {
debug_assert!(dst.head() > src.head());
debug_assert!(len > 0);
let dst_end = dst.head();
let mut dst = dst.head().next_n(len);
let mut src = src.head().next_n(len);
while dst != dst_end {
dst = dst.prev();
src = src.prev();
let value: u64 = get_slot_value(src, sp);
set_slot_value(dst, value, sp);
}
}
pub fn extract_mem0(store: &mut PrunedStore, instance: Inst) -> (Mem0Ptr, Mem0Len) {
let instance = unsafe { instance.as_ref() };
let Some(memory) = instance.get_memory(0) else {
return (Mem0Ptr::from([].as_mut_ptr()), Mem0Len::from(0));
};
let mem0 = resolve_memory_mut(store, &memory).data_mut();
let mem0_ptr = mem0.as_mut_ptr();
let mem0_len = mem0.len();
(Mem0Ptr::from(mem0_ptr), Mem0Len::from(mem0_len))
}
pub fn memory_bytes<'a>(
memory: index::Memory,
mem0: Mem0Ptr,
mem0_len: Mem0Len,
instance: Inst,
state: &'a mut VmState,
) -> &'a mut [u8] {
if memory.is_default() {
return mem0_bytes::<'a>(mem0, mem0_len);
}
let memory = fetch_memory(instance, memory);
resolve_memory_mut(state.store, &memory).data_mut()
}
pub fn memory_slice(memory: &CoreMemory, pos: usize, len: usize) -> Result<&[u8], TrapCode> {
memory
.data()
.get(pos..)
.and_then(|memory| memory.get(..len))
.ok_or(TrapCode::MemoryOutOfBounds)
}
pub fn memory_slice_mut(
memory: &mut CoreMemory,
pos: usize,
len: usize,
) -> Result<&mut [u8], TrapCode> {
memory
.data_mut()
.get_mut(pos..)
.and_then(|memory| memory.get_mut(..len))
.ok_or(TrapCode::MemoryOutOfBounds)
}
pub fn offset_ip(ip: Ip, offset: BranchOffset) -> Ip {
unsafe { ip.offset(i32::from(offset) as isize) }
}
macro_rules! impl_fetch_from_instance {
(
$( fn $fn:ident($param:ident: $ty:ty) -> $ret:ty = $getter:expr );* $(;)?
) => {
$(
pub fn $fn(instance: Inst, $param: $ty) -> $ret {
let instance = unsafe { instance.as_ref() };
let index = ::core::primitive::u32::from($param);
let Some($param) = $getter(instance, index) else {
unsafe {
$crate::engine::utils::unreachable_unchecked!(
::core::concat!("missing ", ::core::stringify!($param), " at: {:?}"),
index,
)
}
};
$param
}
)*
};
}
impl_fetch_from_instance! {
fn fetch_data(func: index::Data) -> DataSegment = InstanceEntity::get_data_segment;
fn fetch_elem(func: index::Elem) -> ElementSegment = InstanceEntity::get_element_segment;
fn fetch_func(func: index::Func) -> Func = InstanceEntity::get_func;
fn fetch_global(global: index::Global) -> Global = InstanceEntity::get_global;
fn fetch_memory(memory: index::Memory) -> Memory = InstanceEntity::get_memory;
fn fetch_table(table: index::Table) -> Table = InstanceEntity::get_table;
fn fetch_func_type(func_type: index::FuncType) -> DedupFuncType = {
|instance: &InstanceEntity, index: u32| instance.get_signature(index).copied()
};
}
macro_rules! impl_resolve_from_store {
(
$( fn $fn:ident($param:ident: $ty:ty) -> $ret:ty = $getter:expr );* $(;)?
) => {
$(
pub fn $fn<'a>(store: &'a mut PrunedStore, $param: $ty) -> $ret {
match $getter(store.inner_mut(), $param) {
::core::result::Result::Ok($param) => $param,
::core::result::Result::Err(error) => unsafe {
$crate::engine::utils::unreachable_unchecked!(
::core::concat!("could not resolve stored ", ::core::stringify!($param), ": {:?}"),
error,
)
},
}
}
)*
};
}
impl_resolve_from_store! {
// fn resolve_elem(elem: &ElementSegment) -> &'a CoreElementSegment = StoreInner::try_resolve_element;
fn resolve_func(func: &Func) -> &'a FuncEntity = StoreInner::try_resolve_func;
fn resolve_global(global: &Global) -> &'a CoreGlobal = StoreInner::try_resolve_global;
fn resolve_memory(memory: &Memory) -> &'a CoreMemory = StoreInner::try_resolve_memory;
fn resolve_table(table: &Table) -> &'a CoreTable = StoreInner::try_resolve_table;
fn resolve_instance(func: &Instance) -> &'a InstanceEntity = StoreInner::try_resolve_instance;
// fn resolve_func_type(func_type: DedupFuncType) -> DedupFuncType = StoreInner::resolve_func_type;
fn resolve_elem_mut(elem: &ElementSegment) -> &'a mut CoreElementSegment = StoreInner::try_resolve_element_mut;
fn resolve_data_mut(data: &DataSegment) -> &'a mut DataSegmentEntity = StoreInner::try_resolve_data_mut;
fn resolve_global_mut(global: &Global) -> &'a mut CoreGlobal = StoreInner::try_resolve_global_mut;
fn resolve_memory_mut(memory: &Memory) -> &'a mut CoreMemory = StoreInner::try_resolve_memory_mut;
fn resolve_table_mut(table: &Table) -> &'a mut CoreTable = StoreInner::try_resolve_table_mut;
}
#[expect(clippy::too_many_arguments)]
pub fn resolve_indirect_func<I>(
index: I,
table: index::Table,
func_type: index::FuncType,
state: &mut VmState<'_>,
sp: Sp,
instance: Inst,
ireg: Ireg,
freg32: Freg32,
freg64: Freg64,
) -> Result<Func, TrapCode>
where
I: GetValue<u64>,
{
let index = get_value(index, sp, ireg, freg32, freg64);
let table = fetch_table(instance, table);
let table = resolve_table(state.store, &table);
let rawref = table.get(index).ok_or(TrapCode::TableOutOfBounds)?;
debug_assert!(matches!(rawref.ty(), RefType::Func));
let funcref = <Nullable<Func>>::from_raw_parts(rawref.raw(), &*state.store);
let func = funcref.val().ok_or(TrapCode::IndirectCallToNull)?;
let actual_fnty = resolve_func(state.store, func).ty_dedup();
let expected_fnty = fetch_func_type(instance, func_type);
if expected_fnty.ne(actual_fnty) {
return Err(TrapCode::BadSignature);
}
Ok(*func)
}
pub fn set_global<V>(global: index::Global, value: V, state: &mut VmState, instance: Inst)
where
RawVal: WriteAs<V>,
{
let global = fetch_global(instance, global);
let global = resolve_global_mut(state.store, &global);
let mut value_ptr = global.get_raw_ptr();
let global_ref = unsafe { value_ptr.as_mut() };
global_ref.write_as(value);
}
pub fn update_instance(
store: &mut PrunedStore,
instance: Inst,
new_instance: Inst,
mem0: Mem0Ptr,
mem0_len: Mem0Len,
) -> (Inst, Mem0Ptr, Mem0Len) {
if new_instance == instance {
return (instance, mem0, mem0_len);
}
let (mem0, mem0_len) = extract_mem0(store, new_instance);
(new_instance, mem0, mem0_len)
}
pub fn call_func_entry(
state: &mut VmState,
caller_ip: Ip,
params: BoundedSlotSpan,
func: &FuncEntry,
instance: Option<Inst>,
) -> Control<(Ip, Sp), Break> {
let (callee_ip, len_local_slots, len_stack_slots) = compile_or_get_func_entry!(state, func);
let callee_sp = state
.stack
.push_frame(
Some(caller_ip),
callee_ip,
params,
len_local_slots,
len_stack_slots,
instance,
)
.into_control()?;
Control::Continue((callee_ip, callee_sp))
}
#[inline(never)]
pub fn call_wasm(
state: &mut VmState,
caller_ip: Ip,
params: BoundedSlotSpan,
func: EngineFunc,
instance: Option<Inst>,
) -> Control<(Ip, Sp), Break> {
let (callee_ip, len_local_slots, len_stack_slots) = compile_or_get_func!(state, func);
let callee_sp = state
.stack
.push_frame(
Some(caller_ip),
callee_ip,
params,
len_local_slots,
len_stack_slots,
instance,
)
.into_control()?;
Control::Continue((callee_ip, callee_sp))
}
pub fn return_call_func_entry(
state: &mut VmState,
params: BoundedSlotSpan,
func: &FuncEntry,
instance: Option<Inst>,
) -> Control<(Ip, Sp), Break> {
let (callee_ip, len_local_slots, len_stack_slots) = compile_or_get_func_entry!(state, func);
let callee_sp = state
.stack
.replace_frame(
callee_ip,
params,
len_local_slots,
len_stack_slots,
instance,
)
.into_control()?;
Control::Continue((callee_ip, callee_sp))
}
#[inline(never)]
pub fn return_call_wasm(
state: &mut VmState,
params: BoundedSlotSpan,
func: EngineFunc,
instance: Option<Inst>,
) -> Control<(Ip, Sp), Break> {
let (callee_ip, len_local_slots, len_stack_slots) = compile_or_get_func!(state, func);
let callee_sp = state
.stack
.replace_frame(
callee_ip,
params,
len_local_slots,
len_stack_slots,
instance,
)
.into_control()?;
Control::Continue((callee_ip, callee_sp))
}
pub fn call_host(
state: &mut VmState,
func: Func,
caller_ip: Option<Ip>,
host_func: HostFuncEntity,
params: BoundedSlotSpan,
instance: Option<Inst>,
call_hooks: CallHooks,
) -> Control<Sp, Break> {
debug_assert_eq!(params.len(), host_func.len_param_cells());
let trampoline = *host_func.trampoline();
let (sp, inout) = state
.stack
.prepare_host_frame(caller_ip, params, host_func.len_result_cells())
.into_control()?;
match state
.store
.call_host_func(trampoline, instance, inout, call_hooks)
{
Ok(()) => {}
Err(StoreError::External(error)) => {
done!(state, DoneReason::host_error(error, func, params.span()))
}
Err(StoreError::Internal(error)) => unsafe {
unreachable_unchecked!(
"internal interpreter error while executing host function: {error}"
)
},
}
state.code.refresh();
Control::Continue(sp)
}
pub fn return_call_host(
state: &mut VmState,
func: Func,
host_func: HostFuncEntity,
params: BoundedSlotSpan,
instance: Inst,
) -> Control<(Ip, Sp, Inst), Break> {
debug_assert_eq!(params.len(), host_func.len_param_cells());
let trampoline = *host_func.trampoline();
let (control, inout) = state
.stack
.return_prepare_host_frame(params, host_func.len_result_cells(), instance)
.into_control()?;
match state
.store
.call_host_func(trampoline, Some(instance), inout, CallHooks::Call)
{
Ok(()) => {}
Err(StoreError::External(error)) => {
// Note: we won't allow resumption in case the execution would
// have returned with this the host function tail call.
let reason = match control {
Control::Continue(_) => DoneReason::host_error(error, func, params.span()),
Control::Break(_) => DoneReason::error(error),
};
done!(state, reason)
}
Err(StoreError::Internal(error)) => unsafe {
unreachable_unchecked!(
"internal interpreter error while executing host function: {error}"
)
},
}
state.code.refresh();
match control {
Control::Continue((ip, sp, instance)) => Control::Continue((ip, sp, instance)),
Control::Break(sp) => done!(state, DoneReason::Return(sp)),
}
}
pub fn call_wasm_or_host(
state: &mut VmState,
caller_ip: Ip,
func: Func,
params: BoundedSlotSpan,
mem0: Mem0Ptr,
mem0_len: Mem0Len,
instance: Inst,
) -> Control<(Ip, Sp, Mem0Ptr, Mem0Len, Inst), Break> {
let func_entity = resolve_func(state.store, &func);
let next_state = match func_entity {
FuncEntity::Wasm(wasm_func) => {
let func = wasm_func.func_body();
let callee_instance = *wasm_func.instance();
let callee_instance: Inst = resolve_instance(state.store, &callee_instance).into();
let (callee_ip, callee_sp) =
call_wasm(state, caller_ip, params, func, Some(callee_instance))?;
let (instance, mem0, mem0_len) =
update_instance(state.store, instance, callee_instance, mem0, mem0_len);
(callee_ip, callee_sp, mem0, mem0_len, instance)
}
FuncEntity::Host(host_func) => {
let host_func = *host_func;
let sp = call_host(
state,
func,
Some(caller_ip),
host_func,
params,
Some(instance),
CallHooks::Call,
)?;
// Host functions may re-enter WASM (e.g. calling cabi_realloc)
// which can trigger memory.grow, invalidating the cached mem0
// pointer. Re-extract to avoid stale pointer dereference.
let (mem0, mem0_len) = extract_mem0(state.store, instance);
(caller_ip, sp, mem0, mem0_len, instance)
}
};
Control::Continue(next_state)
}