wasmi/engine/
resumable.rs

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