Skip to content

Commit

Permalink
Resumable function invocation (#110)
Browse files Browse the repository at this point in the history
* Move call_stack to Interpreter struct

* Accept func and args when creating the Interpreter

* Create a RunState to indicate whether the current interpreter is recoverable

* Add functionality to resume execution in Interpreter level

* Implement resumable execution in func

* Expose FuncInvocation and ResumableError

* Fix missing docs for FuncInvocation

* Add test for resumable invoke and move external parameter passing to start/resume_invocation

* Add comments why assert is always true

* Add note why value stack is always empty after execution

* Use as_func

* Document `resume_execution` on conditions for `is_resumable` and `resumable_value_type`

* Document conditions where NotResumable and AlreadyStarted error is returned

* Warn user that invoke_resumable is experimental
  • Loading branch information
sorpaas authored and pepyakin committed Jul 9, 2018
1 parent df0e3dd commit a605175
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 31 deletions.
156 changes: 153 additions & 3 deletions src/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ use std::fmt;
use parity_wasm::elements::Local;
use {Trap, TrapKind, Signature};
use host::Externals;
use runner::{check_function_args, Interpreter};
use runner::{check_function_args, Interpreter, InterpreterState};
use value::RuntimeValue;
use types::ValueType;
use module::ModuleInstance;
use isa;

Expand Down Expand Up @@ -144,15 +145,164 @@ impl FuncInstance {
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?;
match *func.as_internal() {
FuncInstanceInternal::Internal { .. } => {
let mut interpreter = Interpreter::new(externals);
interpreter.start_execution(func, args)
let mut interpreter = Interpreter::new(func, args)?;
interpreter.start_execution(externals)
}
FuncInstanceInternal::Host {
ref host_func_index,
..
} => externals.invoke_index(*host_func_index, args.into()),
}
}

/// Invoke the function, get a resumable handle. This handle can then be used to [`start_execution`]. If a
/// Host trap happens, caller can use [`resume_execution`] to feed the expected return value back in, and then
/// continue the execution.
///
/// This is an experimental API, and this functionality may not be available in other WebAssembly engines.
///
/// # Errors
///
/// Returns `Err` if `args` types is not match function [`signature`].
///
/// [`signature`]: #method.signature
/// [`Trap`]: #enum.Trap.html
/// [`start_execution`]: struct.FuncInvocation.html#method.start_execution
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
pub fn invoke_resumable<'args>(
func: &FuncRef,
args: &'args [RuntimeValue],
) -> Result<FuncInvocation<'args>, Trap> {
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?;
match *func.as_internal() {
FuncInstanceInternal::Internal { .. } => {
let interpreter = Interpreter::new(func, args)?;
Ok(FuncInvocation {
kind: FuncInvocationKind::Internal(interpreter),
})
}
FuncInstanceInternal::Host {
ref host_func_index,
..
} => {
Ok(FuncInvocation {
kind: FuncInvocationKind::Host {
args,
host_func_index: *host_func_index,
finished: false,
},
})
},
}
}
}

/// A resumable invocation error.
#[derive(Debug)]
pub enum ResumableError {
/// Trap happened.
Trap(Trap),
/// The invocation is not resumable.
///
/// Invocations are only resumable if a host function is called, and the host function returns a trap of `Host` kind. For other cases, this error will be returned. This includes:
/// - The invocation is directly a host function.
/// - The invocation has not been started.
/// - The invocation returns normally or returns any trap other than `Host` kind.
///
/// This error is returned by [`resume_execution`].
///
/// [`resume_execution`]: struct.FuncInvocation.html#method.resume_execution
NotResumable,
/// The invocation has already been started.
///
/// This error is returned by [`start_execution`].
///
/// [`start_execution`]: struct.FuncInvocation.html#method.start_execution
AlreadyStarted,
}

impl From<Trap> for ResumableError {
fn from(trap: Trap) -> Self {
ResumableError::Trap(trap)
}
}

/// A resumable invocation handle. This struct is returned by `FuncInstance::invoke_resumable`.
pub struct FuncInvocation<'args> {
kind: FuncInvocationKind<'args>,
}

enum FuncInvocationKind<'args> {
Internal(Interpreter),
Host {
args: &'args [RuntimeValue],
host_func_index: usize,
finished: bool
},
}

impl<'args> FuncInvocation<'args> {
/// Whether this invocation is currently resumable.
pub fn is_resumable(&self) -> bool {
match &self.kind {
&FuncInvocationKind::Internal(ref interpreter) => interpreter.state().is_resumable(),
&FuncInvocationKind::Host { .. } => false,
}
}

/// If the invocation is resumable, the expected return value type to be feed back in.
pub fn resumable_value_type(&self) -> Option<ValueType> {
match &self.kind {
&FuncInvocationKind::Internal(ref interpreter) => {
match interpreter.state() {
&InterpreterState::Resumable(ref value_type) => value_type.clone(),
_ => None,
}
},
&FuncInvocationKind::Host { .. } => None,
}
}

/// Start the invocation execution.
pub fn start_execution<'externals, E: Externals + 'externals>(&mut self, externals: &'externals mut E) -> Result<Option<RuntimeValue>, ResumableError> {
match self.kind {
FuncInvocationKind::Internal(ref mut interpreter) => {
if interpreter.state() != &InterpreterState::Initialized {
return Err(ResumableError::AlreadyStarted);
}
Ok(interpreter.start_execution(externals)?)
},
FuncInvocationKind::Host { ref args, ref mut finished, ref host_func_index } => {
if *finished {
return Err(ResumableError::AlreadyStarted);
}
*finished = true;
Ok(externals.invoke_index(*host_func_index, args.clone().into())?)
},
}
}

/// Resume an execution if a previous trap of Host kind happened.
///
/// `return_val` must be of the value type [`resumable_value_type`], defined by the host function import. Otherwise,
/// `UnexpectedSignature` trap will be returned. The current invocation must also be resumable
/// [`is_resumable`]. Otherwise, a `NotResumable` error will be returned.
///
/// [`resumable_value_type`]: #method.resumable_value_type
/// [`is_resumable`]: #method.is_resumable
pub fn resume_execution<'externals, E: Externals + 'externals>(&mut self, return_val: Option<RuntimeValue>, externals: &'externals mut E) -> Result<Option<RuntimeValue>, ResumableError> {
match self.kind {
FuncInvocationKind::Internal(ref mut interpreter) => {
if !interpreter.state().is_resumable() {
return Err(ResumableError::AlreadyStarted);
}
Ok(interpreter.resume_execution(return_val, externals)?)
},
FuncInvocationKind::Host { .. } => {
return Err(ResumableError::NotResumable);
},
}
}
}

#[derive(Clone, Debug)]
Expand Down
12 changes: 11 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,16 @@ pub enum TrapKind {
Host(Box<host::HostError>),
}

impl TrapKind {
/// Whether this trap is specified by the host.
pub fn is_host(&self) -> bool {
match self {
&TrapKind::Host(_) => true,
_ => false,
}
}
}

/// Internal interpreter error.
#[derive(Debug)]
pub enum Error {
Expand Down Expand Up @@ -368,7 +378,7 @@ pub use self::host::{Externals, NopExternals, HostError, RuntimeArgs};
pub use self::imports::{ModuleImportResolver, ImportResolver, ImportsBuilder};
pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef};
pub use self::global::{GlobalInstance, GlobalRef};
pub use self::func::{FuncInstance, FuncRef};
pub use self::func::{FuncInstance, FuncRef, FuncInvocation, ResumableError};
pub use self::types::{Signature, ValueType, GlobalDescriptor, TableDescriptor, MemoryDescriptor};

/// WebAssembly-specific sizes and units.
Expand Down
Loading

0 comments on commit a605175

Please sign in to comment.