#![allow(clippy::indexing_slicing)]
use super::epoch::apply_epoch_deadline;
use super::exn::surface_exception;
use super::frame::Delimiter;
use super::{cell, Execution, Outcome};
use crate::canon::RefKind;
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) fn root_ref_params(inner: &mut StoreInner, params: &[Val]) {
for v in params {
match v {
Val::AnyRef(Some(r)) => inner.push_gc_root(r.raw(), RefKind::Any),
Val::ExternRef(Some(r)) => inner.push_gc_root(r.raw(), RefKind::Extern),
Val::ExnRef(Some(r)) => inner.push_gc_root(r.raw(), RefKind::Exn),
_ => {}
}
}
}
fn decode_results(result_tys: &[ValType], cells: Vec<cell::Cell>) -> Vec<Val> {
result_tys
.iter()
.zip(cells)
.map(|(t, c)| cell::decode(c, t))
.collect()
}
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: Vec<Val>,
) -> (Execution, Boundary) {
let mut exec = store.inner.take_exec();
let value_base = exec.values.len();
let stop_depth = exec.frames.len();
let delim = if stop_depth == 0 {
Delimiter::TopLevel
} else {
Delimiter::HostReentry
};
exec.enter_call(delim, instance, func_index, code, args);
let b = Boundary {
value_base,
stop_depth,
run_stop: stop_depth + 1,
};
(exec, b)
}
pub(super) fn finish<T>(
store: &mut Store<T>,
mut exec: Execution,
b: &Boundary,
result_tys: &[ValType],
outcome: Result<()>,
) -> Result<Vec<Val>> {
let result = match outcome {
Ok(()) => Ok(decode_results(
result_tys,
exec.take_results(b.value_base, b.stop_depth),
)),
Err(e) => {
exec.discard_to(b.value_base, b.stop_depth);
Err(surface_exception(&mut store.inner, e))
}
};
if b.stop_depth != 0 {
store.inner.park_exec(exec);
}
result
}
pub(crate) fn execute<T>(
store: &mut Store<T>,
instance: Instance,
func_index: u32,
code: Code,
args: Vec<Val>,
result_tys: &[ValType],
) -> Result<Vec<Val>> {
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)
}
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 } => exec.do_grow(store, memory, delta)?,
Outcome::TableGrow { table, delta, init } => {
exec.do_grow_table(store, table, delta, init)?;
}
Outcome::GcGrow {
reserved_target,
bytes_needed,
} => store.grow_gc_reservation(reserved_target, bytes_needed)?,
}
}
}
impl Execution {
#[inline(never)]
pub(super) fn invoke_host<T>(
&mut self,
store: &mut Store<T>,
func: Func,
instance: Instance,
stop_depth: usize,
) -> Result<()> {
let (mut params, mut results) = store.inner.take_host_scratch();
let host_index = match store.inner.func(func) {
FuncEntity::Host {
sig, host_index, ..
} => {
results.clear();
results.extend_from_slice(&sig.result_defaults);
params.clear();
self.pop_params_into(&sig.params, &mut params);
*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")
}
};
let roots_mark = store.inner.gc_roots_mark();
root_ref_params(&mut store.inner, ¶ms);
let cb = store.host_funcs[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)),
¶ms,
&mut results,
)
}) {
Ok(result) => result,
Err(payload) => {
super::guard::restore_after_panic(&mut store.inner, roots_mark);
super::guard::reraise(payload);
}
};
store.inner.swap_exec(self); if let Err(e) = result {
store.inner.gc_roots_truncate(roots_mark);
return self.host_call_error(&mut store.inner, e, stop_depth);
}
store.inner.take_pending_exception();
self.push_results_slice(&results);
store.inner.put_host_scratch(params, results);
store.inner.gc_roots_truncate(roots_mark); Ok(())
}
pub(super) fn host_call_error(
&mut self,
inner: &mut crate::store::StoreInner,
e: crate::Error,
stop_depth: usize,
) -> Result<()> {
if e.is::<ThrownException>() {
if let Some(exn) = inner.take_pending_exception() {
return self.raise_host_exception(inner, exn, stop_depth);
}
}
Err(e)
}
fn do_grow<T>(&mut self, store: &mut Store<T>, memory: Memory, delta: u64) -> Result<()> {
let is_64 = store.inner.memory(memory).ty.is_64();
let old = store.grow_memory(memory, delta)?;
self.push_index(is_64, old.unwrap_or(u64::MAX)); Ok(())
}
fn do_grow_table<T>(
&mut self,
store: &mut Store<T>,
table: Table,
delta: u64,
init: Ref,
) -> Result<()> {
let is_64 = store.inner.table(table).ty.is_64();
let old = store.grow_table(table, delta, init)?;
self.push_index(is_64, old.unwrap_or(u64::MAX));
Ok(())
}
}