soroban_wasmi/engine/
resumable.rs

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