#![allow(clippy::indexing_slicing)]
use super::epoch::apply_epoch_deadline;
use super::exn::surface_exception;
use super::frame::Delimiter;
use super::{Execution, Outcome};
use crate::canon::RefKind;
use crate::error::InternalError;
use crate::exception::ThrownException;
use crate::extern_::{Memory, Table};
use crate::func::{Caller, Func};
use crate::instance::Instance;
use crate::module::code::Code;
use crate::store::{FuncEntity, Store, StoreInner};
use crate::value::{Ref, Val, ValType};
use crate::Result;
pub(super) struct HostCallState {
pub(super) params: Vec<Val>,
pub(super) results: Vec<Val>,
pub(super) roots_mark: usize,
pub(super) pending: Option<crate::value::Rooted<crate::value::ExnRef>>,
pub(super) pending_generation: u64,
pub(super) host_index: u32,
pub(super) func: Func,
pub(super) results_at: usize,
}
fn host_sig(inner: &StoreInner, func: Func) -> Result<&crate::store::HostSig> {
match inner.func(func) {
FuncEntity::Host { sig, .. } => Ok(sig),
#[cfg(feature = "async")]
FuncEntity::HostAsync { sig, .. } => Ok(sig),
FuncEntity::Wasm { .. } => {
Err(InternalError::FrameStack("host call target is not a host function").into())
}
}
}
pub(super) fn root_ref_params(inner: &mut StoreInner, params: &[Val]) {
for v in params {
match v {
Val::AnyRef(Some(r)) => inner.gc_roots_mut().push((r.raw(), RefKind::Any)),
Val::ExternRef(Some(r)) => inner.gc_roots_mut().push((r.raw(), RefKind::Extern)),
Val::ExnRef(Some(r)) => inner.gc_roots_mut().push((r.raw(), RefKind::Exn)),
_ => {}
}
}
}
fn check_results(inner: &mut StoreInner, result_tys: &[ValType], out: &mut [Val]) -> Result<()> {
if !result_tys.iter().any(ValType::is_ref) {
return Ok(());
}
let roots_mark = inner.gc_roots_mark();
let result = validate_and_root(inner, result_tys, out);
if result.is_err() {
inner.gc_roots_truncate(roots_mark);
for (out, ty) in out.iter_mut().zip(result_tys) {
*out = Val::default_for_valtype(ty);
}
}
result
}
fn validate_and_root(
inner: &mut StoreInner,
result_tys: &[ValType],
out: &mut [Val],
) -> Result<()> {
if let Err(error) = crate::extern_::ensure_values_match(
inner,
out,
result_tys,
"execution attempted to return an incompatible value",
) {
return Err(error
.downcast_ref::<InternalError>()
.copied()
.unwrap_or(InternalError::ResultShape(
"execution returned an invalid or dangling value",
))
.into());
}
for value in out {
*value = inner.root_host_result(*value).map_err(|error| {
error
.downcast_ref::<InternalError>()
.copied()
.unwrap_or(InternalError::GcMetadata(
"execution result referenced invalid GC state",
))
})?;
}
Ok(())
}
pub(super) struct Boundary {
pub(super) value_base: usize,
pub(super) stop_depth: usize,
pub(super) run_stop: usize,
}
pub(super) fn enter<T>(
store: &mut Store<T>,
instance: Instance,
func_index: u32,
code: Code,
args: &[Val],
) -> Result<(Execution, Boundary)> {
let error = if store.inner.has_parked_execution() {
store.inner.internal_error()
} else {
store.inner.take_internal_error()
};
if let Some(error) = error {
return Err(error);
}
let mut exec = store.inner.take_exec();
exec.trim_to_live(); exec.stack_limit = store.inner.engine().max_wasm_stack();
exec.gates_passed = false; let value_base = exec.slots.len();
let stop_depth = exec.frames.len();
let delim = if stop_depth == 0 {
exec.mem0 = super::NO_MEM0; Delimiter::TopLevel
} else {
Delimiter::HostReentry
};
let native = match super::native::nested_bytes() {
0 => 0,
used => used.saturating_add(super::HOST_REENTRY_RESERVE),
};
if let Err(error) = exec.enter_call((delim, native), instance, func_index, code, args) {
let error = exec.attach_suspension_backtrace(&store.inner, error);
if stop_depth != 0 {
store.inner.park_exec(exec);
}
return Err(error);
}
let b = Boundary {
value_base,
stop_depth,
run_stop: stop_depth.checked_add(1).ok_or(InternalError::FrameStack(
"execution boundary depth overflow",
))?,
};
Ok((exec, b))
}
pub(super) fn finish<T>(
store: &mut Store<T>,
mut exec: Execution,
b: &Boundary,
result_tys: &[ValType],
outcome: Result<()>,
out: &mut [Val],
) -> Result<()> {
let result = match outcome {
Ok(()) => match exec.take_results(b.value_base, b.stop_depth, result_tys, out) {
Ok(()) => check_results(&mut store.inner, result_tys, out),
Err(error) => match exec.discard_to(b.value_base, b.stop_depth) {
Ok(()) => Err(error),
Err(cleanup_error) => Err(cleanup_error),
},
},
Err(e) => match exec.discard_to(b.value_base, b.stop_depth) {
Ok(()) => Err(surface_exception(&mut store.inner, e)),
Err(cleanup_error) => Err(cleanup_error),
},
};
if b.stop_depth != 0 {
if let Err(error) = &result {
if let Some(error) = error.downcast_ref::<crate::error::InternalError>() {
store.inner.latch_internal_error(*error);
}
}
store.inner.park_exec(exec);
} else {
store.inner.recycle_exec(exec);
}
result
}
pub(crate) fn execute<T>(
store: &mut Store<T>,
(instance, func_index, code): (Instance, u32, Code),
args: &[Val],
result_tys: &[ValType],
out: &mut [Val],
) -> Result<()> {
let (mut exec, b) = enter(store, instance, func_index, code, args)?;
let outcome = drive(&mut exec, store, b.run_stop);
finish(store, exec, &b, result_tys, outcome, out)
}
fn drive<T>(exec: &mut Execution, store: &mut Store<T>, run_stop: usize) -> Result<()> {
loop {
match exec.run(store, run_stop)? {
Outcome::Finished => return Ok(()),
#[cfg(feature = "async")]
Outcome::HostAsync { .. } => {
return Err(crate::Error::msg(
"async host function called from a synchronous context",
))
}
#[cfg(feature = "async")]
Outcome::FuelYield => {
return Err(crate::Error::msg("fuel yield requires an async store"))
}
Outcome::EpochDeadline => {
if let Err(e) = apply_epoch_deadline(exec, store) {
return Err(exec.attach_suspension_backtrace(&store.inner, e));
}
}
Outcome::Grow { memory, delta, at } => exec.do_grow(store, memory, delta, at)?,
Outcome::TableGrow {
table,
delta,
init,
at,
} => {
exec.do_grow_table(store, table, (delta, init), at)?;
}
Outcome::GcGrow {
reserved_target,
bytes_needed,
} => exec.do_grow_gc(store, reserved_target, bytes_needed)?,
}
}
}
impl Execution {
fn prep_host<T>(&mut self, store: &mut Store<T>, func: Func) -> Result<HostCallState> {
let host_index = match store.inner.func(func) {
FuncEntity::Host { host_index, .. } => *host_index,
FuncEntity::Wasm { .. } => unreachable!("HostCall only suspends on sync host funcs"),
#[cfg(feature = "async")]
FuncEntity::HostAsync { .. } => {
unreachable!("HostCall only suspends on sync host funcs")
}
};
self.prepare_host_call(store, func, host_index)
}
pub(super) fn prepare_host_call<T>(
&mut self,
store: &mut Store<T>,
func: Func,
host_index: u32,
) -> Result<HostCallState> {
let (mut params, mut results) = store.inner.take_host_scratch();
results.clear();
params.clear();
let popped = host_sig(&store.inner, func).and_then(|sig| {
results.extend_from_slice(&sig.result_defaults);
self.pop_params_into(&sig.params, &mut params)?;
self.set_live_end(self.sp)
});
if let Err(error) = popped {
store.inner.put_host_scratch(params, results);
return Err(error);
}
let roots_mark = store.inner.gc_roots_mut().len();
let pending = store.inner.pending_exception();
let pending_generation = store.inner.pending_exception_generation();
if let Some(exception) = pending {
store
.inner
.gc_roots_mut()
.push((exception.raw(), RefKind::Exn));
}
root_ref_params(&mut store.inner, ¶ms);
Ok(HostCallState {
params,
results,
roots_mark,
pending,
pending_generation,
host_index,
func,
results_at: self.sp,
})
}
pub(super) fn discard_host_call<T>(store: &mut Store<T>, state: HostCallState) {
store.inner.gc_roots_mut().truncate(state.roots_mark);
store.inner.put_host_scratch(state.params, state.results);
}
pub(super) fn finish_host_call<T>(
&mut self,
store: &mut Store<T>,
state: HostCallState,
outcome: Result<()>,
stop_depth: usize,
) -> Result<()> {
if let Err(error) = outcome {
let fresh_throw = error.is::<ThrownException>()
&& store.inner.pending_exception_generation() != state.pending_generation;
if fresh_throw {
let prior_pending = state.pending;
let prior_generation = state.pending_generation;
let Some(exn) = store.inner.take_pending_exception() else {
store
.inner
.restore_pending_exception(prior_pending, prior_generation);
Self::discard_host_call(store, state);
return Err(InternalError::ResultShape(
"host throw marker had no pending exception",
)
.into());
};
Self::discard_host_call(store, state);
store
.inner
.restore_pending_exception(prior_pending, prior_generation);
return self.raise_host_exception(&mut store.inner, exn, stop_depth);
}
Self::discard_host_call(store, state);
return Err(error);
}
if let Err(error) = host_sig(&store.inner, state.func).and_then(|sig| {
crate::extern_::ensure_values_match(
&store.inner,
&state.results,
&sig.results,
"function attempted to return an incompatible value",
)
}) {
store
.inner
.restore_pending_exception(state.pending, state.pending_generation);
Self::discard_host_call(store, state);
return Err(error);
}
self.sp = state.results_at;
self.push_results_slice(&state.results);
if self.frames.len() == stop_depth {
self.slots.truncate(self.sp);
}
let live = self.set_live_end(self.sp);
Self::discard_host_call(store, state);
live
}
#[inline(never)]
pub(super) fn invoke_host<T>(
&mut self,
store: &mut Store<T>,
func: Func,
instance: Instance,
stop_depth: usize,
) -> Result<()> {
let mut state = self.prep_host(store, func)?;
let cb = store.host_funcs[state.host_index as usize].clone();
store.inner.swap_exec(self); let result = match super::guard::catch_host(|| {
cb(
Caller::new(store.as_context_mut(), Some(instance)),
&state.params,
&mut state.results,
)
}) {
Ok(result) => result,
Err(payload) => {
super::guard::restore_after_panic(
&mut store.inner,
state.roots_mark,
state.pending,
state.pending_generation,
);
super::guard::reraise(payload);
}
};
#[cfg(feature = "async")]
store.inner.recover_cancelled_async_calls();
store.inner.swap_exec(self); if let Some(error) = store.inner.take_internal_error() {
store
.inner
.restore_pending_exception(state.pending, state.pending_generation);
Self::discard_host_call(store, state);
return Err(error);
}
self.finish_host_call(store, state, result, stop_depth)
}
fn do_grow<T>(
&mut self,
store: &mut Store<T>,
memory: Memory,
delta: u64,
at: usize,
) -> Result<()> {
let is_64 = store.inner.memory(memory).ty.is_64();
let old =
self.with_parked_host_operation(store, |store| store.grow_memory(memory, delta))?;
self.put_index(at, is_64, old.unwrap_or(u64::MAX)) }
fn do_grow_table<T>(
&mut self,
store: &mut Store<T>,
table: Table,
(delta, init): (u64, Ref),
at: usize,
) -> Result<()> {
let is_64 = store.inner.table(table).ty.is_64();
let old =
self.with_parked_host_operation(store, |store| store.grow_table(table, delta, init))?;
self.put_index(at, is_64, old.unwrap_or(u64::MAX))
}
pub(super) fn do_grow_gc<T>(
&mut self,
store: &mut Store<T>,
reserved_target: usize,
bytes_needed: u64,
) -> Result<()> {
self.with_parked_host_operation(store, |store| {
store.grow_gc_reservation(reserved_target, bytes_needed)
})
}
fn with_parked_host_operation<T, R>(
&mut self,
store: &mut Store<T>,
operation: impl FnOnce(&mut Store<T>) -> Result<R>,
) -> Result<R> {
let roots_mark = store.inner.gc_roots_mark();
let pending = store.inner.pending_exception();
let pending_generation = store.inner.pending_exception_generation();
if let Some(exception) = pending {
store.inner.push_gc_root(exception.raw(), RefKind::Exn);
}
store.inner.swap_exec(self);
let outcome = super::guard::catch_host(|| operation(store));
match outcome {
Ok(result) => {
#[cfg(feature = "async")]
store.inner.recover_cancelled_async_calls();
store.inner.swap_exec(self);
store.inner.gc_roots_truncate(roots_mark);
result
}
Err(payload) => {
super::guard::restore_after_panic(
&mut store.inner,
roots_mark,
pending,
pending_generation,
);
super::guard::reraise(payload);
}
}
}
}