use tinywasm_types::{FuncAddr, TypeAddr};
use super::{FromWasmValues, Function, FunctionTyped, IntoWasmValues};
use crate::interpreter::stack::{CallFrame, StackBase};
use crate::{Error, InterpreterRuntime, Result, Store, WasmValue};
#[derive(Clone, PartialEq, Eq)]
pub enum ExecProgress<T> {
Completed(T),
Suspended,
}
#[cfg_attr(feature = "debug", derive(core::fmt::Debug))]
pub struct FuncExecution<'store> {
execution: ExecutionInner<'store>,
results: &'store mut [WasmValue],
}
#[cfg_attr(feature = "debug", derive(core::fmt::Debug))]
pub struct FuncExecutionTyped<'store, R> {
execution: ExecutionInner<'store>,
result: Option<R>,
}
#[cfg_attr(feature = "debug", derive(core::fmt::Debug))]
struct ExecutionInner<'store> {
store: &'store mut Store,
state: ExecState,
}
#[cfg_attr(feature = "debug", derive(core::fmt::Debug))]
enum ExecState {
Running { callframe: CallFrame, root_func_addr: FuncAddr },
Completed(Option<CallResult>),
}
#[cfg_attr(feature = "debug", derive(core::fmt::Debug))]
enum CallResult {
Stack { type_addr: TypeAddr },
Written,
}
impl Function {
pub fn call_resumable<'store>(
&self,
store: &'store mut Store,
params: &[WasmValue],
results: &'store mut [WasmValue],
) -> Result<FuncExecution<'store>> {
let type_addr = self.validate_call(store, params, results.len())?;
store.enter_execution()?;
let result: Result<ExecState> = (|| {
if store.state.funcs.is_host(self.addr()) {
let func = {
let func = store.state.funcs.host(self.addr());
func.func.clone()
};
func.call_values(store, self.module_id, type_addr, params, results)?;
return Ok(ExecState::Completed(Some(CallResult::Written)));
}
let (wasm_params, wasm_locals) = {
let wasm = store.state.funcs.wasm(self.addr());
(wasm.func.params, wasm.func.locals)
};
store.call_stack.clear();
store.value_stack.clear();
store.push_wasm_values(params)?;
let locals_base = store.value_stack.enter_locals(&wasm_params, &wasm_locals)?;
let callframe = CallFrame::new(self.addr(), locals_base, wasm_locals);
Ok(ExecState::Running { callframe, root_func_addr: self.addr() })
})();
if result.is_err() {
store.call_stack.clear();
store.value_stack.clear();
}
store.exit_execution();
let state = result?;
Ok(FuncExecution { execution: ExecutionInner { store, state }, results })
}
}
impl ExecutionInner<'_> {
fn resume_raw(
&mut self,
run: impl FnOnce(&mut Store, CallFrame) -> Result<crate::interpreter::ExecState>,
) -> Result<ExecProgress<CallResult>> {
let (callframe, root_func_addr) = match &mut self.state {
ExecState::Running { callframe, root_func_addr } => (*callframe, *root_func_addr),
ExecState::Completed(result) => {
return result
.take()
.map(ExecProgress::Completed)
.ok_or_else(|| Error::other("execution already completed"));
}
};
self.store.enter_execution()?;
let result = run(self.store, callframe);
self.store.exit_execution();
let result = match result {
Ok(result) => result,
Err(error) => {
self.store.call_stack.clear();
self.store.value_stack.clear();
self.state = ExecState::Completed(None);
return Err(error);
}
};
match result {
crate::interpreter::ExecState::Completed => {
let result_ty = self.store.state.funcs.type_addr(root_func_addr);
self.state = ExecState::Completed(None);
Ok(ExecProgress::Completed(CallResult::Stack { type_addr: result_ty }))
}
crate::interpreter::ExecState::Suspended(callframe) => {
let ExecState::Running { callframe: current, .. } = &mut self.state else {
unreachable!("invalid function execution state")
};
*current = callframe;
Ok(ExecProgress::Suspended)
}
}
}
}
impl FuncExecution<'_> {
fn resume(
&mut self,
run: impl FnOnce(&mut Store, CallFrame) -> Result<crate::interpreter::ExecState>,
) -> Result<ExecProgress<()>> {
match self.execution.resume_raw(run)? {
ExecProgress::Completed(CallResult::Stack { type_addr }) => {
self.execution.store.pop_stack_values(type_addr, self.results)?;
Ok(ExecProgress::Completed(()))
}
ExecProgress::Completed(CallResult::Written) => Ok(ExecProgress::Completed(())),
ExecProgress::Suspended => Ok(ExecProgress::Suspended),
}
}
pub fn resume_with_fuel(&mut self, fuel: u32) -> Result<ExecProgress<()>> {
self.resume(|store, callframe| InterpreterRuntime::exec_with_fuel(store, callframe, fuel))
}
#[cfg(feature = "std")]
pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result<ExecProgress<()>> {
self.resume(|store, callframe| InterpreterRuntime::exec_with_time_budget(store, callframe, time_budget))
}
}
impl<P: IntoWasmValues, R: FromWasmValues> FunctionTyped<P, R> {
pub fn call_resumable<'store>(&self, store: &'store mut Store, params: P) -> Result<FuncExecutionTyped<'store, R>> {
self.func.item.validate_store(store)?;
if store.state.funcs.is_host(self.func.addr()) {
let result = self.call(store, params)?;
let execution = ExecutionInner { store, state: ExecState::Completed(None) };
return Ok(FuncExecutionTyped { execution, result: Some(result) });
}
let (type_addr, wasm_params, wasm_locals) = {
let wasm = store.state.funcs.wasm(self.func.addr());
(wasm.type_addr, wasm.func.params, wasm.func.locals)
};
store.enter_execution()?;
let result: Result<ExecState> = (|| {
store.call_stack.clear();
store.value_stack.clear();
store.push_typed_values::<false>(type_addr, params.into_wasm_values(), StackBase::default())?;
let locals_base = store
.value_stack
.enter_locals(&wasm_params, &wasm_locals)
.inspect_err(|_| store.value_stack.clear())?;
let callframe = CallFrame::new(self.func.addr(), locals_base, wasm_locals);
Ok(ExecState::Running { callframe, root_func_addr: self.func.addr() })
})();
store.exit_execution();
let execution = ExecutionInner { store, state: result? };
Ok(FuncExecutionTyped { execution, result: None })
}
}
impl<'store, R: FromWasmValues> FuncExecutionTyped<'store, R> {
fn resume(
&mut self,
run: impl FnOnce(&mut Store, CallFrame) -> Result<crate::interpreter::ExecState>,
) -> Result<ExecProgress<R>> {
if let Some(result) = self.result.take() {
return Ok(ExecProgress::Completed(result));
}
match self.execution.resume_raw(run)? {
ExecProgress::Completed(CallResult::Stack { type_addr }) => {
Ok(ExecProgress::Completed(self.execution.store.take_typed_results(type_addr, StackBase::default())?))
}
ExecProgress::Completed(CallResult::Written) => unreachable!("untyped result in typed execution"),
ExecProgress::Suspended => Ok(ExecProgress::Suspended),
}
}
pub fn resume_with_fuel(&mut self, fuel: u32) -> Result<ExecProgress<R>> {
self.resume(|store, callframe| InterpreterRuntime::exec_with_fuel(store, callframe, fuel))
}
#[cfg(feature = "std")]
pub fn resume_with_time_budget(&mut self, time_budget: crate::std::time::Duration) -> Result<ExecProgress<R>> {
self.resume(|store, callframe| InterpreterRuntime::exec_with_time_budget(store, callframe, time_budget))
}
}