radix_wasmi/engine/
resumable.rs

1use super::Func;
2use crate::{engine::Stack, func::CallResultsTuple, AsContextMut, Engine, Error, WasmResults};
3use core::{fmt, marker::PhantomData, mem::replace, ops::Deref};
4use wasmi_core::{Trap, Value};
5
6/// Returned by [`Engine`] methods for calling a function in a resumable way.
7///
8/// # Note
9///
10/// This is the base type for resumable call results and can be converted into
11/// either the dynamically typed [`ResumableCall`] or the statically typed
12/// [`TypedResumableCall`] that act as user facing API. Therefore this type
13/// must provide all the information necessary to be properly converted into
14/// either user facing types.
15#[derive(Debug)]
16pub(crate) enum ResumableCallBase<T> {
17    /// The resumable call has finished properly and returned a result.
18    Finished(T),
19    /// The resumable call encountered a host error and can be resumed.
20    Resumable(ResumableInvocation),
21}
22
23/// Returned by calling a [`Func`] in a resumable way.
24#[derive(Debug)]
25pub enum ResumableCall {
26    /// The resumable call has finished properly and returned a result.
27    Finished,
28    /// The resumable call encountered a host error and can be resumed.
29    Resumable(ResumableInvocation),
30}
31
32impl ResumableCall {
33    /// Creates a [`ResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`].
34    pub(crate) fn new(call: ResumableCallBase<()>) -> Self {
35        match call {
36            ResumableCallBase::Finished(()) => Self::Finished,
37            ResumableCallBase::Resumable(invocation) => Self::Resumable(invocation),
38        }
39    }
40}
41
42/// State required to resume a [`Func`] invocation.
43#[derive(Debug)]
44pub struct ResumableInvocation {
45    /// The engine in use for the function invokation.
46    ///
47    /// # Note
48    ///
49    /// - This handle is required to resolve function types
50    ///   of both `func` and `host_func` fields as well as in
51    ///   the `Drop` impl to recycle the stack.
52    engine: Engine,
53    /// The underlying root function to be executed.
54    ///
55    /// # Note
56    ///
57    /// The results of this function must always match with the
58    /// results given when resuming the call.
59    func: Func,
60    /// The host function that returned a host error.
61    ///
62    /// # Note
63    ///
64    /// - This is required to receive its result values that are
65    ///   needed to be fed back in manually by the user. This way we
66    ///   avoid heap memory allocations.
67    /// - The results of this function must always match with the
68    ///   arguments given when resuming the call.
69    host_func: Func,
70    /// The host error that was returned by the `host_func` which
71    /// caused the resumable function invocation to break.
72    ///
73    /// # Note
74    ///
75    /// This might be useful to users of this API to inspect the
76    /// actual host error. This is therefore guaranteed to never
77    /// be a Wasm trap.
78    host_error: Trap,
79    /// The value and call stack in use by the [`ResumableInvocation`].
80    ///
81    /// # Note
82    ///
83    /// - We need to keep the stack around since the user might want to
84    ///   resume the execution.
85    /// - This stack is borrowed from the engine and needs to be given
86    ///   back to the engine when the [`ResumableInvocation`] goes out
87    ///   of scope.
88    pub(super) stack: Stack,
89}
90
91impl ResumableInvocation {
92    /// Creates a new [`ResumableInvocation`].
93    pub(super) fn new(
94        engine: Engine,
95        func: Func,
96        host_func: Func,
97        host_error: Trap,
98        stack: Stack,
99    ) -> Self {
100        Self {
101            engine,
102            func,
103            host_func,
104            host_error,
105            stack,
106        }
107    }
108
109    /// Replaces the internal stack with an empty one that has no heap allocations.
110    pub(super) fn take_stack(&mut self) -> Stack {
111        replace(&mut self.stack, Stack::empty())
112    }
113
114    /// Updates the [`ResumableInvocation`] with the new `host_func` and a `host_error`.
115    pub(super) fn update(&mut self, host_func: Func, host_error: Trap) {
116        self.host_func = host_func;
117        self.host_error = host_error;
118    }
119}
120
121impl Drop for ResumableInvocation {
122    fn drop(&mut self) {
123        let stack = self.take_stack();
124        self.engine.recycle_stack(stack);
125    }
126}
127
128impl ResumableInvocation {
129    /// Returns the host [`Func`] that returned the host error.
130    ///
131    /// # Note
132    ///
133    /// When using [`ResumableInvocation::resume`] the `inputs`
134    /// need to match the results of this host function so that
135    /// the function invocation can properly resume. For that
136    /// number and types of the values provided must match.
137    pub fn host_func(&self) -> Func {
138        self.host_func
139    }
140
141    /// Returns a shared reference to the encountered host error.
142    ///
143    /// # Note
144    ///
145    /// This is guaranteed to never be a Wasm trap.
146    pub fn host_error(&self) -> &Trap {
147        &self.host_error
148    }
149
150    /// Resumes the call to the [`Func`] with the given inputs.
151    ///
152    /// The result is written back into the `outputs` buffer upon success.
153    ///
154    /// Returns a resumable handle to the function invocation upon
155    /// enountering host errors with which it is possible to handle
156    /// the error and continue the execution as if no error occured.
157    ///
158    /// # Errors
159    ///
160    /// - If the function resumption returned a Wasm [`Trap`].
161    /// - If the types or the number of values in `inputs` does not match
162    ///   the types and number of result values of the errorneous host function.
163    /// - If the number of output values does not match the expected number of
164    ///   outputs required by the called function.
165    pub fn resume<T>(
166        self,
167        mut ctx: impl AsContextMut<UserState = T>,
168        inputs: &[Value],
169        outputs: &mut [Value],
170    ) -> Result<ResumableCall, Error> {
171        self.engine
172            .resolve_func_type(self.host_func().ty_dedup(ctx.as_context()), |func_type| {
173                func_type.match_results(inputs, true)
174            })?;
175        self.engine
176            .resolve_func_type(self.func.ty_dedup(ctx.as_context()), |func_type| {
177                func_type.match_results(outputs, false)?;
178                func_type.prepare_outputs(outputs);
179                <Result<(), Error>>::Ok(()) // TODO: why do we need types here?
180            })?;
181        self.engine
182            .clone()
183            .resume_func(ctx.as_context_mut(), self, inputs, outputs)
184            .map_err(Into::into)
185            .map(ResumableCall::new)
186    }
187}
188
189/// Returned by calling a [`TypedFunc`] in a resumable way.
190///
191/// [`TypedFunc`]: [`crate::TypedFunc`]
192#[derive(Debug)]
193pub enum TypedResumableCall<T> {
194    /// The resumable call has finished properly and returned a result.
195    Finished(T),
196    /// The resumable call encountered a host error and can be resumed.
197    Resumable(TypedResumableInvocation<T>),
198}
199
200impl<Results> TypedResumableCall<Results> {
201    /// Creates a [`TypedResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`].
202    pub(crate) fn new(call: ResumableCallBase<Results>) -> Self {
203        match call {
204            ResumableCallBase::Finished(results) => Self::Finished(results),
205            ResumableCallBase::Resumable(invocation) => {
206                Self::Resumable(TypedResumableInvocation::new(invocation))
207            }
208        }
209    }
210}
211
212/// State required to resume a [`TypedFunc`] invocation.
213///
214/// [`TypedFunc`]: [`crate::TypedFunc`]
215pub struct TypedResumableInvocation<Results> {
216    invocation: ResumableInvocation,
217    /// The parameter and result typed encoded in Rust type system.
218    results: PhantomData<fn() -> Results>,
219}
220
221impl<Results> TypedResumableInvocation<Results> {
222    /// Creates a [`TypedResumableInvocation`] wrapper for the given [`ResumableInvocation`].
223    pub(crate) fn new(invocation: ResumableInvocation) -> Self {
224        Self {
225            invocation,
226            results: PhantomData,
227        }
228    }
229
230    /// Resumes the call to the [`TypedFunc`] with the given inputs.
231    ///
232    /// Returns a resumable handle to the function invocation upon
233    /// enountering host errors with which it is possible to handle
234    /// the error and continue the execution as if no error occured.
235    ///
236    /// # Errors
237    ///
238    /// - If the function resumption returned a Wasm [`Trap`].
239    /// - If the types or the number of values in `inputs` does not match
240    ///   the types and number of result values of the errorneous host function.
241    ///
242    /// [`TypedFunc`]: [`crate::TypedFunc`]
243    pub fn resume<T>(
244        self,
245        mut ctx: impl AsContextMut<UserState = T>,
246        inputs: &[Value],
247    ) -> Result<TypedResumableCall<Results>, Error>
248    where
249        Results: WasmResults,
250    {
251        self.engine
252            .resolve_func_type(self.host_func().ty_dedup(ctx.as_context()), |func_type| {
253                func_type.match_results(inputs, true)
254            })?;
255        self.engine
256            .clone()
257            .resume_func(
258                ctx.as_context_mut(),
259                self.invocation,
260                inputs,
261                <CallResultsTuple<Results>>::default(),
262            )
263            .map_err(Into::into)
264            .map(TypedResumableCall::new)
265    }
266}
267
268impl<Results> Deref for TypedResumableInvocation<Results> {
269    type Target = ResumableInvocation;
270
271    fn deref(&self) -> &Self::Target {
272        &self.invocation
273    }
274}
275
276impl<Results> fmt::Debug for TypedResumableInvocation<Results> {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        f.debug_struct("TypedResumableInvocation")
279            .field("invocation", &self.invocation)
280            .field("results", &self.results)
281            .finish()
282    }
283}