Resumable function invocation (#110)
* 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
This commit is contained in:
parent
df0e3ddd46
commit
a605175abe
156
src/func.rs
156
src/func.rs
|
@ -3,8 +3,9 @@ use std::fmt;
|
||||||
use parity_wasm::elements::Local;
|
use parity_wasm::elements::Local;
|
||||||
use {Trap, TrapKind, Signature};
|
use {Trap, TrapKind, Signature};
|
||||||
use host::Externals;
|
use host::Externals;
|
||||||
use runner::{check_function_args, Interpreter};
|
use runner::{check_function_args, Interpreter, InterpreterState};
|
||||||
use value::RuntimeValue;
|
use value::RuntimeValue;
|
||||||
|
use types::ValueType;
|
||||||
use module::ModuleInstance;
|
use module::ModuleInstance;
|
||||||
use isa;
|
use isa;
|
||||||
|
|
||||||
|
@ -144,8 +145,8 @@ impl FuncInstance {
|
||||||
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?;
|
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?;
|
||||||
match *func.as_internal() {
|
match *func.as_internal() {
|
||||||
FuncInstanceInternal::Internal { .. } => {
|
FuncInstanceInternal::Internal { .. } => {
|
||||||
let mut interpreter = Interpreter::new(externals);
|
let mut interpreter = Interpreter::new(func, args)?;
|
||||||
interpreter.start_execution(func, args)
|
interpreter.start_execution(externals)
|
||||||
}
|
}
|
||||||
FuncInstanceInternal::Host {
|
FuncInstanceInternal::Host {
|
||||||
ref host_func_index,
|
ref host_func_index,
|
||||||
|
@ -153,6 +154,155 @@ impl FuncInstance {
|
||||||
} => externals.invoke_index(*host_func_index, args.into()),
|
} => 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)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -221,6 +221,16 @@ pub enum TrapKind {
|
||||||
Host(Box<host::HostError>),
|
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.
|
/// Internal interpreter error.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -368,7 +378,7 @@ pub use self::host::{Externals, NopExternals, HostError, RuntimeArgs};
|
||||||
pub use self::imports::{ModuleImportResolver, ImportResolver, ImportsBuilder};
|
pub use self::imports::{ModuleImportResolver, ImportResolver, ImportsBuilder};
|
||||||
pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef};
|
pub use self::module::{ModuleInstance, ModuleRef, ExternVal, NotStartedModuleRef};
|
||||||
pub use self::global::{GlobalInstance, GlobalRef};
|
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};
|
pub use self::types::{Signature, ValueType, GlobalDescriptor, TableDescriptor, MemoryDescriptor};
|
||||||
|
|
||||||
/// WebAssembly-specific sizes and units.
|
/// WebAssembly-specific sizes and units.
|
||||||
|
|
131
src/runner.rs
131
src/runner.rs
|
@ -14,6 +14,7 @@ use value::{
|
||||||
};
|
};
|
||||||
use host::Externals;
|
use host::Externals;
|
||||||
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
use common::{DEFAULT_MEMORY_INDEX, DEFAULT_TABLE_INDEX};
|
||||||
|
use types::ValueType;
|
||||||
use memory_units::Pages;
|
use memory_units::Pages;
|
||||||
use nan_preserving_float::{F32, F64};
|
use nan_preserving_float::{F32, F64};
|
||||||
use isa;
|
use isa;
|
||||||
|
@ -36,6 +37,27 @@ pub enum InstructionOutcome {
|
||||||
Return(isa::DropKeep),
|
Return(isa::DropKeep),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
/// Function execution state, related to pause and resume.
|
||||||
|
pub enum InterpreterState {
|
||||||
|
/// The interpreter has been created, but has not been executed.
|
||||||
|
Initialized,
|
||||||
|
/// The interpreter has started execution, and cannot be called again if it exits normally, or no Host traps happened.
|
||||||
|
Started,
|
||||||
|
/// The interpreter has been executed, and returned a Host trap. It can resume execution by providing back a return
|
||||||
|
/// value.
|
||||||
|
Resumable(Option<ValueType>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterpreterState {
|
||||||
|
pub fn is_resumable(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&InterpreterState::Resumable(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Function run result.
|
/// Function run result.
|
||||||
enum RunResult {
|
enum RunResult {
|
||||||
/// Function has returned.
|
/// Function has returned.
|
||||||
|
@ -45,23 +67,18 @@ enum RunResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function interpreter.
|
/// Function interpreter.
|
||||||
pub struct Interpreter<'a, E: Externals + 'a> {
|
pub struct Interpreter {
|
||||||
externals: &'a mut E,
|
|
||||||
value_stack: ValueStack,
|
value_stack: ValueStack,
|
||||||
|
call_stack: Vec<FunctionContext>,
|
||||||
|
return_type: Option<ValueType>,
|
||||||
|
state: InterpreterState,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, E: Externals> Interpreter<'a, E> {
|
impl Interpreter {
|
||||||
pub fn new(externals: &'a mut E) -> Interpreter<'a, E> {
|
pub fn new(func: &FuncRef, args: &[RuntimeValue]) -> Result<Interpreter, Trap> {
|
||||||
let value_stack = ValueStack::with_limit(DEFAULT_VALUE_STACK_LIMIT);
|
let mut value_stack = ValueStack::with_limit(DEFAULT_VALUE_STACK_LIMIT);
|
||||||
Interpreter {
|
|
||||||
externals,
|
|
||||||
value_stack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_execution(&mut self, func: &FuncRef, args: &[RuntimeValue]) -> Result<Option<RuntimeValue>, Trap> {
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
self.value_stack
|
value_stack
|
||||||
.push(*arg)
|
.push(*arg)
|
||||||
.map_err(
|
.map_err(
|
||||||
// There is not enough space for pushing initial arguments.
|
// There is not enough space for pushing initial arguments.
|
||||||
|
@ -70,26 +87,78 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let initial_frame = FunctionContext::new(func.clone());
|
|
||||||
|
|
||||||
let mut call_stack = Vec::new();
|
let mut call_stack = Vec::new();
|
||||||
|
let initial_frame = FunctionContext::new(func.clone());
|
||||||
call_stack.push(initial_frame);
|
call_stack.push(initial_frame);
|
||||||
|
|
||||||
self.run_interpreter_loop(&mut call_stack)?;
|
let return_type = func.signature().return_type();
|
||||||
|
|
||||||
let opt_return_value = func.signature().return_type().map(|_vt| {
|
Ok(Interpreter {
|
||||||
|
value_stack,
|
||||||
|
call_stack,
|
||||||
|
return_type,
|
||||||
|
state: InterpreterState::Initialized,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn state(&self) -> &InterpreterState {
|
||||||
|
&self.state
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start_execution<'a, E: Externals + 'a>(&mut self, externals: &'a mut E) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
// Ensure that the VM has not been executed. This is checked in `FuncInvocation::start_execution`.
|
||||||
|
assert!(self.state == InterpreterState::Initialized);
|
||||||
|
|
||||||
|
self.state = InterpreterState::Started;
|
||||||
|
self.run_interpreter_loop(externals)?;
|
||||||
|
|
||||||
|
let opt_return_value = self.return_type.map(|_vt| {
|
||||||
self.value_stack.pop()
|
self.value_stack.pop()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ensure that stack is empty after the execution.
|
// Ensure that stack is empty after the execution. This is guaranteed by the validation properties.
|
||||||
assert!(self.value_stack.len() == 0);
|
assert!(self.value_stack.len() == 0);
|
||||||
|
|
||||||
Ok(opt_return_value)
|
Ok(opt_return_value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_interpreter_loop(&mut self, call_stack: &mut Vec<FunctionContext>) -> Result<(), Trap> {
|
pub fn resume_execution<'a, E: Externals + 'a>(&mut self, return_val: Option<RuntimeValue>, externals: &'a mut E) -> Result<Option<RuntimeValue>, Trap> {
|
||||||
|
use std::mem::swap;
|
||||||
|
|
||||||
|
// Ensure that the VM is resumable. This is checked in `FuncInvocation::resume_execution`.
|
||||||
|
assert!(self.state.is_resumable());
|
||||||
|
|
||||||
|
let mut resumable_state = InterpreterState::Started;
|
||||||
|
swap(&mut self.state, &mut resumable_state);
|
||||||
|
let expected_ty = match resumable_state {
|
||||||
|
InterpreterState::Resumable(ty) => ty,
|
||||||
|
_ => unreachable!("Resumable arm is checked above is_resumable; qed"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let value_ty = return_val.as_ref().map(|val| val.value_type());
|
||||||
|
if value_ty != expected_ty {
|
||||||
|
return Err(TrapKind::UnexpectedSignature.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(return_val) = return_val {
|
||||||
|
self.value_stack.push(return_val).map_err(Trap::new)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run_interpreter_loop(externals)?;
|
||||||
|
|
||||||
|
let opt_return_value = self.return_type.map(|_vt| {
|
||||||
|
self.value_stack.pop()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure that stack is empty after the execution. This is guaranteed by the validation properties.
|
||||||
|
assert!(self.value_stack.len() == 0);
|
||||||
|
|
||||||
|
Ok(opt_return_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_interpreter_loop<'a, E: Externals + 'a>(&mut self, externals: &'a mut E) -> Result<(), Trap> {
|
||||||
loop {
|
loop {
|
||||||
let mut function_context = call_stack
|
let mut function_context = self.call_stack
|
||||||
.pop()
|
.pop()
|
||||||
.expect("on loop entry - not empty; on loop continue - checking for emptiness; qed");
|
.expect("on loop entry - not empty; on loop continue - checking for emptiness; qed");
|
||||||
let function_ref = function_context.function.clone();
|
let function_ref = function_context.function.clone();
|
||||||
|
@ -112,26 +181,37 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
||||||
|
|
||||||
match function_return {
|
match function_return {
|
||||||
RunResult::Return => {
|
RunResult::Return => {
|
||||||
if call_stack.last().is_none() {
|
if self.call_stack.last().is_none() {
|
||||||
// This was the last frame in the call stack. This means we
|
// This was the last frame in the call stack. This means we
|
||||||
// are done executing.
|
// are done executing.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RunResult::NestedCall(nested_func) => {
|
RunResult::NestedCall(nested_func) => {
|
||||||
if call_stack.len() + 1 >= DEFAULT_CALL_STACK_LIMIT {
|
if self.call_stack.len() + 1 >= DEFAULT_CALL_STACK_LIMIT {
|
||||||
return Err(TrapKind::StackOverflow.into());
|
return Err(TrapKind::StackOverflow.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
match *nested_func.as_internal() {
|
match *nested_func.as_internal() {
|
||||||
FuncInstanceInternal::Internal { .. } => {
|
FuncInstanceInternal::Internal { .. } => {
|
||||||
let nested_context = FunctionContext::new(nested_func.clone());
|
let nested_context = FunctionContext::new(nested_func.clone());
|
||||||
call_stack.push(function_context);
|
self.call_stack.push(function_context);
|
||||||
call_stack.push(nested_context);
|
self.call_stack.push(nested_context);
|
||||||
},
|
},
|
||||||
FuncInstanceInternal::Host { ref signature, .. } => {
|
FuncInstanceInternal::Host { ref signature, .. } => {
|
||||||
let args = prepare_function_args(signature, &mut self.value_stack);
|
let args = prepare_function_args(signature, &mut self.value_stack);
|
||||||
let return_val = FuncInstance::invoke(&nested_func, &args, self.externals)?;
|
// We push the function context first. If the VM is not resumable, it does no harm. If it is, we then save the context here.
|
||||||
|
self.call_stack.push(function_context);
|
||||||
|
|
||||||
|
let return_val = match FuncInstance::invoke(&nested_func, &args, externals) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(trap) => {
|
||||||
|
if trap.kind().is_host() {
|
||||||
|
self.state = InterpreterState::Resumable(nested_func.signature().return_type());
|
||||||
|
}
|
||||||
|
return Err(trap);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Check if `return_val` matches the signature.
|
// Check if `return_val` matches the signature.
|
||||||
let value_ty = return_val.as_ref().map(|val| val.value_type());
|
let value_ty = return_val.as_ref().map(|val| val.value_type());
|
||||||
|
@ -143,7 +223,6 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
||||||
if let Some(return_val) = return_val {
|
if let Some(return_val) = return_val {
|
||||||
self.value_stack.push(return_val).map_err(Trap::new)?;
|
self.value_stack.push(return_val).map_err(Trap::new)?;
|
||||||
}
|
}
|
||||||
call_stack.push(function_context);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use {
|
use {
|
||||||
Error, Signature, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
Error, Signature, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
||||||
MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef,
|
MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef,
|
||||||
RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind,
|
RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind, ResumableError,
|
||||||
};
|
};
|
||||||
use types::ValueType;
|
use types::ValueType;
|
||||||
use memory_units::Pages;
|
use memory_units::Pages;
|
||||||
|
@ -32,6 +32,8 @@ impl HostError for HostErrorWithCode {}
|
||||||
struct TestHost {
|
struct TestHost {
|
||||||
memory: Option<MemoryRef>,
|
memory: Option<MemoryRef>,
|
||||||
instance: Option<ModuleRef>,
|
instance: Option<ModuleRef>,
|
||||||
|
|
||||||
|
trap_sub_result: Option<RuntimeValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestHost {
|
impl TestHost {
|
||||||
|
@ -39,6 +41,8 @@ impl TestHost {
|
||||||
TestHost {
|
TestHost {
|
||||||
memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap()),
|
memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap()),
|
||||||
instance: None,
|
instance: None,
|
||||||
|
|
||||||
|
trap_sub_result: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +79,11 @@ const GET_MEM_FUNC_INDEX: usize = 3;
|
||||||
/// This function requires attached module instance.
|
/// This function requires attached module instance.
|
||||||
const RECURSE_FUNC_INDEX: usize = 4;
|
const RECURSE_FUNC_INDEX: usize = 4;
|
||||||
|
|
||||||
|
/// trap_sub(a: i32, b: i32) -> i32
|
||||||
|
///
|
||||||
|
/// This function is the same as sub(a, b), but it will send a Host trap which pauses the interpreter execution.
|
||||||
|
const TRAP_SUB_FUNC_INDEX: usize = 5;
|
||||||
|
|
||||||
impl Externals for TestHost {
|
impl Externals for TestHost {
|
||||||
fn invoke_index(
|
fn invoke_index(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -136,6 +145,14 @@ impl Externals for TestHost {
|
||||||
}
|
}
|
||||||
Ok(Some(result))
|
Ok(Some(result))
|
||||||
}
|
}
|
||||||
|
TRAP_SUB_FUNC_INDEX => {
|
||||||
|
let a: i32 = args.nth(0);
|
||||||
|
let b: i32 = args.nth(1);
|
||||||
|
|
||||||
|
let result: RuntimeValue = (a - b).into();
|
||||||
|
self.trap_sub_result = Some(result);
|
||||||
|
return Err(TrapKind::Host(Box::new(HostErrorWithCode { error_code: 301 })).into());
|
||||||
|
}
|
||||||
_ => panic!("env doesn't provide function at index {}", index),
|
_ => panic!("env doesn't provide function at index {}", index),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,6 +174,7 @@ impl TestHost {
|
||||||
ERR_FUNC_INDEX => (&[ValueType::I32], None),
|
ERR_FUNC_INDEX => (&[ValueType::I32], None),
|
||||||
INC_MEM_FUNC_INDEX => (&[ValueType::I32], None),
|
INC_MEM_FUNC_INDEX => (&[ValueType::I32], None),
|
||||||
GET_MEM_FUNC_INDEX => (&[ValueType::I32], Some(ValueType::I32)),
|
GET_MEM_FUNC_INDEX => (&[ValueType::I32], Some(ValueType::I32)),
|
||||||
|
TRAP_SUB_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)),
|
||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -172,6 +190,7 @@ impl ModuleImportResolver for TestHost {
|
||||||
"inc_mem" => INC_MEM_FUNC_INDEX,
|
"inc_mem" => INC_MEM_FUNC_INDEX,
|
||||||
"get_mem" => GET_MEM_FUNC_INDEX,
|
"get_mem" => GET_MEM_FUNC_INDEX,
|
||||||
"recurse" => RECURSE_FUNC_INDEX,
|
"recurse" => RECURSE_FUNC_INDEX,
|
||||||
|
"trap_sub" => TRAP_SUB_FUNC_INDEX,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(Error::Instantiation(
|
return Err(Error::Instantiation(
|
||||||
format!("Export {} not found", field_name),
|
format!("Export {} not found", field_name),
|
||||||
|
@ -232,6 +251,49 @@ fn call_host_func() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn resume_call_host_func() {
|
||||||
|
let module = parse_wat(
|
||||||
|
r#"
|
||||||
|
(module
|
||||||
|
(import "env" "trap_sub" (func $trap_sub (param i32 i32) (result i32)))
|
||||||
|
|
||||||
|
(func (export "test") (result i32)
|
||||||
|
(call $trap_sub
|
||||||
|
(i32.const 5)
|
||||||
|
(i32.const 7)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut env = TestHost::new();
|
||||||
|
|
||||||
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
.expect("Failed to instantiate module")
|
||||||
|
.assert_no_start();
|
||||||
|
|
||||||
|
let export = instance.export_by_name("test").unwrap();
|
||||||
|
let func_instance = export.as_func().unwrap();
|
||||||
|
|
||||||
|
let mut invocation = FuncInstance::invoke_resumable(&func_instance, &[]).unwrap();
|
||||||
|
let result = invocation.start_execution(&mut env);
|
||||||
|
match result {
|
||||||
|
Err(ResumableError::Trap(_)) => {},
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(invocation.is_resumable());
|
||||||
|
let trap_sub_result = env.trap_sub_result.take();
|
||||||
|
assert_eq!(
|
||||||
|
invocation.resume_execution(trap_sub_result, &mut env).expect(
|
||||||
|
"Failed to invoke 'test' function",
|
||||||
|
),
|
||||||
|
Some(RuntimeValue::I32(-2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn host_err() {
|
fn host_err() {
|
||||||
let module = parse_wat(
|
let module = parse_wat(
|
||||||
|
@ -325,6 +387,8 @@ fn pull_internal_mem_from_module() {
|
||||||
let mut env = TestHost {
|
let mut env = TestHost {
|
||||||
memory: None,
|
memory: None,
|
||||||
instance: None,
|
instance: None,
|
||||||
|
|
||||||
|
trap_sub_result: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||||
|
|
Loading…
Reference in New Issue