diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 096002c..173f547 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -47,3 +47,6 @@ mod table; mod utils; mod value; mod variable; + +#[cfg(test)] +mod tests; diff --git a/src/interpreter/runner.rs b/src/interpreter/runner.rs index f622406..cdd629e 100644 --- a/src/interpreter/runner.rs +++ b/src/interpreter/runner.rs @@ -1,6 +1,7 @@ use std::mem; use std::ops; use std::u32; +use std::fmt::Display; use elements::{Opcode, BlockType, FunctionType}; use interpreter::Error; use interpreter::module::{ModuleInstance, ItemIndex}; @@ -37,8 +38,10 @@ pub enum InstructionOutcome { RunInstruction, /// Continue with next instruction. RunNextInstruction, - /// Pop given number of stack frames. - PopFrame(usize), + /// Branch to given frame. + Branch(usize), + /// End current frame. + End, /// Return from current function block. Return, } @@ -46,7 +49,9 @@ pub enum InstructionOutcome { #[derive(Debug, Clone)] pub struct BlockFrame { // A label for reference from branch instructions. - position: usize, + branch_position: usize, + // A label for reference from end instructions. + end_position: usize, // A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label. value_limit: usize, // A signature, which is a block signature type indicating the number and types of result values of the region. @@ -266,13 +271,13 @@ impl Interpreter { fn run_block(context: &mut FunctionContext, block_type: BlockType, body: &[Opcode]) -> Result { let frame_position = context.position + 1; - context.push_frame(frame_position, block_type.clone())?; + context.push_frame(frame_position, frame_position, block_type.clone())?; Interpreter::execute_block(context, body) } fn run_loop(context: &mut FunctionContext, block_type: BlockType, body: &[Opcode]) -> Result { let frame_position = context.position; - context.push_frame(frame_position, block_type.clone())?; + context.push_frame(frame_position, frame_position + 1, block_type.clone())?; Interpreter::execute_block(context, body) } @@ -287,7 +292,7 @@ impl Interpreter { if begin_index != end_index { let frame_position = context.position + 1; - context.push_frame(frame_position, block_type.clone())?; + context.push_frame(frame_position, frame_position, block_type.clone())?; Interpreter::execute_block(context, &body[begin_index..end_index]) } else { Ok(InstructionOutcome::RunNextInstruction) @@ -295,20 +300,20 @@ impl Interpreter { } fn run_else(context: &mut FunctionContext) -> Result { - Ok(InstructionOutcome::PopFrame(0)) + Ok(InstructionOutcome::End) } fn run_end(context: &mut FunctionContext) -> Result { - Ok(InstructionOutcome::PopFrame(0)) + Ok(InstructionOutcome::End) } fn run_br(context: &mut FunctionContext, label_idx: u32) -> Result { - Ok(InstructionOutcome::PopFrame(label_idx as usize)) + Ok(InstructionOutcome::Branch(label_idx as usize)) } fn run_br_if(context: &mut FunctionContext, label_idx: u32) -> Result { if context.value_stack_mut().pop_as()? { - Ok(InstructionOutcome::PopFrame(label_idx as usize)) + Ok(InstructionOutcome::Branch(label_idx as usize)) } else { Ok(InstructionOutcome::RunNextInstruction) } @@ -316,7 +321,7 @@ impl Interpreter { fn run_br_table(context: &mut FunctionContext, table: &Vec, default: u32) -> Result { let index: u32 = context.value_stack_mut().pop_as()?; - Ok(InstructionOutcome::PopFrame(table.get(index as usize).cloned().unwrap_or(default) as usize)) + Ok(InstructionOutcome::Branch(table.get(index as usize).cloned().unwrap_or(default) as usize)) } fn run_return(context: &mut FunctionContext) -> Result { @@ -491,7 +496,7 @@ impl Interpreter { } fn run_lt(context: &mut FunctionContext) -> Result - where RuntimeValue: TryInto, T: PartialOrd { + where RuntimeValue: TryInto, T: PartialOrd + Display { context .value_stack_mut() .pop_pair_as::() @@ -835,14 +840,19 @@ impl Interpreter { match Interpreter::run_instruction(context, instruction)? { InstructionOutcome::RunInstruction => (), InstructionOutcome::RunNextInstruction => context.position += 1, - InstructionOutcome::PopFrame(index) => { - context.pop_frame()?; + InstructionOutcome::Branch(index) => { if index != 0 { - return Ok(InstructionOutcome::PopFrame(index - 1)); + context.discard_frame()?; + return Ok(InstructionOutcome::Branch(index - 1)); } else { + context.pop_frame(true)?; return Ok(InstructionOutcome::RunInstruction); } }, + InstructionOutcome::End => { + context.pop_frame(false)?; + return Ok(InstructionOutcome::RunInstruction); + }, InstructionOutcome::Return => return Ok(InstructionOutcome::Return), } } @@ -859,7 +869,7 @@ impl<'a> FunctionContext<'a> { locals: args, position: 0, }; - context.push_frame(body.len() - 1, match function.return_type() { + context.push_frame(body.len() - 1, body.len() - 1, match function.return_type() { Some(value_type) => BlockType::Value(value_type), None => BlockType::NoResult, })?; @@ -903,15 +913,21 @@ impl<'a> FunctionContext<'a> { &self.frame_stack } - pub fn push_frame(&mut self, position: usize, signature: BlockType) -> Result<(), Error> { + pub fn push_frame(&mut self, branch_position: usize, end_position: usize, signature: BlockType) -> Result<(), Error> { self.frame_stack.push(BlockFrame { - position: position, + branch_position: branch_position, + end_position: end_position, value_limit: self.value_stack.len(), signature: signature, }) } - pub fn pop_frame(&mut self) -> Result<(), Error> { + pub fn discard_frame(&mut self) -> Result<(), Error> { + self.frame_stack.pop() + .map(|_| ()) + } + + pub fn pop_frame(&mut self, is_branch: bool) -> Result<(), Error> { let frame = self.frame_stack.pop()?; if frame.value_limit > self.value_stack.len() { return Err(Error::Stack("invalid stack len".into())); @@ -922,7 +938,7 @@ impl<'a> FunctionContext<'a> { BlockType::NoResult => None, }; self.value_stack.resize(frame.value_limit, RuntimeValue::I32(0)); - self.position = frame.position; + self.position = if is_branch { frame.branch_position } else { frame.end_position }; if let Some(frame_value) = frame_value { self.value_stack.push(frame_value)?; } @@ -934,7 +950,8 @@ impl<'a> FunctionContext<'a> { impl BlockFrame { pub fn invalid() -> Self { BlockFrame { - position: usize::max_value(), + branch_position: usize::max_value(), + end_position: usize::max_value(), value_limit: usize::max_value(), signature: BlockType::NoResult, } @@ -950,158 +967,3 @@ fn effective_address(offset: u32, align: u32) -> Result { .ok_or(Error::Interpreter("invalid memory alignment".into())) } } - -#[cfg(test)] -mod tests { - use std::sync::Weak; - use elements::{Module, ValueType, Opcodes, Opcode, BlockType, FunctionType}; - use interpreter::Error; - use interpreter::module::ModuleInstance; - use interpreter::runner::{Interpreter, FunctionContext}; - use interpreter::value::{RuntimeValue, TryInto}; - use interpreter::variable::{VariableInstance, VariableType}; - - fn run_function_i32(body: &Opcodes, arg: i32) -> Result { - let ftype = FunctionType::new(vec![ValueType::I32], Some(ValueType::I32)); - let module = ModuleInstance::new(Weak::default(), Module::default()).unwrap(); - let mut context = FunctionContext::new(&module, 1024, 1024, &ftype, body.elements(), vec![ - VariableInstance::new(true, VariableType::I32, RuntimeValue::I32(arg)).unwrap() - ])?; - Interpreter::run_function(&mut context, body.elements()) - .map(|v| v.unwrap().try_into().unwrap()) - } - - #[test] - fn trap() { - let body = Opcodes::new(vec![ - Opcode::Unreachable, // trap - Opcode::End]); - - assert_eq!(run_function_i32(&body, 0).unwrap_err(), Error::Trap("programmatic".into())); - } - - #[test] - fn nop() { - let body = Opcodes::new(vec![ - Opcode::Nop, // nop - Opcode::I32Const(20), // 20 - Opcode::Nop, // nop - Opcode::End]); - - assert_eq!(run_function_i32(&body, 0).unwrap(), 20); - } - - #[test] - fn if_then() { - let body = Opcodes::new(vec![ - Opcode::I32Const(20), // 20 - Opcode::GetLocal(0), // read argument - Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 - Opcodes::new(vec![ - Opcode::I32Const(10), // 10 - Opcode::End, // end - ])), - Opcode::End]); - - assert_eq!(run_function_i32(&body, 0).unwrap(), 20); - assert_eq!(run_function_i32(&body, 1).unwrap(), 10); - } - - #[test] - fn if_then_else() { - let body = Opcodes::new(vec![ - Opcode::GetLocal(0), // read argument - Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 - Opcodes::new(vec![ - Opcode::I32Const(10), // 10 - Opcode::Else, // else - Opcode::I32Const(20), // 20 - Opcode::End, // end - ])), - Opcode::End]); - - assert_eq!(run_function_i32(&body, 0).unwrap(), 20); - assert_eq!(run_function_i32(&body, 1).unwrap(), 10); - } - - #[test] - fn return_from_if() { - let body = Opcodes::new(vec![ - Opcode::GetLocal(0), // read argument - Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 - Opcodes::new(vec![ - Opcode::I32Const(20), // 20 - Opcode::Return, // return - Opcode::End, - ])), - Opcode::I32Const(10), // 10 - Opcode::End]); - - assert_eq!(run_function_i32(&body, 0).unwrap(), 10); - assert_eq!(run_function_i32(&body, 1).unwrap(), 20); - } - - #[test] - fn block() { - let body = Opcodes::new(vec![ - Opcode::Block(BlockType::Value(ValueType::I32), // mark block - Opcodes::new(vec![ - Opcode::I32Const(10), // 10 - Opcode::End, - ])), - Opcode::End]); - - assert_eq!(run_function_i32(&body, 0).unwrap(), 10); - } - - #[test] - fn loop_block() { - // TODO: test -/* - let body = Opcodes::new(vec![ - Opcode::I32Const(2), // 2 - Opcode::Loop(BlockType::Value(ValueType::I32), // start loop - Opcodes::new(vec![ - Opcode::GetLocal(0), // read argument - Opcode::I32Const(1), // 1 - Opcode::I32Sub, // argument-- - Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 - Opcodes::new(vec![ - Opcode::I32Const(2), // 2 - Opcode::I32Mul, // prev_val * 2 - Opcode::Br(1), // branch to loop - Opcode::End, // end (if) - ])), - Opcode::End, // end (loop) - ])), - Opcode::End]); // end (fun) - - assert_eq!(run_function_i32(&body, 2).unwrap(), 10); -*/ - } - - #[test] - fn branch() { - // TODO - } - - #[test] - fn branch_if() { - // TODO - } - - #[test] - fn branch_table() { - // TODO - } - - #[test] - fn drop() { - // TODO - } - - #[test] - fn select() { - // TODO - } -} diff --git a/src/interpreter/stack.rs b/src/interpreter/stack.rs index 5430ad1..a0272a9 100644 --- a/src/interpreter/stack.rs +++ b/src/interpreter/stack.rs @@ -3,6 +3,7 @@ use interpreter::Error; use interpreter::value::{RuntimeValue, TryInto}; /// Stack with limit. +#[derive(Debug)] pub struct StackWithLimit where T: Clone { /// Stack values. values: VecDeque, diff --git a/src/interpreter/tests/mod.rs b/src/interpreter/tests/mod.rs new file mode 100644 index 0000000..e79767c --- /dev/null +++ b/src/interpreter/tests/mod.rs @@ -0,0 +1 @@ +mod wabt; \ No newline at end of file diff --git a/src/interpreter/tests/wabt.rs b/src/interpreter/tests/wabt.rs new file mode 100644 index 0000000..3a57a18 --- /dev/null +++ b/src/interpreter/tests/wabt.rs @@ -0,0 +1,579 @@ +///! Tests from https://github.com/WebAssembly/wabt/tree/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp + +use std::sync::Weak; +use elements::{Module, ValueType, Opcodes, Opcode, BlockType, FunctionType}; +use interpreter::Error; +use interpreter::module::ModuleInstance; +use interpreter::runner::{Interpreter, FunctionContext}; +use interpreter::value::{RuntimeValue, TryInto}; +use interpreter::variable::{VariableInstance, VariableType}; + +fn run_function_i32(body: &Opcodes, arg: i32) -> Result { + let ftype = FunctionType::new(vec![ValueType::I32], Some(ValueType::I32)); + let module = ModuleInstance::new(Weak::default(), Module::default()).unwrap(); + let mut context = FunctionContext::new(&module, 1024, 1024, &ftype, body.elements(), vec![ + VariableInstance::new(true, VariableType::I32, RuntimeValue::I32(arg)).unwrap(), // arg + VariableInstance::new(true, VariableType::I32, RuntimeValue::I32(0)).unwrap(), // local1 + VariableInstance::new(true, VariableType::I32, RuntimeValue::I32(0)).unwrap(), // local2 + ])?; + Interpreter::run_function(&mut context, body.elements()) + .map(|v| v.unwrap().try_into().unwrap()) +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/unreachable.txt +#[test] +fn unreachable() { + let body = Opcodes::new(vec![ + Opcode::Unreachable, // trap + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap_err(), Error::Trap("programmatic".into())); +} + +#[test] +fn nop() { + let body = Opcodes::new(vec![ + Opcode::Nop, // nop + Opcode::I32Const(1), // [1] + Opcode::Nop, // nop + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/expr-block.txt +#[test] +fn expr_block() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::Value(ValueType::I32), // mark block + Opcodes::new(vec![ + Opcode::I32Const(10), // [10] + Opcode::Drop, + Opcode::I32Const(1), // [1] + Opcode::End, + ])), + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/loop.txt +#[test] +fn loop_test() { + let body = Opcodes::new(vec![ + Opcode::Loop(BlockType::NoResult, // loop + Opcodes::new(vec![ + Opcode::GetLocal(1), // [local1] + Opcode::GetLocal(0), // [local1, arg] + Opcode::I32Add, // [arg + local1] + Opcode::SetLocal(1), // [] + local1 = arg + local1 + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(1), // [arg, 1] + Opcode::I32Add, // [arg + 1] + Opcode::SetLocal(0), // [] + arg = arg + 1 + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(5), // [arg, 5] + Opcode::I32LtS, // [arg < 5] + Opcode::If(BlockType::NoResult, + Opcodes::new(vec![ + Opcode::Br(1), // break loop + Opcode::End, + ])), + Opcode::End])), // end loop + Opcode::GetLocal(1), // [local1] + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 10); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/if.txt#L3 +#[test] +fn if_1() { + let body = Opcodes::new(vec![ + Opcode::I32Const(0), // [0] + Opcode::SetLocal(0), // [] + arg = 0 + Opcode::I32Const(1), // [1] + Opcode::If(BlockType::NoResult, // if 1 + Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(1), // [arg, 1] + Opcode::I32Add, // [arg + 1] + Opcode::SetLocal(0), // [] + arg = arg + 1 + Opcode::End, // end if + ])), + Opcode::I32Const(0), // [0] + Opcode::If(BlockType::NoResult, // if 0 + Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(1), // [arg, 1] + Opcode::I32Add, // [arg + 1] + Opcode::SetLocal(0), // [] + arg = arg + 1 + Opcode::End, // end if + ])), + Opcode::GetLocal(0), // [arg] + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/if.txt#L23 +#[test] +fn if_2() { + let body = Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::If(BlockType::NoResult, // if 1 + Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::SetLocal(0), // [] + arg = 1 + Opcode::Else, // else + Opcode::I32Const(2), // [2] + Opcode::SetLocal(0), // [] + arg = 2 + Opcode::End, // end if + ])), + Opcode::I32Const(0), // [0] + Opcode::If(BlockType::NoResult, // if 0 + Opcodes::new(vec![ + Opcode::I32Const(4), // [4] + Opcode::SetLocal(1), // [] + local1 = 4 + Opcode::Else, // else + Opcode::I32Const(8), // [8] + Opcode::SetLocal(1), // [] + local1 = 8 + Opcode::End, // end if + ])), + Opcode::GetLocal(0), // [arg] + Opcode::GetLocal(1), // [arg, local1] + Opcode::I32Add, // [arg + local1] + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 9); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/expr-if.txt +#[test] +fn expr_if() { + let body = Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(0), // [arg, 0] + Opcode::I32Eq, // [arg == 0] + Opcode::If(BlockType::Value(ValueType::I32), // if arg == 0 + Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::Else, // else + Opcode::I32Const(2), // [2] + Opcode::End, // end if + ])), + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); + assert_eq!(run_function_i32(&body, 1).unwrap(), 2); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/br.txt#L4 +#[test] +fn br_0() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // mark block + Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::If(BlockType::NoResult, // if 1 + Opcodes::new(vec![ + Opcode::Br(1), // break from block + Opcode::End, // end if + ])), + Opcode::I32Const(1), // [1] + Opcode::SetLocal(0), // [] + arg = 1 + Opcode::End, // end block + ])), + Opcode::I32Const(1), // [1] + Opcode::SetLocal(1), // [] + local1 = 1 + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(0), // [arg, 0] + Opcode::I32Eq, // [arg == 0] + Opcode::GetLocal(1), // [arg == 0, local1] + Opcode::I32Const(1), // [arg == 0, local1, 1] + Opcode::I32Eq, // [arg == 0, local1 == 1] + Opcode::I32Add, // [arg == 0 + local1 == 1] + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 2); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/br.txt#L26 +#[test] +fn br_1() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block1 + Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block2 + Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::If(BlockType::NoResult, // if 1 + Opcodes::new(vec![ + Opcode::Br(2), // break from block2 + Opcode::End, // end if + ])), + Opcode::I32Const(1), // [1] + Opcode::SetLocal(0), // [] + arg = 1 + Opcode::End, // end (block2) + ])), + Opcode::I32Const(1), // [1] + Opcode::SetLocal(1), // [] + local1 = 1 + Opcode::End, // end (block1) + ])), + Opcode::I32Const(1), // [1] + Opcode::SetLocal(2), // [] + local2 = 1 + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(0), // [arg, 0] + Opcode::I32Eq, // [arg == 0] + Opcode::GetLocal(1), // [arg == 0, local1] + Opcode::I32Const(0), // [arg == 0, local1, 0] + Opcode::I32Eq, // [arg == 0, local1 == 0] + Opcode::I32Add, // [arg == 0 + local1 == 0] + Opcode::GetLocal(2), // [arg == 0 + local1 == 0, local2] + Opcode::I32Const(1), // [arg == 0 + local1 == 0, local2, 1] + Opcode::I32Eq, // [arg == 0 + local1 == 0, local2 == 1] + Opcode::I32Add, // [arg == 0 + local1 == 0 + local2 == 1] + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 3); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/br.txt#L56 +#[test] +fn br_2() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block1 + Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block2 + Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::If(BlockType::NoResult, // if 1 + Opcodes::new(vec![ + Opcode::Br(2), // break from block2 + Opcode::End, // end if + ])), + Opcode::I32Const(1), // [1] + Opcode::Return, // return 1 + Opcode::End, // end (block2) + ])), + Opcode::End, // end (block1) + ])), + Opcode::I32Const(2), // [2] + Opcode::Return, // return 2 + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 2); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/br.txt#L71 +#[test] +fn br_3() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block1 + Opcodes::new(vec![ + Opcode::Loop(BlockType::NoResult, // loop + Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(1), // [arg, 1] + Opcode::I32Add, // [arg + 1] + Opcode::SetLocal(0), // [] + arg = arg + 1 + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(5), // [arg, 5] + Opcode::I32GeS, // [5 >= arg] + Opcode::If(BlockType::NoResult, // if 5 >= arg + Opcodes::new(vec![ + Opcode::Br(2), // break from block1 + Opcode::End, // end + ])), + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(4), // [arg, 4] + Opcode::I32Eq, // [arg == 4] + Opcode::If(BlockType::NoResult, // if arg == 4 + Opcodes::new(vec![ + Opcode::Br(1), // break from loop + Opcode::End, // end + ])), + Opcode::GetLocal(0), // [arg] + Opcode::SetLocal(1), // [] + local1 = arg + Opcode::Br(0), // continue loop + Opcode::End, // end (loop) + ])), + Opcode::End, // end (block1) + ])), + Opcode::GetLocal(1), // [local1] + Opcode::Return, // return local1 + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 3); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/expr-br.txt +#[test] +fn expr_br() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::Value(ValueType::I32), // block1 + Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::I32Const(0), // [arg, 0] + Opcode::I32Eq, // [arg == 0] + Opcode::If(BlockType::NoResult, // if arg == 0 + Opcodes::new(vec![ + Opcode::I32Const(1), // [1] + Opcode::Br(1), // break from block1 + Opcode::End, // end (if) + ])), + Opcode::I32Const(2), // [2] + Opcode::End, // end (block1) + ])), + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); + assert_eq!(run_function_i32(&body, 1).unwrap(), 2); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/brif.txt +#[test] +fn brif() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block1 + Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::BrIf(0), // if arg != 0: break from block1 + Opcode::I32Const(1), // [1] + Opcode::Return, // return 1 + Opcode::End, // end (block1) + ])), + Opcode::I32Const(2), // [2] + Opcode::Return, // return 2 + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); + assert_eq!(run_function_i32(&body, 1).unwrap(), 2); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/brif-loop.txt +#[test] +fn brif_loop() { + +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/expr-brif.txt +#[test] +fn expr_brif() { + let body = Opcodes::new(vec![ + Opcode::Loop(BlockType::NoResult, // loop + Opcodes::new(vec![ + Opcode::GetLocal(1), // [local1] + Opcode::I32Const(1), // [local1, 1] + Opcode::I32Add, // [local1 + 1] + Opcode::SetLocal(1), // [] + local1 = local1 + 1 + Opcode::GetLocal(1), // [local1] + Opcode::GetLocal(0), // [local1, local0] + Opcode::I32LtS, // [local1 < local0] + Opcode::BrIf(0), // if local1 < local0: break from loop + Opcode::End, // end (loop) + ])), + Opcode::GetLocal(1), // [local1] + Opcode::End]); + + assert_eq!(run_function_i32(&body, 3).unwrap(), 3); + assert_eq!(run_function_i32(&body, 10).unwrap(), 10); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/brtable.txt +#[test] +fn brtable() { + let body = Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block3 + Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block2 + Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block1 + Opcodes::new(vec![ + Opcode::Block(BlockType::NoResult, // block0 + Opcodes::new(vec![ + Opcode::GetLocal(0), // [arg] + Opcode::BrTable(vec![0, 1, 2], 3), // br_table + Opcode::End, // end (block0) + ])), + Opcode::I32Const(0), // [0] + Opcode::Return, // return 0 + Opcode::End, // end (block1) + ])), + Opcode::I32Const(1), // [1] + Opcode::Return, // return 1 + Opcode::End, // end (block2) + ])), + Opcode::End, // end (block3) + ])), + Opcode::I32Const(2), // [2] + Opcode::Return, // return 2 + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 0); + assert_eq!(run_function_i32(&body, 1).unwrap(), 1); + assert_eq!(run_function_i32(&body, 2).unwrap(), 2); + assert_eq!(run_function_i32(&body, 3).unwrap(), 2); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/return.txt +#[test] +fn return_test() { + let body = Opcodes::new(vec![ + Opcode::GetLocal(0), + Opcode::I32Const(0), + Opcode::I32Eq, + Opcode::If(BlockType::NoResult, + Opcodes::new(vec![ + Opcode::I32Const(1), + Opcode::Return, + Opcode::End, + ])), + Opcode::GetLocal(0), + Opcode::I32Const(1), + Opcode::I32Eq, + Opcode::If(BlockType::NoResult, + Opcodes::new(vec![ + Opcode::I32Const(2), + Opcode::Return, + Opcode::End, + ])), + Opcode::I32Const(3), + Opcode::Return, + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 1); + assert_eq!(run_function_i32(&body, 1).unwrap(), 2); + assert_eq!(run_function_i32(&body, 5).unwrap(), 3); +} + +/// https://github.com/WebAssembly/wabt/blob/8e1f6031e9889ba770c7be4a9b084da5f14456a0/test/interp/return-void.txt +#[test] +fn return_void() { + // TODO: linear memory required +} + + + + + + + + + + + + + + + + + + +/* + + #[test] + fn basics_loop() { + let body = Opcodes::new(vec![ + Opcode::I32Const(2), // [2] + Opcode::SetLocal(1), // [] + local1 = 2 + Opcode::Block(BlockType::NoResult, // add block label to exit from loop. TODO: is it the correct pattern? + Opcodes::new(vec![ + Opcode::Loop(BlockType::NoResult, // start loop + Opcodes::new(vec![ + Opcode::GetLocal(0), // [local0] + Opcode::I32Const(1), // [local0, 1] + Opcode::I32Sub, // [local0 - 1] + Opcode::SetLocal(0), // [] + local0 = local0 - 1 + Opcode::GetLocal(0), // [local0] + Opcode::If(BlockType::NoResult, // if local0 != 0 + Opcodes::new(vec![ + Opcode::GetLocal(1), // [local1] + Opcode::I32Const(2), // [local1, 2] + Opcode::I32Mul, // [local1 * 2] + Opcode::SetLocal(1), // [] + local1 = local1 * 2 + Opcode::Else, // else + Opcode::Br(2), // exit from loop (2 = if + loop) + Opcode::End, // end (if) + ])), + Opcode::End, // end (loop) + ])), + Opcode::End, // end (block) + ])), + Opcode::GetLocal(1), // [local1] + Opcode::End]); // end (fun) + + assert_eq!(run_function_i32(&body, 2).unwrap(), 4); + assert_eq!(run_function_i32(&body, 8).unwrap(), 256); + } + + + #[test] + fn basics_if_then() { + let body = Opcodes::new(vec![ + Opcode::I32Const(20), // 20 + Opcode::GetLocal(0), // read argument + Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 + Opcodes::new(vec![ + Opcode::I32Const(10), // 10 + Opcode::End, // end + ])), + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 20); + assert_eq!(run_function_i32(&body, 1).unwrap(), 10); + } + + #[test] + fn basics_if_then_else() { + let body = Opcodes::new(vec![ + Opcode::GetLocal(0), // read argument + Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 + Opcodes::new(vec![ + Opcode::I32Const(10), // 10 + Opcode::Else, // else + Opcode::I32Const(20), // 20 + Opcode::End, // end + ])), + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 20); + assert_eq!(run_function_i32(&body, 1).unwrap(), 10); + } + + #[test] + fn basics_return() { + let body = Opcodes::new(vec![ + Opcode::GetLocal(0), // read argument + Opcode::If(BlockType::Value(ValueType::I32), // if argument != 0 + Opcodes::new(vec![ + Opcode::I32Const(20), // 20 + Opcode::Return, // return + Opcode::End, + ])), + Opcode::I32Const(10), // 10 + Opcode::End]); + + assert_eq!(run_function_i32(&body, 0).unwrap(), 10); + assert_eq!(run_function_i32(&body, 1).unwrap(), 20); + } + + #[test] + fn branch_if() { + // TODO + } + + #[test] + fn branch_table() { + // TODO + } + + #[test] + fn drop() { + // TODO + } + + #[test] + fn select() { + // TODO + }*/