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}