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}