diff --git a/src/prepare/tests.rs b/src/prepare/tests.rs new file mode 100644 index 0000000..aba3b0b --- /dev/null +++ b/src/prepare/tests.rs @@ -0,0 +1,743 @@ + + + +fn validate(wat: &str) -> ValidatedModule { + let wasm = wabt::wat2wasm(wat).unwrap(); + let module = deserialize_buffer::(&wasm).unwrap(); + let validated_module = validate_module(module).unwrap(); + validated_module +} + +fn compile(module: &ValidatedModule) -> (Vec, Vec) { + let code = &module.code_map[0]; + let mut instructions = Vec::new(); + let mut pcs = Vec::new(); + let mut iter = code.iterate_from(0); + loop { + let pc = iter.position(); + if let Some(instruction) = iter.next() { + instructions.push(instruction.clone()); + pcs.push(pc); + } else { + break; + } + } + + (instructions, pcs) +} + +macro_rules! targets { + ($($target:expr),*) => { + ::isa::BrTargets::from_internal( + &[$($target,)*] + .iter() + .map(|&target| ::isa::InstructionInternal::BrTableTarget(target)) + .collect::>()[..] + ) + }; +} + +#[test] +fn implicit_return_no_value() { + let module = validate( + r#" + (module + (func (export "call") + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + })] + ) +} + +#[test] +fn implicit_return_with_value() { + let module = validate( + r#" + (module + (func (export "call") (result i32) + i32.const 0 + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(0), + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn implicit_return_param() { + let module = validate( + r#" + (module + (func (export "call") (param i32) + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::None, + }),] + ) +} + +#[test] +fn get_local() { + let module = validate( + r#" + (module + (func (export "call") (param i32) (result i32) + get_local 0 + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn explicit_return() { + let module = validate( + r#" + (module + (func (export "call") (param i32) (result i32) + get_local 0 + return + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn add_params() { + let module = validate( + r#" + (module + (func (export "call") (param i32) (param i32) (result i32) + get_local 0 + get_local 1 + i32.add + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + // This is tricky. Locals are now loaded from the stack. The load + // happens from address relative of the current stack pointer. The first load + // takes the value below the previous one (i.e the second argument) and then, it increments + // the stack pointer. And then the same thing hapens with the value below the previous one + // (which happens to be the value loaded by the first get_local). + isa::Instruction::GetLocal(2), + isa::Instruction::GetLocal(2), + isa::Instruction::I32Add, + isa::Instruction::Return(isa::DropKeep { + drop: 2, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn drop_locals() { + let module = validate( + r#" + (module + (func (export "call") (param i32) + (local i32) + get_local 0 + set_local 1 + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(2), + isa::Instruction::SetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 2, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_without_else() { + let module = validate( + r#" + (module + (func (export "call") (param i32) (result i32) + i32.const 1 + if + i32.const 2 + return + end + i32.const 3 + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: pcs[4], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Return(isa::DropKeep { + drop: 1, // 1 param + keep: isa::Keep::Single, // 1 result + }), + isa::Instruction::I32Const(3), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} + +#[test] +fn if_else() { + let module = validate( + r#" + (module + (func (export "call") + (local i32) + i32.const 1 + if + i32.const 2 + set_local 0 + else + i32.const 3 + set_local 0 + end + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: pcs[5], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::SetLocal(1), + isa::Instruction::Br(isa::Target { + dst_pc: pcs[7], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(3), + isa::Instruction::SetLocal(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_else_returns_result() { + let module = validate( + r#" + (module + (func (export "call") + i32.const 1 + if (result i32) + i32.const 2 + else + i32.const 3 + end + drop + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: pcs[4], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Br(isa::Target { + dst_pc: pcs[5], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(3), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_else_branch_from_true_branch() { + let module = validate( + r#" + (module + (func (export "call") + i32.const 1 + if (result i32) + i32.const 1 + i32.const 1 + br_if 0 + drop + i32.const 2 + else + i32.const 3 + end + drop + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: pcs[8], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(1), + isa::Instruction::I32Const(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: pcs[9], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }, + }), + isa::Instruction::Drop, + isa::Instruction::I32Const(2), + isa::Instruction::Br(isa::Target { + dst_pc: pcs[9], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(3), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn if_else_branch_from_false_branch() { + let module = validate( + r#" + (module + (func (export "call") + i32.const 1 + if (result i32) + i32.const 1 + else + i32.const 2 + i32.const 1 + br_if 0 + drop + i32.const 3 + end + drop + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfEqz(isa::Target { + dst_pc: pcs[4], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(1), + isa::Instruction::Br(isa::Target { + dst_pc: pcs[9], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::I32Const(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: pcs[9], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }, + }), + isa::Instruction::Drop, + isa::Instruction::I32Const(3), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn loop_() { + let module = validate( + r#" + (module + (func (export "call") + loop (result i32) + i32.const 1 + br_if 0 + i32.const 2 + end + drop + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: 0, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn loop_empty() { + let module = validate( + r#" + (module + (func (export "call") + loop + end + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }),] + ) +} + +#[test] +fn spec_as_br_if_value_cond() { + use self::isa::Instruction::*; + + let module = validate( + r#" + (func (export "as-br_if-value-cond") (result i32) + (block (result i32) + (drop + (br_if 0 + (i32.const 6) + (br_table 0 0 + (i32.const 9) + (i32.const 0) + ) + ) + ) + (i32.const 7) + ) + ) + "#, + ); + let (code, _) = compile(&module); + assert_eq!( + code, + vec![ + I32Const(6), + I32Const(9), + I32Const(0), + isa::Instruction::BrTable(targets![ + isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 1, + keep: isa::Keep::Single + } + }, + isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 1, + keep: isa::Keep::Single + } + } + ]), + BrIfNez(isa::Target { + dst_pc: 9, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single + } + }), + Drop, + I32Const(7), + Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::Single + }) + ] + ); +} + +#[test] +fn brtable() { + let module = validate( + r#" + (module + (func (export "call") + block $1 + loop $2 + i32.const 0 + br_table $2 $1 + end + end + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(0), + isa::Instruction::BrTable(targets![ + isa::Target { + dst_pc: 0, + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }, + isa::Target { + dst_pc: pcs[2], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + } + ]), + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn brtable_returns_result() { + let module = validate( + r#" + (module + (func (export "call") + block $1 (result i32) + block $2 (result i32) + i32.const 0 + i32.const 1 + br_table $2 $1 + end + unreachable + end + drop + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + println!("{:?}", (&code, &pcs)); + assert_eq!( + code, + vec![ + isa::Instruction::I32Const(0), + isa::Instruction::I32Const(1), + isa::Instruction::BrTable(targets![ + isa::Target { + dst_pc: pcs[3], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::Single, + }, + }, + isa::Target { + dst_pc: pcs[4], + drop_keep: isa::DropKeep { + keep: isa::Keep::Single, + drop: 0, + }, + } + ]), + isa::Instruction::Unreachable, + isa::Instruction::Drop, + isa::Instruction::Return(isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }), + ] + ) +} + +#[test] +fn wabt_example() { + let module = validate( + r#" + (module + (func (export "call") (param i32) (result i32) + block $exit + get_local 0 + br_if $exit + i32.const 1 + return + end + i32.const 2 + return + ) + ) + "#, + ); + let (code, pcs) = compile(&module); + assert_eq!( + code, + vec![ + isa::Instruction::GetLocal(1), + isa::Instruction::BrIfNez(isa::Target { + dst_pc: pcs[4], + drop_keep: isa::DropKeep { + drop: 0, + keep: isa::Keep::None, + }, + }), + isa::Instruction::I32Const(1), + isa::Instruction::Return(isa::DropKeep { + drop: 1, // 1 parameter + keep: isa::Keep::Single, + }), + isa::Instruction::I32Const(2), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + isa::Instruction::Return(isa::DropKeep { + drop: 1, + keep: isa::Keep::Single, + }), + ] + ) +} diff --git a/test.sh b/test.sh index 3cef9ad..4ee4c32 100755 --- a/test.sh +++ b/test.sh @@ -4,6 +4,6 @@ set -eux cd $(dirname $0) -time cargo test +time cargo test --all cd - diff --git a/validation/Cargo.toml b/validation/Cargo.toml index a5775fa..bf71c92 100644 --- a/validation/Cargo.toml +++ b/validation/Cargo.toml @@ -9,6 +9,9 @@ parity-wasm = { version = "0.31", default-features = false } memory_units_crate = { package = "memory_units", version = "0.3.0" } hashbrown = { version = "0.1.8", optional = true } +[dev-dependencies] +assert_matches = "1.1" + [features] default = ["std"] std = ["parity-wasm/std"] diff --git a/validation/src/lib.rs b/validation/src/lib.rs index 7e0f957..24bc2e6 100644 --- a/validation/src/lib.rs +++ b/validation/src/lib.rs @@ -50,9 +50,8 @@ pub mod context; pub mod func; pub mod util; -// TODO: Uncomment -// #[cfg(test)] -// mod tests; +#[cfg(test)] +mod tests; // TODO: Consider using a type other than String, because // of formatting machinary is not welcomed in substrate runtimes. @@ -101,6 +100,44 @@ pub trait FunctionValidator { fn finish(self) -> Self::Output; } +impl Validation for () { + type Output = (); + type FunctionValidator = (); + fn new(module: &Module) -> () { + () + } + fn on_function_validated( + &mut self, + index: u32, + output: <::FunctionValidator as FunctionValidator>::Output, + ) -> () { + () + } + fn finish(self) -> () { + () + } +} + +impl FunctionValidator for () { + type Output = (); + + fn new(ctx: &func::FunctionValidationContext) -> () { + () + } + + fn next_instruction( + &mut self, + ctx: &mut func::FunctionValidationContext, + instruction: &Instruction, + ) -> Result<(), Error> { + Ok(()) + } + + fn finish(self) -> () { + () + } +} + pub fn validate_module(module: &Module) -> Result { let mut context_builder = ModuleContextBuilder::new(); let mut imported_globals = Vec::new(); diff --git a/validation/src/tests.rs b/validation/src/tests.rs index c63b380..badfeb5 100644 --- a/validation/src/tests.rs +++ b/validation/src/tests.rs @@ -1,16 +1,18 @@ -use super::{validate_module, ValidatedModule}; -use isa; +use crate::Error; use parity_wasm::builder::module; use parity_wasm::elements::{ - deserialize_buffer, BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, - Instruction, Instructions, MemoryType, Module, TableType, ValueType, + BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction, Instructions, + MemoryType, Module, TableType, ValueType, }; -use wabt; + +fn validate_module(module: &Module) -> Result<(), Error> { + super::validate_module::<()>(module) +} #[test] fn empty_is_valid() { let module = module().build(); - assert!(validate_module(module).is_ok()); + assert!(validate_module(&module).is_ok()); } #[test] @@ -27,7 +29,7 @@ fn limits() { for (min, max, is_valid) in test_cases { // defined table let m = module().table().with_min(min).with_max(max).build().build(); - assert_eq!(validate_module(m).is_ok(), is_valid); + assert_eq!(validate_module(&m).is_ok(), is_valid); // imported table let m = module() @@ -37,7 +39,7 @@ fn limits() { External::Table(TableType::new(min, max)), )) .build(); - assert_eq!(validate_module(m).is_ok(), is_valid); + assert_eq!(validate_module(&m).is_ok(), is_valid); // defined memory let m = module() @@ -46,7 +48,7 @@ fn limits() { .with_max(max) .build() .build(); - assert_eq!(validate_module(m).is_ok(), is_valid); + assert_eq!(validate_module(&m).is_ok(), is_valid); // imported table let m = module() @@ -56,7 +58,7 @@ fn limits() { External::Memory(MemoryType::new(min, max)), )) .build(); - assert_eq!(validate_module(m).is_ok(), is_valid); + assert_eq!(validate_module(&m).is_ok(), is_valid); } } @@ -68,7 +70,7 @@ fn global_init_const() { InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]), )) .build(); - assert!(validate_module(m).is_ok()); + assert!(validate_module(&m).is_ok()); // init expr type differs from declared global type let m = module() @@ -77,7 +79,7 @@ fn global_init_const() { InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); } #[test] @@ -93,7 +95,7 @@ fn global_init_global() { InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), )) .build(); - assert!(validate_module(m).is_ok()); + assert!(validate_module(&m).is_ok()); // get_global can reference only previously defined globals let m = module() @@ -102,7 +104,7 @@ fn global_init_global() { InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); // get_global can reference only const globals let m = module() @@ -116,7 +118,7 @@ fn global_init_global() { InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); // get_global in init_expr can only refer to imported globals. let m = module() @@ -129,7 +131,7 @@ fn global_init_global() { InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); } #[test] @@ -141,7 +143,7 @@ fn global_init_misc() { InitExpr::new(vec![Instruction::I32Const(42)]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); // empty init expr let m = module() @@ -150,7 +152,7 @@ fn global_init_misc() { InitExpr::new(vec![Instruction::End]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); // not an constant opcode used let m = module() @@ -159,7 +161,7 @@ fn global_init_misc() { InitExpr::new(vec![Instruction::Unreachable, Instruction::End]), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); } #[test] @@ -175,7 +177,7 @@ fn module_limits_validity() { .with_min(10) .build() .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); // module cannot contain more than 1 table atm. let m = module() @@ -188,7 +190,7 @@ fn module_limits_validity() { .with_min(10) .build() .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); } #[test] @@ -220,7 +222,7 @@ fn funcs() { .build() .build() .build(); - assert!(validate_module(m).is_ok()); + assert!(validate_module(&m).is_ok()); } #[test] @@ -233,7 +235,7 @@ fn globals() { External::Global(GlobalType::new(ValueType::I32, false)), )) .build(); - assert!(validate_module(m).is_ok()); + assert!(validate_module(&m).is_ok()); // import mutable global is invalid. let m = module() @@ -243,7 +245,7 @@ fn globals() { External::Global(GlobalType::new(ValueType::I32, true)), )) .build(); - assert!(validate_module(m).is_err()); + assert!(validate_module(&m).is_err()); } #[test] @@ -269,746 +271,5 @@ fn if_else_with_return_type_validation() { .build() .build() .build(); - validate_module(m).unwrap(); -} - -fn validate(wat: &str) -> ValidatedModule { - let wasm = wabt::wat2wasm(wat).unwrap(); - let module = deserialize_buffer::(&wasm).unwrap(); - let validated_module = validate_module(module).unwrap(); - validated_module -} - -fn compile(module: &ValidatedModule) -> (Vec, Vec) { - let code = &module.code_map[0]; - let mut instructions = Vec::new(); - let mut pcs = Vec::new(); - let mut iter = code.iterate_from(0); - loop { - let pc = iter.position(); - if let Some(instruction) = iter.next() { - instructions.push(instruction.clone()); - pcs.push(pc); - } else { - break; - } - } - - (instructions, pcs) -} - -macro_rules! targets { - ($($target:expr),*) => { - ::isa::BrTargets::from_internal( - &[$($target,)*] - .iter() - .map(|&target| ::isa::InstructionInternal::BrTableTarget(target)) - .collect::>()[..] - ) - }; -} - -#[test] -fn implicit_return_no_value() { - let module = validate( - r#" - (module - (func (export "call") - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - })] - ) -} - -#[test] -fn implicit_return_with_value() { - let module = validate( - r#" - (module - (func (export "call") (result i32) - i32.const 0 - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(0), - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::Single, - }), - ] - ) -} - -#[test] -fn implicit_return_param() { - let module = validate( - r#" - (module - (func (export "call") (param i32) - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::None, - }),] - ) -} - -#[test] -fn get_local() { - let module = validate( - r#" - (module - (func (export "call") (param i32) (result i32) - get_local 0 - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::GetLocal(1), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::Single, - }), - ] - ) -} - -#[test] -fn explicit_return() { - let module = validate( - r#" - (module - (func (export "call") (param i32) (result i32) - get_local 0 - return - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::GetLocal(1), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::Single, - }), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::Single, - }), - ] - ) -} - -#[test] -fn add_params() { - let module = validate( - r#" - (module - (func (export "call") (param i32) (param i32) (result i32) - get_local 0 - get_local 1 - i32.add - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - // This is tricky. Locals are now loaded from the stack. The load - // happens from address relative of the current stack pointer. The first load - // takes the value below the previous one (i.e the second argument) and then, it increments - // the stack pointer. And then the same thing hapens with the value below the previous one - // (which happens to be the value loaded by the first get_local). - isa::Instruction::GetLocal(2), - isa::Instruction::GetLocal(2), - isa::Instruction::I32Add, - isa::Instruction::Return(isa::DropKeep { - drop: 2, - keep: isa::Keep::Single, - }), - ] - ) -} - -#[test] -fn drop_locals() { - let module = validate( - r#" - (module - (func (export "call") (param i32) - (local i32) - get_local 0 - set_local 1 - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::GetLocal(2), - isa::Instruction::SetLocal(1), - isa::Instruction::Return(isa::DropKeep { - drop: 2, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn if_without_else() { - let module = validate( - r#" - (module - (func (export "call") (param i32) (result i32) - i32.const 1 - if - i32.const 2 - return - end - i32.const 3 - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(1), - isa::Instruction::BrIfEqz(isa::Target { - dst_pc: pcs[4], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(2), - isa::Instruction::Return(isa::DropKeep { - drop: 1, // 1 param - keep: isa::Keep::Single, // 1 result - }), - isa::Instruction::I32Const(3), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::Single, - }), - ] - ) -} - -#[test] -fn if_else() { - let module = validate( - r#" - (module - (func (export "call") - (local i32) - i32.const 1 - if - i32.const 2 - set_local 0 - else - i32.const 3 - set_local 0 - end - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(1), - isa::Instruction::BrIfEqz(isa::Target { - dst_pc: pcs[5], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(2), - isa::Instruction::SetLocal(1), - isa::Instruction::Br(isa::Target { - dst_pc: pcs[7], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(3), - isa::Instruction::SetLocal(1), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn if_else_returns_result() { - let module = validate( - r#" - (module - (func (export "call") - i32.const 1 - if (result i32) - i32.const 2 - else - i32.const 3 - end - drop - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(1), - isa::Instruction::BrIfEqz(isa::Target { - dst_pc: pcs[4], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(2), - isa::Instruction::Br(isa::Target { - dst_pc: pcs[5], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(3), - isa::Instruction::Drop, - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn if_else_branch_from_true_branch() { - let module = validate( - r#" - (module - (func (export "call") - i32.const 1 - if (result i32) - i32.const 1 - i32.const 1 - br_if 0 - drop - i32.const 2 - else - i32.const 3 - end - drop - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(1), - isa::Instruction::BrIfEqz(isa::Target { - dst_pc: pcs[8], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(1), - isa::Instruction::I32Const(1), - isa::Instruction::BrIfNez(isa::Target { - dst_pc: pcs[9], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::Single, - }, - }), - isa::Instruction::Drop, - isa::Instruction::I32Const(2), - isa::Instruction::Br(isa::Target { - dst_pc: pcs[9], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(3), - isa::Instruction::Drop, - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn if_else_branch_from_false_branch() { - let module = validate( - r#" - (module - (func (export "call") - i32.const 1 - if (result i32) - i32.const 1 - else - i32.const 2 - i32.const 1 - br_if 0 - drop - i32.const 3 - end - drop - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(1), - isa::Instruction::BrIfEqz(isa::Target { - dst_pc: pcs[4], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(1), - isa::Instruction::Br(isa::Target { - dst_pc: pcs[9], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(2), - isa::Instruction::I32Const(1), - isa::Instruction::BrIfNez(isa::Target { - dst_pc: pcs[9], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::Single, - }, - }), - isa::Instruction::Drop, - isa::Instruction::I32Const(3), - isa::Instruction::Drop, - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn loop_() { - let module = validate( - r#" - (module - (func (export "call") - loop (result i32) - i32.const 1 - br_if 0 - i32.const 2 - end - drop - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(1), - isa::Instruction::BrIfNez(isa::Target { - dst_pc: 0, - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(2), - isa::Instruction::Drop, - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn loop_empty() { - let module = validate( - r#" - (module - (func (export "call") - loop - end - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }),] - ) -} - -#[test] -fn spec_as_br_if_value_cond() { - use self::isa::Instruction::*; - - let module = validate( - r#" - (func (export "as-br_if-value-cond") (result i32) - (block (result i32) - (drop - (br_if 0 - (i32.const 6) - (br_table 0 0 - (i32.const 9) - (i32.const 0) - ) - ) - ) - (i32.const 7) - ) - ) - "#, - ); - let (code, _) = compile(&module); - assert_eq!( - code, - vec![ - I32Const(6), - I32Const(9), - I32Const(0), - isa::Instruction::BrTable(targets![ - isa::Target { - dst_pc: 9, - drop_keep: isa::DropKeep { - drop: 1, - keep: isa::Keep::Single - } - }, - isa::Target { - dst_pc: 9, - drop_keep: isa::DropKeep { - drop: 1, - keep: isa::Keep::Single - } - } - ]), - BrIfNez(isa::Target { - dst_pc: 9, - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::Single - } - }), - Drop, - I32Const(7), - Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::Single - }) - ] - ); -} - -#[test] -fn brtable() { - let module = validate( - r#" - (module - (func (export "call") - block $1 - loop $2 - i32.const 0 - br_table $2 $1 - end - end - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(0), - isa::Instruction::BrTable(targets![ - isa::Target { - dst_pc: 0, - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }, - isa::Target { - dst_pc: pcs[2], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - } - ]), - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn brtable_returns_result() { - let module = validate( - r#" - (module - (func (export "call") - block $1 (result i32) - block $2 (result i32) - i32.const 0 - i32.const 1 - br_table $2 $1 - end - unreachable - end - drop - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - println!("{:?}", (&code, &pcs)); - assert_eq!( - code, - vec![ - isa::Instruction::I32Const(0), - isa::Instruction::I32Const(1), - isa::Instruction::BrTable(targets![ - isa::Target { - dst_pc: pcs[3], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::Single, - }, - }, - isa::Target { - dst_pc: pcs[4], - drop_keep: isa::DropKeep { - keep: isa::Keep::Single, - drop: 0, - }, - } - ]), - isa::Instruction::Unreachable, - isa::Instruction::Drop, - isa::Instruction::Return(isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }), - ] - ) -} - -#[test] -fn wabt_example() { - let module = validate( - r#" - (module - (func (export "call") (param i32) (result i32) - block $exit - get_local 0 - br_if $exit - i32.const 1 - return - end - i32.const 2 - return - ) - ) - "#, - ); - let (code, pcs) = compile(&module); - assert_eq!( - code, - vec![ - isa::Instruction::GetLocal(1), - isa::Instruction::BrIfNez(isa::Target { - dst_pc: pcs[4], - drop_keep: isa::DropKeep { - drop: 0, - keep: isa::Keep::None, - }, - }), - isa::Instruction::I32Const(1), - isa::Instruction::Return(isa::DropKeep { - drop: 1, // 1 parameter - keep: isa::Keep::Single, - }), - isa::Instruction::I32Const(2), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::Single, - }), - isa::Instruction::Return(isa::DropKeep { - drop: 1, - keep: isa::Keep::Single, - }), - ] - ) + validate_module(&m).unwrap(); } diff --git a/validation/src/util.rs b/validation/src/util.rs index 3fb6c1c..5f320e9 100644 --- a/validation/src/util.rs +++ b/validation/src/util.rs @@ -3,6 +3,9 @@ use crate::Error; use alloc::prelude::v1::*; use parity_wasm::elements::{Local, ValueType}; +#[cfg(test)] +use assert_matches::assert_matches; + /// Locals are the concatenation of a slice of function parameters /// with function declared local variables. ///