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}