#![allow(clippy::indexing_slicing)]
use super::epoch::{apply_epoch_deadline_async, yield_now};
use super::host::{enter, finish, HostCallState};
use super::{Execution, Outcome};
use crate::func::{Caller, Func};
use crate::instance::Instance;
use crate::module::code::Code;
use crate::store::{FuncEntity, Store};
use crate::value::{Val, ValType};
use crate::Result;
pub(crate) async fn execute_async<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 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(), crate::canon::RefKind::Exn);
}
let token = store.inner.begin_async_call_cleanup(
Some(roots_mark),
pending,
pending_generation,
Some(crate::store::AsyncCallBoundary {
value_base: b.value_base,
stop_depth: b.stop_depth,
}),
);
let mut cancellation = super::guard::AsyncCancellation::new(token);
let outcome = drive_async(&mut exec, store, b.run_stop).await;
let cleanup = store
.inner
.complete_async_call_cleanup(cancellation.token());
cancellation.disarm();
store.inner.gc_roots_truncate(roots_mark);
let outcome = match cleanup {
Ok(()) => outcome,
Err(error) => {
store
.inner
.restore_pending_exception(pending, pending_generation);
Err(error)
}
};
finish(store, exec, &b, result_tys, outcome)
}
async fn drive_async<T>(exec: &mut Execution, store: &mut Store<T>, run_stop: usize) -> Result<()> {
loop {
match exec.run(store, run_stop)? {
Outcome::Finished => return Ok(()),
Outcome::HostAsync { func, instance } => {
exec.invoke_host_async(store, func, instance, run_stop)
.await?;
exec.gc_pressure_safepoint(&mut store.inner)?;
}
Outcome::FuelYield => {
store.inner.swap_exec(exec);
yield_now().await;
store.inner.recover_cancelled_async_calls();
store.inner.swap_exec(exec);
store.inner.refuel_from_reserve();
}
Outcome::EpochDeadline => {
if let Err(e) = apply_epoch_deadline_async(exec, store).await {
return Err(exec.attach_suspension_backtrace(&store.inner, e));
}
}
Outcome::Grow { memory, delta } => {
let is_64 = store.inner.memory(memory).ty.is_64();
store.inner.swap_exec(exec);
let old = store.grow_memory_async(memory, delta).await;
store.inner.recover_cancelled_async_calls();
store.inner.swap_exec(exec);
exec.push_index(is_64, old?.unwrap_or(u64::MAX));
}
Outcome::TableGrow { table, delta, init } => {
let is_64 = store.inner.table(table).ty.is_64();
store.inner.swap_exec(exec);
let old = store.grow_table_async(table, delta, init).await;
store.inner.recover_cancelled_async_calls();
store.inner.swap_exec(exec);
exec.push_index(is_64, old?.unwrap_or(u64::MAX));
}
Outcome::GcGrow {
reserved_target,
bytes_needed,
} => exec.do_grow_gc(store, reserved_target, bytes_needed)?,
}
}
}
impl Execution {
async fn invoke_host_async<T>(
&mut self,
store: &mut Store<T>,
func: Func,
instance: Instance,
stop_depth: usize,
) -> Result<()> {
let mut state = self.prep_host_async(store, func)?;
let cb = store.async_host_funcs[state.host_index as usize].clone();
store.inner.swap_exec(self);
let guarded = async {
let caller = Caller::new(store.as_context_mut(), Some(instance));
Box::into_pin(cb(caller, &state.params, &mut state.results)).await
};
let outcome = super::guard::CatchUnwind(Box::pin(guarded)).await;
let outcome = match outcome {
Ok(outcome) => {
store.inner.recover_cancelled_async_calls();
store.inner.swap_exec(self);
outcome
}
Err(payload) => {
super::guard::restore_after_panic(
&mut store.inner,
state.roots_mark,
state.pending,
state.pending_generation,
);
super::guard::reraise(payload);
}
};
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, outcome, stop_depth)
}
fn prep_host_async<T>(&mut self, store: &mut Store<T>, func: Func) -> Result<HostCallState> {
let (host_index, sig) = match store.inner.func(func) {
FuncEntity::HostAsync {
sig, host_index, ..
} => (*host_index, sig.clone()),
_ => unreachable!("HostAsync only suspends on async host funcs"),
};
self.prepare_host_call(store, host_index, sig)
}
}