use super::Func;
use crate::{
engine::Stack,
func::{CallResultsTuple, FuncError},
ir::SlotSpan,
AsContext,
AsContextMut,
Engine,
Error,
TrapCode,
Val,
WasmResults,
};
use core::{fmt, marker::PhantomData, mem::replace, ops::Deref};
#[derive(Debug)]
pub(crate) enum ResumableCallBase<T> {
Finished(T),
HostTrap(ResumableCallHostTrap),
OutOfFuel(ResumableCallOutOfFuel),
}
#[derive(Debug)]
pub enum ResumableError {
HostTrap(ResumableHostTrapError),
OutOfFuel(ResumableOutOfFuelError),
}
impl ResumableError {
pub fn into_error(self) -> Error {
match self {
ResumableError::HostTrap(error) => error.into_error(),
ResumableError::OutOfFuel(error) => error.into_error(),
}
}
}
#[derive(Debug)]
pub struct ResumableHostTrapError {
host_error: Error,
host_func: Func,
caller_results: SlotSpan,
}
impl core::error::Error for ResumableHostTrapError {}
impl fmt::Display for ResumableHostTrapError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.host_error.fmt(f)
}
}
impl ResumableHostTrapError {
#[cold]
pub(crate) fn new(host_error: Error, host_func: Func, caller_results: SlotSpan) -> Self {
Self {
host_error,
host_func,
caller_results,
}
}
pub(crate) fn into_error(self) -> Error {
self.host_error
}
pub(crate) fn host_func(&self) -> &Func {
&self.host_func
}
pub(crate) fn caller_results(&self) -> &SlotSpan {
&self.caller_results
}
}
#[derive(Debug)]
pub struct ResumableOutOfFuelError {
required_fuel: u64,
}
impl core::error::Error for ResumableOutOfFuelError {}
impl fmt::Display for ResumableOutOfFuelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ran out of fuel while calling a resumable function: required_fuel={}",
self.required_fuel
)
}
}
impl ResumableOutOfFuelError {
#[cold]
pub(crate) fn new(required_fuel: u64) -> Self {
Self { required_fuel }
}
pub(crate) fn required_fuel(self) -> u64 {
self.required_fuel
}
pub(crate) fn into_error(self) -> Error {
Error::from(TrapCode::OutOfFuel)
}
}
#[derive(Debug)]
pub enum ResumableCall {
Finished,
HostTrap(ResumableCallHostTrap),
OutOfFuel(ResumableCallOutOfFuel),
}
impl ResumableCall {
pub(crate) fn new(call: ResumableCallBase<()>) -> Self {
match call {
ResumableCallBase::Finished(()) => Self::Finished,
ResumableCallBase::HostTrap(invocation) => Self::HostTrap(invocation),
ResumableCallBase::OutOfFuel(invocation) => Self::OutOfFuel(invocation),
}
}
}
#[derive(Debug)]
pub struct ResumableCallCommon {
engine: Engine,
func: Func,
stack: Stack,
}
impl ResumableCallCommon {
pub(super) fn new(engine: Engine, func: Func, stack: Stack) -> Self {
Self {
engine,
func,
stack,
}
}
pub(super) fn take_stack(&mut self) -> Stack {
replace(&mut self.stack, Stack::empty())
}
pub(super) fn stack_mut(&mut self) -> &mut Stack {
&mut self.stack
}
fn prepare_outputs<T>(
&self,
ctx: impl AsContext<Data = T>,
outputs: &mut [Val],
) -> Result<(), Error> {
self.engine.resolve_func_type(
self.func.ty_dedup(ctx.as_context()),
|func_type| -> Result<(), Error> {
func_type.prepare_outputs(outputs)?;
Ok(())
},
)
}
}
impl Drop for ResumableCallCommon {
fn drop(&mut self) {
let stack = self.take_stack();
self.engine.recycle_stack(stack);
}
}
unsafe impl Sync for ResumableCallCommon {}
#[derive(Debug)]
pub struct ResumableCallHostTrap {
pub(super) common: ResumableCallCommon,
host_func: Func,
host_error: Error,
caller_results: SlotSpan,
}
impl ResumableCallHostTrap {
pub(super) fn new(
engine: Engine,
func: Func,
host_func: Func,
host_error: Error,
caller_results: SlotSpan,
stack: Stack,
) -> Self {
Self {
common: ResumableCallCommon::new(engine, func, stack),
host_func,
host_error,
caller_results,
}
}
pub(super) fn update(&mut self, host_func: Func, host_error: Error, caller_results: SlotSpan) {
self.host_func = host_func;
self.host_error = host_error;
self.caller_results = caller_results;
}
pub(super) fn update_to_out_of_fuel(self, required_fuel: u64) -> ResumableCallOutOfFuel {
ResumableCallOutOfFuel {
common: self.common,
required_fuel,
}
}
pub fn host_func(&self) -> Func {
self.host_func
}
pub fn host_error(&self) -> &Error {
&self.host_error
}
pub fn into_host_error(self) -> Error {
self.host_error
}
pub(crate) fn caller_results(&self) -> SlotSpan {
self.caller_results
}
fn validate_inputs<T>(
&self,
ctx: impl AsContext<Data = T>,
inputs: &[Val],
) -> Result<(), FuncError> {
self.common
.engine
.resolve_func_type(self.host_func().ty_dedup(ctx.as_context()), |func_type| {
func_type.match_results(inputs)
})
}
pub fn resume<T>(
self,
mut ctx: impl AsContextMut<Data = T>,
inputs: &[Val],
outputs: &mut [Val],
) -> Result<ResumableCall, Error> {
self.validate_inputs(ctx.as_context(), inputs)?;
self.common.prepare_outputs(ctx.as_context(), outputs)?;
self.common
.engine
.clone()
.resume_func_host_trap(ctx.as_context_mut(), self, inputs, outputs)
.map(ResumableCall::new)
}
}
#[derive(Debug)]
pub struct ResumableCallOutOfFuel {
pub(super) common: ResumableCallCommon,
required_fuel: u64,
}
impl ResumableCallOutOfFuel {
pub(super) fn new(engine: Engine, func: Func, stack: Stack, required_fuel: u64) -> Self {
Self {
common: ResumableCallCommon::new(engine, func, stack),
required_fuel,
}
}
pub(super) fn update(&mut self, required_fuel: u64) {
self.required_fuel = required_fuel;
}
pub(super) fn update_to_host_trap(
self,
host_func: Func,
host_error: Error,
caller_results: SlotSpan,
) -> ResumableCallHostTrap {
ResumableCallHostTrap {
common: self.common,
host_func,
host_error,
caller_results,
}
}
pub fn required_fuel(&self) -> u64 {
self.required_fuel
}
pub fn resume<T>(
self,
mut ctx: impl AsContextMut<Data = T>,
outputs: &mut [Val],
) -> Result<ResumableCall, Error> {
self.common.prepare_outputs(ctx.as_context(), outputs)?;
self.common
.engine
.clone()
.resume_func_out_of_fuel(ctx.as_context_mut(), self, outputs)
.map(ResumableCall::new)
}
}
#[derive(Debug)]
pub enum TypedResumableCall<T> {
Finished(T),
HostTrap(TypedResumableCallHostTrap<T>),
OutOfFuel(TypedResumableCallOutOfFuel<T>),
}
impl<Results> TypedResumableCall<Results> {
pub(crate) fn new(call: ResumableCallBase<Results>) -> Self {
match call {
ResumableCallBase::Finished(results) => Self::Finished(results),
ResumableCallBase::HostTrap(invocation) => {
Self::HostTrap(TypedResumableCallHostTrap::new(invocation))
}
ResumableCallBase::OutOfFuel(invocation) => {
Self::OutOfFuel(TypedResumableCallOutOfFuel::new(invocation))
}
}
}
}
pub struct TypedResumableCallHostTrap<Results> {
invocation: ResumableCallHostTrap,
results: PhantomData<fn() -> Results>,
}
impl<Results> TypedResumableCallHostTrap<Results> {
pub(crate) fn new(invocation: ResumableCallHostTrap) -> Self {
Self {
invocation,
results: PhantomData,
}
}
pub fn resume<T>(
self,
mut ctx: impl AsContextMut<Data = T>,
inputs: &[Val],
) -> Result<TypedResumableCall<Results>, Error>
where
Results: WasmResults,
{
self.invocation.validate_inputs(ctx.as_context(), inputs)?;
self.common
.engine
.clone()
.resume_func_host_trap(
ctx.as_context_mut(),
self.invocation,
inputs,
<CallResultsTuple<Results>>::default(),
)
.map(TypedResumableCall::new)
}
}
impl<Results> Deref for TypedResumableCallHostTrap<Results> {
type Target = ResumableCallHostTrap;
fn deref(&self) -> &Self::Target {
&self.invocation
}
}
impl<Results> fmt::Debug for TypedResumableCallHostTrap<Results> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypedResumableCallHostTrap")
.field("invocation", &self.invocation)
.field("results", &self.results)
.finish()
}
}
pub struct TypedResumableCallOutOfFuel<Results> {
invocation: ResumableCallOutOfFuel,
results: PhantomData<fn() -> Results>,
}
impl<Results> TypedResumableCallOutOfFuel<Results> {
pub(crate) fn new(invocation: ResumableCallOutOfFuel) -> Self {
Self {
invocation,
results: PhantomData,
}
}
pub fn resume<T>(
self,
mut ctx: impl AsContextMut<Data = T>,
) -> Result<TypedResumableCall<Results>, Error>
where
Results: WasmResults,
{
self.common
.engine
.clone()
.resume_func_out_of_fuel(
ctx.as_context_mut(),
self.invocation,
<CallResultsTuple<Results>>::default(),
)
.map(TypedResumableCall::new)
}
}
impl<Results> Deref for TypedResumableCallOutOfFuel<Results> {
type Target = ResumableCallOutOfFuel;
fn deref(&self) -> &Self::Target {
&self.invocation
}
}
impl<Results> fmt::Debug for TypedResumableCallOutOfFuel<Results> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypedResumableCallOutOfFuel")
.field("invocation", &self.invocation)
.field("results", &self.results)
.finish()
}
}