use super::{validate_module, ValidatedModule}; use parity_wasm::builder::module; use parity_wasm::elements::{ External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType, Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer, Module, }; use isa; use wabt; #[test] fn empty_is_valid() { let module = module().build(); assert!(validate_module(module).is_ok()); } #[test] fn limits() { let test_cases = vec![ // min > max (10, Some(9), false), // min = max (10, Some(10), true), // table/memory is always valid without max (10, None, true), ]; 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); // imported table let m = module() .with_import( ImportEntry::new( "core".into(), "table".into(), External::Table(TableType::new(min, max)) ) ) .build(); assert_eq!(validate_module(m).is_ok(), is_valid); // defined memory let m = module() .memory() .with_min(min) .with_max(max) .build() .build(); assert_eq!(validate_module(m).is_ok(), is_valid); // imported table let m = module() .with_import( ImportEntry::new( "core".into(), "memory".into(), External::Memory(MemoryType::new(min, max)) ) ) .build(); assert_eq!(validate_module(m).is_ok(), is_valid); } } #[test] fn global_init_const() { let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new( vec![Opcode::I32Const(42), Opcode::End] ) ) ) .build(); assert!(validate_module(m).is_ok()); // init expr type differs from declared global type let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I64, true), InitExpr::new(vec![Opcode::I32Const(42), Opcode::End]) ) ) .build(); assert!(validate_module(m).is_err()); } #[test] fn global_init_global() { let m = module() .with_import( ImportEntry::new( "env".into(), "ext_global".into(), External::Global(GlobalType::new(ValueType::I32, false)) ) ) .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End]) ) ) .build(); assert!(validate_module(m).is_ok()); // get_global can reference only previously defined globals let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End]) ) ) .build(); assert!(validate_module(m).is_err()); // get_global can reference only const globals let m = module() .with_import( ImportEntry::new( "env".into(), "ext_global".into(), External::Global(GlobalType::new(ValueType::I32, true)) ) ) .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End]) ) ) .build(); assert!(validate_module(m).is_err()); // get_global in init_expr can only refer to imported globals. let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, false), InitExpr::new(vec![Opcode::I32Const(0), Opcode::End]) ) ) .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::GetGlobal(0), Opcode::End]) ) ) .build(); assert!(validate_module(m).is_err()); } #[test] fn global_init_misc() { // without delimiting End opcode let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::I32Const(42)]) ) ) .build(); assert!(validate_module(m).is_err()); // empty init expr let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::End]) ) ) .build(); assert!(validate_module(m).is_err()); // not an constant opcode used let m = module() .with_global( GlobalEntry::new( GlobalType::new(ValueType::I32, true), InitExpr::new(vec![Opcode::Unreachable, Opcode::End]) ) ) .build(); assert!(validate_module(m).is_err()); } #[test] fn module_limits_validity() { // module cannot contain more than 1 memory atm. let m = module() .with_import( ImportEntry::new( "core".into(), "memory".into(), External::Memory(MemoryType::new(10, None)) ) ) .memory() .with_min(10) .build() .build(); assert!(validate_module(m).is_err()); // module cannot contain more than 1 table atm. let m = module() .with_import( ImportEntry::new( "core".into(), "table".into(), External::Table(TableType::new(10, None)) ) ) .table() .with_min(10) .build() .build(); assert!(validate_module(m).is_err()); } #[test] fn funcs() { // recursive function calls is legal. let m = module() .function() .signature().return_type().i32().build() .body().with_opcodes(Opcodes::new(vec![ Opcode::Call(1), Opcode::End, ])).build() .build() .function() .signature().return_type().i32().build() .body().with_opcodes(Opcodes::new(vec![ Opcode::Call(0), Opcode::End, ])).build() .build() .build(); assert!(validate_module(m).is_ok()); } #[test] fn globals() { // import immutable global is legal. let m = module() .with_import( ImportEntry::new( "env".into(), "ext_global".into(), External::Global(GlobalType::new(ValueType::I32, false)) ) ) .build(); assert!(validate_module(m).is_ok()); // import mutable global is invalid. let m = module() .with_import( ImportEntry::new( "env".into(), "ext_global".into(), External::Global(GlobalType::new(ValueType::I32, true)) ) ) .build(); assert!(validate_module(m).is_err()); } #[test] fn if_else_with_return_type_validation() { let m = module() .function() .signature().build() .body().with_opcodes(Opcodes::new(vec![ Opcode::I32Const(1), Opcode::If(BlockType::NoResult), Opcode::I32Const(1), Opcode::If(BlockType::Value(ValueType::I32)), Opcode::I32Const(1), Opcode::Else, Opcode::I32Const(2), Opcode::End, Opcode::Drop, Opcode::End, Opcode::End, ])).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(wat: &str) -> Vec { let validated_module = validate(wat); let code = &validated_module.code_map[0]; code.code.clone() } #[test] fn implicit_return_no_value() { let code = compile(r#" (module (func (export "call") ) ) "#); assert_eq!( code, vec![ isa::Instruction::Return { drop: 0, keep: 0, } ] ) } #[test] fn implicit_return_with_value() { let code = compile(r#" (module (func (export "call") (result i32) i32.const 0 ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(0), isa::Instruction::Return { drop: 0, keep: 1, } ] ) } #[test] fn implicit_return_param() { let code = compile(r#" (module (func (export "call") (param i32) ) ) "#); assert_eq!( code, vec![ isa::Instruction::Return { drop: 1, keep: 0, } ] ) } #[test] fn get_local() { let code = compile(r#" (module (func (export "call") (param i32) (result i32) get_local 0 ) ) "#); assert_eq!( code, vec![ isa::Instruction::GetLocal(1), isa::Instruction::Return { drop: 1, keep: 1, } ] ) } #[test] fn explicit_return() { let code = compile(r#" (module (func (export "call") (param i32) (result i32) get_local 0 return ) ) "#); assert_eq!( code, vec![ isa::Instruction::GetLocal(1), isa::Instruction::Return { drop: 1, keep: 1, }, isa::Instruction::Return { drop: 1, keep: 1, } ] ) } #[test] fn add_params() { let code = compile(r#" (module (func (export "call") (param i32) (param i32) (result i32) get_local 0 get_local 1 i32.add ) ) "#); 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 { drop: 2, keep: 1, } ] ) } #[test] fn drop_locals() { let code = compile(r#" (module (func (export "call") (param i32) (local i32) get_local 0 set_local 1 ) ) "#); assert_eq!( code, vec![ isa::Instruction::GetLocal(2), isa::Instruction::SetLocal(1), isa::Instruction::Return { drop: 2, keep: 0, } ] ) } #[test] fn if_without_else() { let code = compile(r#" (module (func (export "call") (param i32) (result i32) i32.const 1 if i32.const 2 return end i32.const 3 ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(1), isa::Instruction::BrIfEqz(isa::Target { dst_pc: 4, drop: 0, keep: 0, }), isa::Instruction::I32Const(2), isa::Instruction::Return { drop: 1, // 1 param keep: 1, // 1 result }, isa::Instruction::I32Const(3), isa::Instruction::Return { drop: 1, keep: 1, }, ] ) } #[test] fn if_else() { let code = compile(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 ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(1), isa::Instruction::BrIfEqz(isa::Target { dst_pc: 5, drop: 0, keep: 0, }), isa::Instruction::I32Const(2), isa::Instruction::SetLocal(1), isa::Instruction::Br(isa::Target { dst_pc: 7, drop: 0, keep: 0, }), isa::Instruction::I32Const(3), isa::Instruction::SetLocal(1), isa::Instruction::Return { drop: 1, keep: 0, }, ] ) } #[test] fn if_else_returns_result() { let code = compile(r#" (module (func (export "call") i32.const 1 if (result i32) i32.const 2 else i32.const 3 end drop ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(1), isa::Instruction::BrIfEqz(isa::Target { dst_pc: 4, drop: 0, keep: 0, }), isa::Instruction::I32Const(2), isa::Instruction::Br(isa::Target { dst_pc: 5, drop: 0, keep: 0, }), isa::Instruction::I32Const(3), isa::Instruction::Drop, isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn if_else_branch_from_true_branch() { let code = compile(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 ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(1), isa::Instruction::BrIfEqz(isa::Target { dst_pc: 8, drop: 0, keep: 0, }), isa::Instruction::I32Const(1), isa::Instruction::I32Const(1), isa::Instruction::BrIfNez(isa::Target { dst_pc: 9, drop: 0, keep: 1, }), isa::Instruction::Drop, isa::Instruction::I32Const(2), isa::Instruction::Br(isa::Target { dst_pc: 9, drop: 0, keep: 0, }), isa::Instruction::I32Const(3), isa::Instruction::Drop, isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn if_else_branch_from_false_branch() { let code = compile(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 ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(1), isa::Instruction::BrIfEqz(isa::Target { dst_pc: 4, drop: 0, keep: 0, }), isa::Instruction::I32Const(1), isa::Instruction::Br(isa::Target { dst_pc: 9, drop: 0, keep: 0, }), isa::Instruction::I32Const(2), isa::Instruction::I32Const(1), isa::Instruction::BrIfNez(isa::Target { dst_pc: 9, drop: 0, keep: 1, }), isa::Instruction::Drop, isa::Instruction::I32Const(3), isa::Instruction::Drop, isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn loop_() { let code = compile(r#" (module (func (export "call") loop (result i32) i32.const 1 br_if 0 i32.const 2 end drop ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(1), isa::Instruction::BrIfNez(isa::Target { dst_pc: 0, drop: 0, keep: 0, }), isa::Instruction::I32Const(2), isa::Instruction::Drop, isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn loop_empty() { let code = compile(r#" (module (func (export "call") loop end ) ) "#); assert_eq!( code, vec![ isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn brtable() { let code = compile(r#" (module (func (export "call") block $1 loop $2 i32.const 0 br_table $2 $1 end end ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(0), isa::Instruction::BrTable( vec![ isa::Target { dst_pc: 0, keep: 0, drop: 0, }, isa::Target { dst_pc: 2, keep: 0, drop: 0, }, ].into_boxed_slice() ), isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn brtable_returns_result() { let code = compile(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 ) ) "#); assert_eq!( code, vec![ isa::Instruction::I32Const(0), isa::Instruction::I32Const(1), isa::Instruction::BrTable( vec![ isa::Target { dst_pc: 3, keep: 1, drop: 0, }, isa::Target { dst_pc: 4, keep: 1, drop: 0, }, ].into_boxed_slice() ), isa::Instruction::Unreachable, isa::Instruction::Drop, isa::Instruction::Return { drop: 0, keep: 0, }, ] ) } #[test] fn wabt_example() { let code = compile(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 ) ) "#); assert_eq!( code, vec![ isa::Instruction::GetLocal(1), isa::Instruction::BrIfNez(isa::Target { dst_pc: 4, keep: 0, drop: 0, }), isa::Instruction::I32Const(1), isa::Instruction::Return { drop: 1, // 1 parameter keep: 1, // return value }, isa::Instruction::I32Const(2), isa::Instruction::Return { drop: 1, keep: 1, }, isa::Instruction::Return { drop: 1, keep: 1, }, ] ) }