Add test for resumable invoke and move external parameter passing to start/resume_invocation
This commit is contained in:
parent
981c0c51d8
commit
06f99f2463
35
src/func.rs
35
src/func.rs
|
@ -145,8 +145,8 @@ impl FuncInstance {
|
|||
check_function_args(func.signature(), &args).map_err(|_| TrapKind::UnexpectedSignature)?;
|
||||
match *func.as_internal() {
|
||||
FuncInstanceInternal::Internal { .. } => {
|
||||
let mut interpreter = Interpreter::new(func, args, externals)?;
|
||||
interpreter.start_execution()
|
||||
let mut interpreter = Interpreter::new(func, args)?;
|
||||
interpreter.start_execution(externals)
|
||||
}
|
||||
FuncInstanceInternal::Host {
|
||||
ref host_func_index,
|
||||
|
@ -165,15 +165,14 @@ impl FuncInstance {
|
|||
///
|
||||
/// [`signature`]: #method.signature
|
||||
/// [`Trap`]: #enum.Trap.html
|
||||
pub fn invoke_resumable<'args, 'externals, E: Externals + 'externals>(
|
||||
pub fn invoke_resumable<'args>(
|
||||
func: &FuncRef,
|
||||
args: &'args [RuntimeValue],
|
||||
externals: &'externals mut E,
|
||||
) -> Result<FuncInvocation<'args, 'externals, E>, Trap> {
|
||||
) -> 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, externals)?;
|
||||
let interpreter = Interpreter::new(func, args)?;
|
||||
Ok(FuncInvocation {
|
||||
kind: FuncInvocationKind::Internal(interpreter),
|
||||
})
|
||||
|
@ -184,7 +183,7 @@ impl FuncInstance {
|
|||
} => {
|
||||
Ok(FuncInvocation {
|
||||
kind: FuncInvocationKind::Host {
|
||||
args, externals,
|
||||
args,
|
||||
host_func_index: *host_func_index,
|
||||
finished: false,
|
||||
},
|
||||
|
@ -195,6 +194,7 @@ impl FuncInstance {
|
|||
}
|
||||
|
||||
/// A resumable invocation error.
|
||||
#[derive(Debug)]
|
||||
pub enum ResumableError {
|
||||
/// Trap happened.
|
||||
Trap(Trap),
|
||||
|
@ -211,21 +211,20 @@ impl From<Trap> for ResumableError {
|
|||
}
|
||||
|
||||
/// A resumable invocation handle. This struct is returned by `FuncInstance::invoke_resumable`.
|
||||
pub struct FuncInvocation<'args, 'externals, E: Externals + 'externals> {
|
||||
kind: FuncInvocationKind<'args, 'externals, E>,
|
||||
pub struct FuncInvocation<'args> {
|
||||
kind: FuncInvocationKind<'args>,
|
||||
}
|
||||
|
||||
enum FuncInvocationKind<'args, 'externals, E: Externals + 'externals> {
|
||||
Internal(Interpreter<'externals, E>),
|
||||
enum FuncInvocationKind<'args> {
|
||||
Internal(Interpreter),
|
||||
Host {
|
||||
args: &'args [RuntimeValue],
|
||||
externals: &'externals mut E,
|
||||
host_func_index: usize,
|
||||
finished: bool
|
||||
},
|
||||
}
|
||||
|
||||
impl<'args, 'externals, E: Externals + 'externals> FuncInvocation<'args, 'externals, E> {
|
||||
impl<'args> FuncInvocation<'args> {
|
||||
/// Whether this invocation is currently resumable.
|
||||
pub fn is_resumable(&self) -> bool {
|
||||
match &self.kind {
|
||||
|
@ -248,15 +247,15 @@ impl<'args, 'externals, E: Externals + 'externals> FuncInvocation<'args, 'extern
|
|||
}
|
||||
|
||||
/// Start the invocation execution.
|
||||
pub fn start_execution(&mut self) -> Result<Option<RuntimeValue>, ResumableError> {
|
||||
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()?)
|
||||
Ok(interpreter.start_execution(externals)?)
|
||||
},
|
||||
FuncInvocationKind::Host { ref args, ref mut externals, ref mut finished, ref host_func_index } => {
|
||||
FuncInvocationKind::Host { ref args, ref mut finished, ref host_func_index } => {
|
||||
if *finished {
|
||||
return Err(ResumableError::AlreadyStarted);
|
||||
}
|
||||
|
@ -267,13 +266,13 @@ impl<'args, 'externals, E: Externals + 'externals> FuncInvocation<'args, 'extern
|
|||
}
|
||||
|
||||
/// Resume an execution if a previous trap of Host kind happened.
|
||||
pub fn resume_execution(&mut self, return_val: Option<RuntimeValue>) -> Result<Option<RuntimeValue>, ResumableError> {
|
||||
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)?)
|
||||
Ok(interpreter.resume_execution(return_val, externals)?)
|
||||
},
|
||||
FuncInvocationKind::Host { .. } => {
|
||||
return Err(ResumableError::NotResumable);
|
||||
|
|
|
@ -67,16 +67,15 @@ enum RunResult {
|
|||
}
|
||||
|
||||
/// Function interpreter.
|
||||
pub struct Interpreter<'a, E: Externals + 'a> {
|
||||
externals: &'a mut E,
|
||||
pub struct Interpreter {
|
||||
value_stack: ValueStack,
|
||||
call_stack: Vec<FunctionContext>,
|
||||
return_type: Option<ValueType>,
|
||||
state: InterpreterState,
|
||||
}
|
||||
|
||||
impl<'a, E: Externals> Interpreter<'a, E> {
|
||||
pub fn new(func: &FuncRef, args: &[RuntimeValue], externals: &'a mut E) -> Result<Interpreter<'a, E>, Trap> {
|
||||
impl Interpreter {
|
||||
pub fn new(func: &FuncRef, args: &[RuntimeValue]) -> Result<Interpreter, Trap> {
|
||||
let mut value_stack = ValueStack::with_limit(DEFAULT_VALUE_STACK_LIMIT);
|
||||
for arg in args {
|
||||
value_stack
|
||||
|
@ -95,7 +94,6 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
|||
let return_type = func.signature().return_type();
|
||||
|
||||
Ok(Interpreter {
|
||||
externals,
|
||||
value_stack,
|
||||
call_stack,
|
||||
return_type,
|
||||
|
@ -107,12 +105,12 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
|||
&self.state
|
||||
}
|
||||
|
||||
pub fn start_execution(&mut self) -> Result<Option<RuntimeValue>, Trap> {
|
||||
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.
|
||||
assert!(self.state == InterpreterState::Initialized);
|
||||
|
||||
self.state = InterpreterState::Started;
|
||||
self.run_interpreter_loop()?;
|
||||
self.run_interpreter_loop(externals)?;
|
||||
|
||||
let opt_return_value = self.return_type.map(|_vt| {
|
||||
self.value_stack.pop()
|
||||
|
@ -124,7 +122,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
|||
Ok(opt_return_value)
|
||||
}
|
||||
|
||||
pub fn resume_execution(&mut self, return_val: Option<RuntimeValue>) -> Result<Option<RuntimeValue>, 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.
|
||||
|
@ -146,7 +144,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
|||
self.value_stack.push(return_val).map_err(Trap::new)?;
|
||||
}
|
||||
|
||||
self.run_interpreter_loop()?;
|
||||
self.run_interpreter_loop(externals)?;
|
||||
|
||||
let opt_return_value = self.return_type.map(|_vt| {
|
||||
self.value_stack.pop()
|
||||
|
@ -158,7 +156,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
|||
Ok(opt_return_value)
|
||||
}
|
||||
|
||||
fn run_interpreter_loop(&mut self) -> Result<(), Trap> {
|
||||
fn run_interpreter_loop<'a, E: Externals + 'a>(&mut self, externals: &'a mut E) -> Result<(), Trap> {
|
||||
loop {
|
||||
let mut function_context = self.call_stack
|
||||
.pop()
|
||||
|
@ -205,7 +203,7 @@ impl<'a, E: Externals> Interpreter<'a, E> {
|
|||
// 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, self.externals) {
|
||||
let return_val = match FuncInstance::invoke(&nested_func, &args, externals) {
|
||||
Ok(val) => val,
|
||||
Err(trap) => {
|
||||
if trap.kind().is_host() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use {
|
||||
Error, Signature, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder,
|
||||
MemoryInstance, MemoryRef, TableInstance, TableRef, ModuleImportResolver, ModuleInstance, ModuleRef,
|
||||
RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind,
|
||||
RuntimeValue, RuntimeArgs, TableDescriptor, MemoryDescriptor, Trap, TrapKind, ResumableError, ExternVal,
|
||||
};
|
||||
use types::ValueType;
|
||||
use memory_units::Pages;
|
||||
|
@ -32,6 +32,8 @@ impl HostError for HostErrorWithCode {}
|
|||
struct TestHost {
|
||||
memory: Option<MemoryRef>,
|
||||
instance: Option<ModuleRef>,
|
||||
|
||||
trap_sub_result: Option<RuntimeValue>,
|
||||
}
|
||||
|
||||
impl TestHost {
|
||||
|
@ -39,6 +41,8 @@ impl TestHost {
|
|||
TestHost {
|
||||
memory: Some(MemoryInstance::alloc(Pages(1), Some(Pages(1))).unwrap()),
|
||||
instance: None,
|
||||
|
||||
trap_sub_result: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +79,11 @@ const GET_MEM_FUNC_INDEX: usize = 3;
|
|||
/// This function requires attached module instance.
|
||||
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 {
|
||||
fn invoke_index(
|
||||
&mut self,
|
||||
|
@ -136,6 +145,14 @@ impl Externals for TestHost {
|
|||
}
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
@ -157,6 +174,7 @@ impl TestHost {
|
|||
ERR_FUNC_INDEX => (&[ValueType::I32], None),
|
||||
INC_MEM_FUNC_INDEX => (&[ValueType::I32], None),
|
||||
GET_MEM_FUNC_INDEX => (&[ValueType::I32], Some(ValueType::I32)),
|
||||
TRAP_SUB_FUNC_INDEX => (&[ValueType::I32, ValueType::I32], Some(ValueType::I32)),
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
|
@ -172,6 +190,7 @@ impl ModuleImportResolver for TestHost {
|
|||
"inc_mem" => INC_MEM_FUNC_INDEX,
|
||||
"get_mem" => GET_MEM_FUNC_INDEX,
|
||||
"recurse" => RECURSE_FUNC_INDEX,
|
||||
"trap_sub" => TRAP_SUB_FUNC_INDEX,
|
||||
_ => {
|
||||
return Err(Error::Instantiation(
|
||||
format!("Export {} not found", field_name),
|
||||
|
@ -232,6 +251,51 @@ 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 func_instance = match instance.export_by_name("test").unwrap() {
|
||||
ExternVal::Func(func_instance) => func_instance,
|
||||
_ => panic!(),
|
||||
};
|
||||
|
||||
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]
|
||||
fn host_err() {
|
||||
let module = parse_wat(
|
||||
|
@ -325,6 +389,8 @@ fn pull_internal_mem_from_module() {
|
|||
let mut env = TestHost {
|
||||
memory: None,
|
||||
instance: None,
|
||||
|
||||
trap_sub_result: None,
|
||||
};
|
||||
|
||||
let instance = ModuleInstance::new(&module, &ImportsBuilder::new().with_resolver("env", &env))
|
||||
|
|
Loading…
Reference in New Issue