Make validation tests work
This commit is contained in:
parent
e78f8ad37e
commit
584b1fd2e9
|
@ -0,0 +1,743 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn validate(wat: &str) -> ValidatedModule {
|
||||||
|
let wasm = wabt::wat2wasm(wat).unwrap();
|
||||||
|
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
||||||
|
let validated_module = validate_module(module).unwrap();
|
||||||
|
validated_module
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile(module: &ValidatedModule) -> (Vec<isa::Instruction>, Vec<u32>) {
|
||||||
|
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::<Vec<_>>()[..]
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
2
test.sh
2
test.sh
|
@ -4,6 +4,6 @@ set -eux
|
||||||
|
|
||||||
cd $(dirname $0)
|
cd $(dirname $0)
|
||||||
|
|
||||||
time cargo test
|
time cargo test --all
|
||||||
|
|
||||||
cd -
|
cd -
|
||||||
|
|
|
@ -9,6 +9,9 @@ parity-wasm = { version = "0.31", default-features = false }
|
||||||
memory_units_crate = { package = "memory_units", version = "0.3.0" }
|
memory_units_crate = { package = "memory_units", version = "0.3.0" }
|
||||||
hashbrown = { version = "0.1.8", optional = true }
|
hashbrown = { version = "0.1.8", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
assert_matches = "1.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["std"]
|
default = ["std"]
|
||||||
std = ["parity-wasm/std"]
|
std = ["parity-wasm/std"]
|
||||||
|
|
|
@ -50,9 +50,8 @@ pub mod context;
|
||||||
pub mod func;
|
pub mod func;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|
||||||
// TODO: Uncomment
|
#[cfg(test)]
|
||||||
// #[cfg(test)]
|
mod tests;
|
||||||
// mod tests;
|
|
||||||
|
|
||||||
// TODO: Consider using a type other than String, because
|
// TODO: Consider using a type other than String, because
|
||||||
// of formatting machinary is not welcomed in substrate runtimes.
|
// of formatting machinary is not welcomed in substrate runtimes.
|
||||||
|
@ -101,6 +100,44 @@ pub trait FunctionValidator {
|
||||||
fn finish(self) -> Self::Output;
|
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: <<Self as Validation>::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<V: Validation>(module: &Module) -> Result<V::Output, Error> {
|
pub fn validate_module<V: Validation>(module: &Module) -> Result<V::Output, Error> {
|
||||||
let mut context_builder = ModuleContextBuilder::new();
|
let mut context_builder = ModuleContextBuilder::new();
|
||||||
let mut imported_globals = Vec::new();
|
let mut imported_globals = Vec::new();
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
use super::{validate_module, ValidatedModule};
|
use crate::Error;
|
||||||
use isa;
|
|
||||||
use parity_wasm::builder::module;
|
use parity_wasm::builder::module;
|
||||||
use parity_wasm::elements::{
|
use parity_wasm::elements::{
|
||||||
deserialize_buffer, BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr,
|
BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr, Instruction, Instructions,
|
||||||
Instruction, Instructions, MemoryType, Module, TableType, ValueType,
|
MemoryType, Module, TableType, ValueType,
|
||||||
};
|
};
|
||||||
use wabt;
|
|
||||||
|
fn validate_module(module: &Module) -> Result<(), Error> {
|
||||||
|
super::validate_module::<()>(module)
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_is_valid() {
|
fn empty_is_valid() {
|
||||||
let module = module().build();
|
let module = module().build();
|
||||||
assert!(validate_module(module).is_ok());
|
assert!(validate_module(&module).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -27,7 +29,7 @@ fn limits() {
|
||||||
for (min, max, is_valid) in test_cases {
|
for (min, max, is_valid) in test_cases {
|
||||||
// defined table
|
// defined table
|
||||||
let m = module().table().with_min(min).with_max(max).build().build();
|
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
|
// imported table
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -37,7 +39,7 @@ fn limits() {
|
||||||
External::Table(TableType::new(min, max)),
|
External::Table(TableType::new(min, max)),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert_eq!(validate_module(m).is_ok(), is_valid);
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
// defined memory
|
// defined memory
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -46,7 +48,7 @@ fn limits() {
|
||||||
.with_max(max)
|
.with_max(max)
|
||||||
.build()
|
.build()
|
||||||
.build();
|
.build();
|
||||||
assert_eq!(validate_module(m).is_ok(), is_valid);
|
assert_eq!(validate_module(&m).is_ok(), is_valid);
|
||||||
|
|
||||||
// imported table
|
// imported table
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -56,7 +58,7 @@ fn limits() {
|
||||||
External::Memory(MemoryType::new(min, max)),
|
External::Memory(MemoryType::new(min, max)),
|
||||||
))
|
))
|
||||||
.build();
|
.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]),
|
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_ok());
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
// init expr type differs from declared global type
|
// init expr type differs from declared global type
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -77,7 +79,7 @@ fn global_init_const() {
|
||||||
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -93,7 +95,7 @@ fn global_init_global() {
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_ok());
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
// get_global can reference only previously defined globals
|
// get_global can reference only previously defined globals
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -102,7 +104,7 @@ fn global_init_global() {
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
// get_global can reference only const globals
|
// get_global can reference only const globals
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -116,7 +118,7 @@ fn global_init_global() {
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
// get_global in init_expr can only refer to imported globals.
|
// get_global in init_expr can only refer to imported globals.
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -129,7 +131,7 @@ fn global_init_global() {
|
||||||
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -141,7 +143,7 @@ fn global_init_misc() {
|
||||||
InitExpr::new(vec![Instruction::I32Const(42)]),
|
InitExpr::new(vec![Instruction::I32Const(42)]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
// empty init expr
|
// empty init expr
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -150,7 +152,7 @@ fn global_init_misc() {
|
||||||
InitExpr::new(vec![Instruction::End]),
|
InitExpr::new(vec![Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
// not an constant opcode used
|
// not an constant opcode used
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -159,7 +161,7 @@ fn global_init_misc() {
|
||||||
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
|
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -175,7 +177,7 @@ fn module_limits_validity() {
|
||||||
.with_min(10)
|
.with_min(10)
|
||||||
.build()
|
.build()
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
|
|
||||||
// module cannot contain more than 1 table atm.
|
// module cannot contain more than 1 table atm.
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -188,7 +190,7 @@ fn module_limits_validity() {
|
||||||
.with_min(10)
|
.with_min(10)
|
||||||
.build()
|
.build()
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -220,7 +222,7 @@ fn funcs() {
|
||||||
.build()
|
.build()
|
||||||
.build()
|
.build()
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_ok());
|
assert!(validate_module(&m).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -233,7 +235,7 @@ fn globals() {
|
||||||
External::Global(GlobalType::new(ValueType::I32, false)),
|
External::Global(GlobalType::new(ValueType::I32, false)),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_ok());
|
assert!(validate_module(&m).is_ok());
|
||||||
|
|
||||||
// import mutable global is invalid.
|
// import mutable global is invalid.
|
||||||
let m = module()
|
let m = module()
|
||||||
|
@ -243,7 +245,7 @@ fn globals() {
|
||||||
External::Global(GlobalType::new(ValueType::I32, true)),
|
External::Global(GlobalType::new(ValueType::I32, true)),
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
assert!(validate_module(m).is_err());
|
assert!(validate_module(&m).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -269,746 +271,5 @@ fn if_else_with_return_type_validation() {
|
||||||
.build()
|
.build()
|
||||||
.build()
|
.build()
|
||||||
.build();
|
.build();
|
||||||
validate_module(m).unwrap();
|
validate_module(&m).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
fn validate(wat: &str) -> ValidatedModule {
|
|
||||||
let wasm = wabt::wat2wasm(wat).unwrap();
|
|
||||||
let module = deserialize_buffer::<Module>(&wasm).unwrap();
|
|
||||||
let validated_module = validate_module(module).unwrap();
|
|
||||||
validated_module
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile(module: &ValidatedModule) -> (Vec<isa::Instruction>, Vec<u32>) {
|
|
||||||
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::<Vec<_>>()[..]
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ use crate::Error;
|
||||||
use alloc::prelude::v1::*;
|
use alloc::prelude::v1::*;
|
||||||
use parity_wasm::elements::{Local, ValueType};
|
use parity_wasm::elements::{Local, ValueType};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
|
||||||
/// Locals are the concatenation of a slice of function parameters
|
/// Locals are the concatenation of a slice of function parameters
|
||||||
/// with function declared local variables.
|
/// with function declared local variables.
|
||||||
///
|
///
|
||||||
|
|
Loading…
Reference in New Issue