wasmi/engine/resumable.rs
1use super::Func;
2use crate::{
3 engine::Stack,
4 func::{CallResultsTuple, FuncError},
5 ir::SlotSpan,
6 AsContext,
7 AsContextMut,
8 Engine,
9 Error,
10 TrapCode,
11 Val,
12 WasmResults,
13};
14use core::{fmt, marker::PhantomData, mem::replace, ops::Deref};
15
16/// Returned by [`Engine`] methods for calling a function in a resumable way.
17///
18/// # Note
19///
20/// This is the base type for resumable call results and can be converted into
21/// either the dynamically typed [`ResumableCall`] or the statically typed
22/// [`TypedResumableCall`] that act as user facing API. Therefore this type
23/// must provide all the information necessary to be properly converted into
24/// either user facing types.
25#[derive(Debug)]
26pub(crate) enum ResumableCallBase<T> {
27 /// The resumable call has finished properly and returned a result.
28 Finished(T),
29 /// The resumable call encountered a host error and can be resumed.
30 HostTrap(ResumableCallHostTrap),
31 /// The resumable call ran out of fuel and can be resumed.
32 OutOfFuel(ResumableCallOutOfFuel),
33}
34
35/// Any resumable error.
36#[derive(Debug)]
37pub enum ResumableError {
38 HostTrap(ResumableHostTrapError),
39 OutOfFuel(ResumableOutOfFuelError),
40}
41
42impl ResumableError {
43 /// Consumes `self` to return the underlying [`Error`].
44 pub fn into_error(self) -> Error {
45 match self {
46 ResumableError::HostTrap(error) => error.into_error(),
47 ResumableError::OutOfFuel(error) => error.into_error(),
48 }
49 }
50}
51
52/// Error returned from a called host function in a resumable state.
53#[derive(Debug)]
54pub struct ResumableHostTrapError {
55 /// The error returned by the called host function.
56 host_error: Error,
57 /// The host function that returned the error.
58 host_func: Func,
59 /// The result registers of the caller of the host function.
60 caller_results: SlotSpan,
61}
62
63impl core::error::Error for ResumableHostTrapError {}
64
65impl fmt::Display for ResumableHostTrapError {
66 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
67 self.host_error.fmt(f)
68 }
69}
70
71impl ResumableHostTrapError {
72 /// Creates a new [`ResumableHostTrapError`].
73 #[cold]
74 pub(crate) fn new(host_error: Error, host_func: Func, caller_results: SlotSpan) -> Self {
75 Self {
76 host_error,
77 host_func,
78 caller_results,
79 }
80 }
81
82 /// Consumes `self` to return the underlying [`Error`].
83 pub(crate) fn into_error(self) -> Error {
84 self.host_error
85 }
86
87 /// Returns the [`Func`] of the [`ResumableHostTrapError`].
88 pub(crate) fn host_func(&self) -> &Func {
89 &self.host_func
90 }
91
92 /// Returns the caller results [`SlotSpan`] of the [`ResumableHostTrapError`].
93 pub(crate) fn caller_results(&self) -> &SlotSpan {
94 &self.caller_results
95 }
96}
97
98/// Error returned from a called host function in a resumable state.
99#[derive(Debug)]
100pub struct ResumableOutOfFuelError {
101 /// The minimum required amount of fuel to progress execution.
102 required_fuel: u64,
103}
104
105impl core::error::Error for ResumableOutOfFuelError {}
106
107impl fmt::Display for ResumableOutOfFuelError {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(
110 f,
111 "ran out of fuel while calling a resumable function: required_fuel={}",
112 self.required_fuel
113 )
114 }
115}
116
117impl ResumableOutOfFuelError {
118 /// Creates a new [`ResumableOutOfFuelError`].
119 #[cold]
120 pub(crate) fn new(required_fuel: u64) -> Self {
121 Self { required_fuel }
122 }
123
124 /// Consumes `self` to return the underlying [`Error`].
125 pub(crate) fn required_fuel(self) -> u64 {
126 self.required_fuel
127 }
128
129 /// Consumes `self` to return the underlying [`Error`].
130 pub(crate) fn into_error(self) -> Error {
131 Error::from(TrapCode::OutOfFuel)
132 }
133}
134
135/// Returned by calling a [`Func`] in a resumable way.
136#[derive(Debug)]
137pub enum ResumableCall {
138 /// The resumable call has finished properly and returned a result.
139 Finished,
140 /// The resumable call encountered a host error and can be resumed.
141 HostTrap(ResumableCallHostTrap),
142 /// The resumable call ran out of fuel but can be resumed.
143 OutOfFuel(ResumableCallOutOfFuel),
144}
145
146impl ResumableCall {
147 /// Creates a [`ResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`].
148 pub(crate) fn new(call: ResumableCallBase<()>) -> Self {
149 match call {
150 ResumableCallBase::Finished(()) => Self::Finished,
151 ResumableCallBase::HostTrap(invocation) => Self::HostTrap(invocation),
152 ResumableCallBase::OutOfFuel(invocation) => Self::OutOfFuel(invocation),
153 }
154 }
155}
156
157/// Common state for resumable calls.
158#[derive(Debug)]
159pub struct ResumableCallCommon {
160 /// The engine in use for the function invocation.
161 ///
162 /// # Note
163 ///
164 /// - This handle is required to resolve function types
165 /// of both `func` and `host_func` fields as well as in
166 /// the `Drop` impl to recycle the stack.
167 engine: Engine,
168 /// The underlying root function to be executed.
169 ///
170 /// # Note
171 ///
172 /// The results of this function must always match with the
173 /// results given when resuming the call.
174 func: Func,
175 /// The value and call stack in use by the [`ResumableCallHostTrap`].
176 ///
177 /// # Note
178 ///
179 /// - We need to keep the stack around since the user might want to
180 /// resume the execution.
181 /// - This stack is borrowed from the engine and needs to be given
182 /// back to the engine when the [`ResumableCallHostTrap`] goes out
183 /// of scope.
184 stack: Stack,
185}
186
187impl ResumableCallCommon {
188 /// Creates a new [`ResumableCallCommon`].
189 pub(super) fn new(engine: Engine, func: Func, stack: Stack) -> Self {
190 Self {
191 engine,
192 func,
193 stack,
194 }
195 }
196
197 /// Replaces the internal stack with an empty one that has no heap allocations.
198 pub(super) fn take_stack(&mut self) -> Stack {
199 replace(&mut self.stack, Stack::empty())
200 }
201
202 /// Returns an exclusive reference to the underlying [`Stack`].
203 pub(super) fn stack_mut(&mut self) -> &mut Stack {
204 &mut self.stack
205 }
206
207 /// Prepares the `outputs` buffer for call resumption.
208 ///
209 /// # Errors
210 ///
211 /// If the number of items in `outputs` does not match the number of results of the resumed function.
212 fn prepare_outputs<T>(
213 &self,
214 ctx: impl AsContext<Data = T>,
215 outputs: &mut [Val],
216 ) -> Result<(), Error> {
217 self.engine.resolve_func_type(
218 self.func.ty_dedup(ctx.as_context()),
219 |func_type| -> Result<(), Error> {
220 func_type.prepare_outputs(outputs)?;
221 Ok(())
222 },
223 )
224 }
225}
226
227impl Drop for ResumableCallCommon {
228 fn drop(&mut self) {
229 let stack = self.take_stack();
230 self.engine.recycle_stack(stack);
231 }
232}
233
234// # Safety
235//
236// `ResumableCallCommon` is not `Sync` because of the following sequence of fields:
237//
238// - `ResumableCallCommon`'s `Stack` is not `Sync`
239// - `Stack`'s `CallStack` is not `Sync`
240// - `CallStack`'s `CallFrame` sequence is not `Sync`
241// - `CallFrame`'s `InstructionPtr` is not `Sync`:
242// Thi is because it is a raw pointer to an `Op` buffer owned by the [`Engine`].
243//
244// Since `Engine` is owned by `ResumableCallCommon` it cannot be outlived.
245// Also the `Op` buffers that are pointed to by the `InstructionPtr` are immutable.
246//
247// Therefore `ResumableCallCommon` can safely be assumed to be `Sync`.
248unsafe impl Sync for ResumableCallCommon {}
249
250/// State required to resume a [`Func`] invocation after a host trap.
251#[derive(Debug)]
252pub struct ResumableCallHostTrap {
253 /// Common state for resumable calls.
254 pub(super) common: ResumableCallCommon,
255 /// The host function that returned a host error.
256 ///
257 /// # Note
258 ///
259 /// - This is required to receive its result values that are
260 /// needed to be fed back in manually by the user. This way we
261 /// avoid heap memory allocations.
262 /// - The results of this function must always match with the
263 /// arguments given when resuming the call.
264 host_func: Func,
265 /// The host error that was returned by the `host_func` which
266 /// caused the resumable function invocation to break.
267 ///
268 /// # Note
269 ///
270 /// This might be useful to users of this API to inspect the
271 /// actual host error. This is therefore guaranteed to never
272 /// be a Wasm trap.
273 host_error: Error,
274 /// The registers where to put provided host function results upon resumption.
275 ///
276 /// # Note
277 ///
278 /// This is only needed for the register-machine Wasmi engine backend.
279 caller_results: SlotSpan,
280}
281
282impl ResumableCallHostTrap {
283 /// Creates a new [`ResumableCallHostTrap`].
284 pub(super) fn new(
285 engine: Engine,
286 func: Func,
287 host_func: Func,
288 host_error: Error,
289 caller_results: SlotSpan,
290 stack: Stack,
291 ) -> Self {
292 Self {
293 common: ResumableCallCommon::new(engine, func, stack),
294 host_func,
295 host_error,
296 caller_results,
297 }
298 }
299
300 /// Updates the [`ResumableCallHostTrap`] with the new `host_func`, `host_error` and `caller_results`.
301 ///
302 /// # Note
303 ///
304 /// This should only be called from the register-machine Wasmi engine backend.
305 pub(super) fn update(&mut self, host_func: Func, host_error: Error, caller_results: SlotSpan) {
306 self.host_func = host_func;
307 self.host_error = host_error;
308 self.caller_results = caller_results;
309 }
310
311 /// Updates the [`ResumableCallHostTrap`] to a [`ResumableCallOutOfFuel`].
312 pub(super) fn update_to_out_of_fuel(self, required_fuel: u64) -> ResumableCallOutOfFuel {
313 ResumableCallOutOfFuel {
314 common: self.common,
315 required_fuel,
316 }
317 }
318
319 /// Returns the host [`Func`] that returned the host error.
320 ///
321 /// # Note
322 ///
323 /// When using [`ResumableCallHostTrap::resume`] the `inputs`
324 /// need to match the results of this host function so that
325 /// the function invocation can properly resume. For that
326 /// number and types of the values provided must match.
327 pub fn host_func(&self) -> Func {
328 self.host_func
329 }
330
331 /// Returns a shared reference to the encountered host error.
332 ///
333 /// # Note
334 ///
335 /// This is guaranteed to never be a Wasm trap.
336 pub fn host_error(&self) -> &Error {
337 &self.host_error
338 }
339
340 /// Consumes `self` and returns the encountered host error.
341 ///
342 /// # Note
343 ///
344 /// This is guaranteed to never be a Wasm trap.
345 pub fn into_host_error(self) -> Error {
346 self.host_error
347 }
348
349 /// Returns the caller results [`SlotSpan`].
350 ///
351 /// # Note
352 ///
353 /// This is `Some` only for [`ResumableCallHostTrap`] originating from the register-machine Wasmi engine.
354 pub(crate) fn caller_results(&self) -> SlotSpan {
355 self.caller_results
356 }
357
358 /// Validates that the `inputs` types are valid for the host function resumption.
359 ///
360 /// # Errors
361 ///
362 /// If the `inputs` types do not match.
363 fn validate_inputs<T>(
364 &self,
365 ctx: impl AsContext<Data = T>,
366 inputs: &[Val],
367 ) -> Result<(), FuncError> {
368 self.common
369 .engine
370 .resolve_func_type(self.host_func().ty_dedup(ctx.as_context()), |func_type| {
371 func_type.match_results(inputs)
372 })
373 }
374
375 /// Resumes the call to the [`Func`] with the given inputs.
376 ///
377 /// The result is written back into the `outputs` buffer upon success.
378 ///
379 /// Returns a resumable handle to the function invocation upon
380 /// encountering host errors with which it is possible to handle
381 /// the error and continue the execution as if no error occurred.
382 ///
383 /// # Errors
384 ///
385 /// - If the function resumption returned a Wasm [`Error`].
386 /// - If the types or the number of values in `inputs` does not match
387 /// the types and number of result values of the erroneous host function.
388 /// - If the number of output values does not match the expected number of
389 /// outputs required by the called function.
390 pub fn resume<T>(
391 self,
392 mut ctx: impl AsContextMut<Data = T>,
393 inputs: &[Val],
394 outputs: &mut [Val],
395 ) -> Result<ResumableCall, Error> {
396 self.validate_inputs(ctx.as_context(), inputs)?;
397 self.common.prepare_outputs(ctx.as_context(), outputs)?;
398 self.common
399 .engine
400 .clone()
401 .resume_func_host_trap(ctx.as_context_mut(), self, inputs, outputs)
402 .map(ResumableCall::new)
403 }
404}
405
406/// State required to resume a [`Func`] invocation after a host trap.
407#[derive(Debug)]
408pub struct ResumableCallOutOfFuel {
409 /// Common state for resumable calls.
410 pub(super) common: ResumableCallCommon,
411 /// The minimum fuel required to progress execution.
412 required_fuel: u64,
413}
414
415impl ResumableCallOutOfFuel {
416 /// Creates a new [`ResumableCallOutOfFuel`].
417 pub(super) fn new(engine: Engine, func: Func, stack: Stack, required_fuel: u64) -> Self {
418 Self {
419 common: ResumableCallCommon::new(engine, func, stack),
420 required_fuel,
421 }
422 }
423
424 /// Updates the [`ResumableCallOutOfFuel`] with the new `required_fuel`.
425 ///
426 /// # Note
427 ///
428 /// This should only be called from the register-machine Wasmi engine backend.
429 pub(super) fn update(&mut self, required_fuel: u64) {
430 self.required_fuel = required_fuel;
431 }
432
433 /// Updates the [`ResumableCallHostTrap`] to a [`ResumableCallOutOfFuel`].
434 pub(super) fn update_to_host_trap(
435 self,
436 host_func: Func,
437 host_error: Error,
438 caller_results: SlotSpan,
439 ) -> ResumableCallHostTrap {
440 ResumableCallHostTrap {
441 common: self.common,
442 host_func,
443 host_error,
444 caller_results,
445 }
446 }
447
448 /// Returns the minimum required fuel to progress execution.
449 pub fn required_fuel(&self) -> u64 {
450 self.required_fuel
451 }
452
453 /// Resumes the call to the [`Func`] with the given inputs.
454 ///
455 /// The result is written back into the `outputs` buffer upon success.
456 /// Returns a resumable handle to the function invocation.
457 ///
458 /// # Errors
459 ///
460 /// - If the function resumption returned a Wasm [`Error`].
461 /// - If the types or the number of values in `inputs` does not match
462 /// the types and number of result values of the erroneous host function.
463 /// - If the number of output values does not match the expected number of
464 /// outputs required by the called function.
465 pub fn resume<T>(
466 self,
467 mut ctx: impl AsContextMut<Data = T>,
468 outputs: &mut [Val],
469 ) -> Result<ResumableCall, Error> {
470 self.common.prepare_outputs(ctx.as_context(), outputs)?;
471 self.common
472 .engine
473 .clone()
474 .resume_func_out_of_fuel(ctx.as_context_mut(), self, outputs)
475 .map(ResumableCall::new)
476 }
477}
478
479/// Returned by calling a [`TypedFunc`] in a resumable way.
480///
481/// [`TypedFunc`]: [`crate::TypedFunc`]
482#[derive(Debug)]
483pub enum TypedResumableCall<T> {
484 /// The resumable call has finished properly and returned a result.
485 Finished(T),
486 /// The resumable call encountered a host error and can be resumed.
487 HostTrap(TypedResumableCallHostTrap<T>),
488 /// The resumable call ran out of fuel and can be resumed.
489 OutOfFuel(TypedResumableCallOutOfFuel<T>),
490}
491
492impl<Results> TypedResumableCall<Results> {
493 /// Creates a [`TypedResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`].
494 pub(crate) fn new(call: ResumableCallBase<Results>) -> Self {
495 match call {
496 ResumableCallBase::Finished(results) => Self::Finished(results),
497 ResumableCallBase::HostTrap(invocation) => {
498 Self::HostTrap(TypedResumableCallHostTrap::new(invocation))
499 }
500 ResumableCallBase::OutOfFuel(invocation) => {
501 Self::OutOfFuel(TypedResumableCallOutOfFuel::new(invocation))
502 }
503 }
504 }
505}
506
507/// State required to resume a [`TypedFunc`] invocation.
508///
509/// [`TypedFunc`]: [`crate::TypedFunc`]
510pub struct TypedResumableCallHostTrap<Results> {
511 invocation: ResumableCallHostTrap,
512 /// The parameter and result typed encoded in Rust type system.
513 results: PhantomData<fn() -> Results>,
514}
515
516impl<Results> TypedResumableCallHostTrap<Results> {
517 /// Creates a [`TypedResumableCallHostTrap`] wrapper for the given [`ResumableCallHostTrap`].
518 pub(crate) fn new(invocation: ResumableCallHostTrap) -> Self {
519 Self {
520 invocation,
521 results: PhantomData,
522 }
523 }
524
525 /// Resumes the call to the [`TypedFunc`] with the given inputs.
526 ///
527 /// Returns a resumable handle to the function invocation upon
528 /// encountering host errors with which it is possible to handle
529 /// the error and continue the execution as if no error occurred.
530 ///
531 /// # Errors
532 ///
533 /// - If the function resumption returned a Wasm [`Error`].
534 /// - If the types or the number of values in `inputs` does not match
535 /// the types and number of result values of the erroneous host function.
536 ///
537 /// [`TypedFunc`]: [`crate::TypedFunc`]
538 pub fn resume<T>(
539 self,
540 mut ctx: impl AsContextMut<Data = T>,
541 inputs: &[Val],
542 ) -> Result<TypedResumableCall<Results>, Error>
543 where
544 Results: WasmResults,
545 {
546 self.invocation.validate_inputs(ctx.as_context(), inputs)?;
547 self.common
548 .engine
549 .clone()
550 .resume_func_host_trap(
551 ctx.as_context_mut(),
552 self.invocation,
553 inputs,
554 <CallResultsTuple<Results>>::default(),
555 )
556 .map(TypedResumableCall::new)
557 }
558}
559
560impl<Results> Deref for TypedResumableCallHostTrap<Results> {
561 type Target = ResumableCallHostTrap;
562
563 fn deref(&self) -> &Self::Target {
564 &self.invocation
565 }
566}
567
568impl<Results> fmt::Debug for TypedResumableCallHostTrap<Results> {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 f.debug_struct("TypedResumableCallHostTrap")
571 .field("invocation", &self.invocation)
572 .field("results", &self.results)
573 .finish()
574 }
575}
576
577/// State required to resume a [`TypedFunc`] invocation after running out of fuel.
578///
579/// [`TypedFunc`]: [`crate::TypedFunc`]
580pub struct TypedResumableCallOutOfFuel<Results> {
581 invocation: ResumableCallOutOfFuel,
582 /// The parameter and result typed encoded in Rust type system.
583 results: PhantomData<fn() -> Results>,
584}
585
586impl<Results> TypedResumableCallOutOfFuel<Results> {
587 /// Creates a [`TypedResumableCallHostTrap`] wrapper for the given [`ResumableCallOutOfFuel`].
588 pub(crate) fn new(invocation: ResumableCallOutOfFuel) -> Self {
589 Self {
590 invocation,
591 results: PhantomData,
592 }
593 }
594
595 /// Resumes the call to the [`TypedFunc`] with the given inputs.
596 ///
597 /// Returns a resumable handle to the function invocation upon
598 /// encountering host errors with which it is possible to handle
599 /// the error and continue the execution as if no error occurred.
600 ///
601 /// # Errors
602 ///
603 /// - If the function resumption returned a Wasm [`Error`].
604 /// - If the types or the number of values in `inputs` does not match
605 /// the types and number of result values of the erroneous host function.
606 ///
607 /// [`TypedFunc`]: [`crate::TypedFunc`]
608 pub fn resume<T>(
609 self,
610 mut ctx: impl AsContextMut<Data = T>,
611 ) -> Result<TypedResumableCall<Results>, Error>
612 where
613 Results: WasmResults,
614 {
615 self.common
616 .engine
617 .clone()
618 .resume_func_out_of_fuel(
619 ctx.as_context_mut(),
620 self.invocation,
621 <CallResultsTuple<Results>>::default(),
622 )
623 .map(TypedResumableCall::new)
624 }
625}
626
627impl<Results> Deref for TypedResumableCallOutOfFuel<Results> {
628 type Target = ResumableCallOutOfFuel;
629
630 fn deref(&self) -> &Self::Target {
631 &self.invocation
632 }
633}
634
635impl<Results> fmt::Debug for TypedResumableCallOutOfFuel<Results> {
636 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
637 f.debug_struct("TypedResumableCallOutOfFuel")
638 .field("invocation", &self.invocation)
639 .field("results", &self.results)
640 .finish()
641 }
642}