Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement resumable calls for TypedFunc #605

Merged
merged 2 commits into from
Jan 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions crates/wasmi/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub use self::{
RelativeDepth,
TranslationError,
},
resumable::{ResumableCall, ResumableInvocation},
resumable::{ResumableCall, ResumableInvocation, TypedResumableCall, TypedResumableInvocation},
stack::StackLimits,
traits::{CallParams, CallResults},
};
Expand All @@ -36,6 +36,7 @@ use self::{
code_map::CodeMap,
executor::execute_frame,
func_types::FuncTypeRegistry,
resumable::ResumableCallBase,
stack::{FuncFrame, Stack, ValueStack},
};
pub(crate) use self::{
Expand Down Expand Up @@ -241,7 +242,7 @@ impl Engine {
func: Func,
params: impl CallParams,
results: Results,
) -> Result<ResumableCall<<Results as CallResults>::Results>, Trap>
) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
where
Results: CallResults,
{
Expand Down Expand Up @@ -277,7 +278,7 @@ impl Engine {
invocation: ResumableInvocation,
params: impl CallParams,
results: Results,
) -> Result<ResumableCall<<Results as CallResults>::Results>, Trap>
) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
where
Results: CallResults,
{
Expand Down Expand Up @@ -411,7 +412,7 @@ impl EngineInner {
func: Func,
params: impl CallParams,
results: Results,
) -> Result<ResumableCall<<Results as CallResults>::Results>, Trap>
) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
where
Results: CallResults,
{
Expand All @@ -426,7 +427,7 @@ impl EngineInner {
match results {
Ok(results) => {
self.stacks.lock().recycle(stack);
Ok(ResumableCall::Finished(results))
Ok(ResumableCallBase::Finished(results))
}
Err(TaggedTrap::Wasm(trap)) => {
self.stacks.lock().recycle(stack);
Expand All @@ -435,7 +436,7 @@ impl EngineInner {
Err(TaggedTrap::Host {
host_func,
host_trap,
}) => Ok(ResumableCall::Resumable(ResumableInvocation::new(
}) => Ok(ResumableCallBase::Resumable(ResumableInvocation::new(
ctx.as_context().store.engine().clone(),
func,
host_func,
Expand All @@ -451,7 +452,7 @@ impl EngineInner {
mut invocation: ResumableInvocation,
params: impl CallParams,
results: Results,
) -> Result<ResumableCall<<Results as CallResults>::Results>, Trap>
) -> Result<ResumableCallBase<<Results as CallResults>::Results>, Trap>
where
Results: CallResults,
{
Expand All @@ -462,7 +463,7 @@ impl EngineInner {
match results {
Ok(results) => {
self.stacks.lock().recycle(invocation.take_stack());
Ok(ResumableCall::Finished(results))
Ok(ResumableCallBase::Finished(results))
}
Err(TaggedTrap::Wasm(trap)) => {
self.stacks.lock().recycle(invocation.take_stack());
Expand All @@ -473,7 +474,7 @@ impl EngineInner {
host_trap,
}) => {
invocation.update(host_func, host_trap);
Ok(ResumableCall::Resumable(invocation))
Ok(ResumableCallBase::Resumable(invocation))
}
}
}
Expand Down
140 changes: 132 additions & 8 deletions crates/wasmi/src/engine/resumable.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,45 @@
use super::Func;
use crate::{engine::Stack, AsContextMut, Engine, Error};
use core::mem::replace;
use crate::{engine::Stack, func::CallResultsTuple, AsContextMut, Engine, Error, WasmResults};
use core::{fmt, marker::PhantomData, mem::replace, ops::Deref};
use wasmi_core::{Trap, Value};

/// Returned by calling a function in a resumable way.
/// Returned by [`Engine`] methods for calling a function in a resumable way.
///
/// # Note
///
/// This is the base type for resumable call results and can be converted into
/// either the dynamically typed [`ResumableCall`] or the statically typed
/// [`TypedResumableCall`] that act as user facing API. Therefore this type
/// must provide all the information necessary to be properly converted into
/// either user facing types.
#[derive(Debug)]
pub enum ResumableCall<T> {
pub(crate) enum ResumableCallBase<T> {
/// The resumable call has finished properly and returned a result.
Finished(T),
/// The resumable call encountered a host error and can be resumed.
Resumable(ResumableInvocation),
}

/// State required to resume a function invocation.
/// Returned by calling a [`Func`] in a resumable way.
#[derive(Debug)]
pub enum ResumableCall {
/// The resumable call has finished properly and returned a result.
Finished,
/// The resumable call encountered a host error and can be resumed.
Resumable(ResumableInvocation),
}

impl ResumableCall {
/// Creates a [`ResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`].
pub(crate) fn new(call: ResumableCallBase<()>) -> Self {
match call {
ResumableCallBase::Finished(()) => Self::Finished,
ResumableCallBase::Resumable(invocation) => Self::Resumable(invocation),
}
}
}

/// State required to resume a [`Func`] invocation.
#[derive(Debug)]
pub struct ResumableInvocation {
/// The engine in use for the function invokation.
Expand Down Expand Up @@ -120,9 +147,9 @@ impl ResumableInvocation {
&self.host_error
}

/// Resumes the call to the Wasm or host function with the given inputs.
/// Resumes the call to the [`Func`] with the given inputs.
///
/// The result is written back into the `outputs` buffer.
/// The result is written back into the `outputs` buffer upon success.
///
/// Returns a resumable handle to the function invocation upon
/// enountering host errors with which it is possible to handle
Expand All @@ -140,7 +167,7 @@ impl ResumableInvocation {
mut ctx: impl AsContextMut<UserState = T>,
inputs: &[Value],
outputs: &mut [Value],
) -> Result<ResumableCall<()>, Error> {
) -> Result<ResumableCall, Error> {
self.engine
.resolve_func_type(self.host_func().signature(ctx.as_context()), |func_type| {
func_type.match_results(inputs, true)
Expand All @@ -155,5 +182,102 @@ impl ResumableInvocation {
.clone()
.resume_func(ctx.as_context_mut(), self, inputs, outputs)
.map_err(Into::into)
.map(ResumableCall::new)
}
}

/// Returned by calling a [`TypedFunc`] in a resumable way.
///
/// [`TypedFunc`]: [`crate::TypedFunc`]
#[derive(Debug)]
pub enum TypedResumableCall<T> {
/// The resumable call has finished properly and returned a result.
Finished(T),
/// The resumable call encountered a host error and can be resumed.
Resumable(TypedResumableInvocation<T>),
}

impl<Results> TypedResumableCall<Results> {
/// Creates a [`TypedResumableCall`] from the [`Engine`]'s base [`ResumableCallBase`].
pub(crate) fn new(call: ResumableCallBase<Results>) -> Self {
match call {
ResumableCallBase::Finished(results) => Self::Finished(results),
ResumableCallBase::Resumable(invocation) => {
Self::Resumable(TypedResumableInvocation::new(invocation))
}
}
}
}

/// State required to resume a [`TypedFunc`] invocation.
///
/// [`TypedFunc`]: [`crate::TypedFunc`]
pub struct TypedResumableInvocation<Results> {
invocation: ResumableInvocation,
/// The parameter and result typed encoded in Rust type system.
results: PhantomData<fn() -> Results>,
}

impl<Results> TypedResumableInvocation<Results> {
/// Creates a [`TypedResumableInvocation`] wrapper for the given [`ResumableInvocation`].
pub(crate) fn new(invocation: ResumableInvocation) -> Self {
Self {
invocation,
results: PhantomData,
}
}

/// Resumes the call to the [`TypedFunc`] with the given inputs.
///
/// Returns a resumable handle to the function invocation upon
/// enountering host errors with which it is possible to handle
/// the error and continue the execution as if no error occured.
///
/// # Errors
///
/// - If the function resumption returned a Wasm [`Trap`].
/// - If the types or the number of values in `inputs` does not match
/// the types and number of result values of the errorneous host function.
///
/// [`TypedFunc`]: [`crate::TypedFunc`]
pub fn resume<T>(
self,
mut ctx: impl AsContextMut<UserState = T>,
inputs: &[Value],
) -> Result<TypedResumableCall<Results>, Error>
where
Results: WasmResults,
{
self.engine
.resolve_func_type(self.host_func().signature(ctx.as_context()), |func_type| {
func_type.match_results(inputs, true)
})?;
self.engine
.clone()
.resume_func(
ctx.as_context_mut(),
self.invocation,
inputs,
<CallResultsTuple<Results>>::default(),
)
.map_err(Into::into)
.map(TypedResumableCall::new)
}
}

impl<Results> Deref for TypedResumableInvocation<Results> {
type Target = ResumableInvocation;

fn deref(&self) -> &Self::Target {
&self.invocation
}
}

impl<Results> fmt::Debug for TypedResumableInvocation<Results> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TypedResumableInvocation")
.field("invocation", &self.invocation)
.field("results", &self.results)
.finish()
}
}
4 changes: 3 additions & 1 deletion crates/wasmi/src/func/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod error;
mod into_func;
mod typed_func;

pub(crate) use self::typed_func::CallResultsTuple;
pub use self::{
caller::Caller,
error::FuncError,
Expand Down Expand Up @@ -328,7 +329,7 @@ impl Func {
mut ctx: impl AsContextMut<UserState = T>,
inputs: &[Value],
outputs: &mut [Value],
) -> Result<ResumableCall<()>, Error> {
) -> Result<ResumableCall, Error> {
self.verify_and_prepare_inputs_outputs(ctx.as_context(), inputs, outputs)?;
// Note: Cloning an [`Engine`] is intentionally a cheap operation.
ctx.as_context()
Expand All @@ -337,6 +338,7 @@ impl Func {
.clone()
.execute_func_resumable(ctx.as_context_mut(), *self, inputs, outputs)
.map_err(Into::into)
.map(ResumableCall::new)
}

/// Verify that the `inputs` and `outputs` value types match the function signature.
Expand Down
37 changes: 36 additions & 1 deletion crates/wasmi/src/func/typed_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::{
AsContext,
AsContextMut,
Error,
TypedResumableCall,
};
use core::{fmt, fmt::Debug, marker::PhantomData};
use wasmi_core::{Trap, UntypedValue};
Expand Down Expand Up @@ -78,7 +79,7 @@ where
})
}

/// Invokes this Wasm or host function with the specified parameters.
/// Calls this Wasm or host function with the specified parameters.
///
/// Returns either the results of the call, or a [`Trap`] if one happened.
///
Expand All @@ -101,6 +102,40 @@ where
<CallResultsTuple<Results>>::default(),
)
}

/// Calls this Wasm or host function with the specified parameters.
///
/// Returns a resumable handle to the function invocation upon
/// enountering host errors with which it is possible to handle
/// the error and continue the execution as if no error occured.
///
/// # Note
///
/// This is a non-standard WebAssembly API and might not be available
/// at other WebAssembly engines. Please be aware that depending on this
/// feature might mean a lock-in to `wasmi` for users.
///
/// # Errors
///
/// If the function returned a [`Trap`] originating from WebAssembly.
pub fn call_resumable(
&self,
mut ctx: impl AsContextMut,
params: Params,
) -> Result<TypedResumableCall<Results>, Trap> {
// Note: Cloning an [`Engine`] is intentionally a cheap operation.
ctx.as_context()
.store
.engine()
.clone()
.execute_func_resumable(
ctx.as_context_mut(),
self.func,
params,
<CallResultsTuple<Results>>::default(),
)
.map(TypedResumableCall::new)
}
}

impl<Params> CallParams for Params
Expand Down
10 changes: 9 additions & 1 deletion crates/wasmi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,15 @@ pub mod errors {
}

pub use self::{
engine::{Config, Engine, ResumableCall, ResumableInvocation, StackLimits},
engine::{
Config,
Engine,
ResumableCall,
ResumableInvocation,
StackLimits,
TypedResumableCall,
TypedResumableInvocation,
},
error::Error,
external::Extern,
func::{Caller, Func, IntoFunc, TypedFunc, WasmParams, WasmResults, WasmRet, WasmType},
Expand Down
Loading