wasmi/src/prepare/tests.rs

747 lines
17 KiB
Rust
Raw Normal View History

Extract validation into a separate crate (#176) * Add some docs. * return_type isn't failable * Add comment about safety of top_label * Attempt number 10 * Rework. Now we will a compiler which wraps and uses info from a evaluation simulator. * Get rid of outcome * Introduce StartedWith * Actually use started_with. * Mirror label_stack. * Avoid using frame_type. * Finally get rid from frame_type. * Extract compilation * Refactoring cleaning * Validation separated from compilation. * Move sink to FunctionReader * Rename to compiler. * fmt * Move push_label under validation context. * Add Validation traits * Express the compiler using validation trait * Move code under prepare * Comments. * WIP * The great move of validation * Make validation compile * Make it compile. * Format it. * Fix warnings. * Clean. * Make it work under no_std * Move deny_floating_point to wasmi * Rename validate_module2 → validate_module * Make validation tests work * Make wasmi compilation tests work * Renamings. * Get rid of memory_units dependency in validation * Rename. * Clean. * Estimate capacity. * fmt. * Clean and detail End opcode. * Add comment about top_label safety * Remove another TODO * Comment access to require_target * Remove redundant PartialEq * Print value that can't be coerced to u32 * s/with_instruction_capacity/with_capacity * fmt. * fmt * Proofs * Add better proof * Get rid of unreachable in StackValueType * Propagate error if frame stack overflown on create * use checked sub instead of - * Keep::count
2019-04-19 14:05:09 +00:00
use super::{compile_module, CompiledModule};
use parity_wasm::{deserialize_buffer, elements::Module};
use isa;
use wabt;
2018-01-17 15:32:33 +00:00
Extract validation into a separate crate (#176) * Add some docs. * return_type isn't failable * Add comment about safety of top_label * Attempt number 10 * Rework. Now we will a compiler which wraps and uses info from a evaluation simulator. * Get rid of outcome * Introduce StartedWith * Actually use started_with. * Mirror label_stack. * Avoid using frame_type. * Finally get rid from frame_type. * Extract compilation * Refactoring cleaning * Validation separated from compilation. * Move sink to FunctionReader * Rename to compiler. * fmt * Move push_label under validation context. * Add Validation traits * Express the compiler using validation trait * Move code under prepare * Comments. * WIP * The great move of validation * Make validation compile * Make it compile. * Format it. * Fix warnings. * Clean. * Make it work under no_std * Move deny_floating_point to wasmi * Rename validate_module2 → validate_module * Make validation tests work * Make wasmi compilation tests work * Renamings. * Get rid of memory_units dependency in validation * Rename. * Clean. * Estimate capacity. * fmt. * Clean and detail End opcode. * Add comment about top_label safety * Remove another TODO * Comment access to require_target * Remove redundant PartialEq * Print value that can't be coerced to u32 * s/with_instruction_capacity/with_capacity * fmt. * fmt * Proofs * Add better proof * Get rid of unreachable in StackValueType * Propagate error if frame stack overflown on create * use checked sub instead of - * Keep::count
2019-04-19 14:05:09 +00:00
fn validate(wat: &str) -> CompiledModule {
2018-12-11 11:54:06 +00:00
let wasm = wabt::wat2wasm(wat).unwrap();
let module = deserialize_buffer::<Module>(&wasm).unwrap();
Extract validation into a separate crate (#176) * Add some docs. * return_type isn't failable * Add comment about safety of top_label * Attempt number 10 * Rework. Now we will a compiler which wraps and uses info from a evaluation simulator. * Get rid of outcome * Introduce StartedWith * Actually use started_with. * Mirror label_stack. * Avoid using frame_type. * Finally get rid from frame_type. * Extract compilation * Refactoring cleaning * Validation separated from compilation. * Move sink to FunctionReader * Rename to compiler. * fmt * Move push_label under validation context. * Add Validation traits * Express the compiler using validation trait * Move code under prepare * Comments. * WIP * The great move of validation * Make validation compile * Make it compile. * Format it. * Fix warnings. * Clean. * Make it work under no_std * Move deny_floating_point to wasmi * Rename validate_module2 → validate_module * Make validation tests work * Make wasmi compilation tests work * Renamings. * Get rid of memory_units dependency in validation * Rename. * Clean. * Estimate capacity. * fmt. * Clean and detail End opcode. * Add comment about top_label safety * Remove another TODO * Comment access to require_target * Remove redundant PartialEq * Print value that can't be coerced to u32 * s/with_instruction_capacity/with_capacity * fmt. * fmt * Proofs * Add better proof * Get rid of unreachable in StackValueType * Propagate error if frame stack overflown on create * use checked sub instead of - * Keep::count
2019-04-19 14:05:09 +00:00
let compiled_module = compile_module(module).unwrap();
compiled_module
}
Extract validation into a separate crate (#176) * Add some docs. * return_type isn't failable * Add comment about safety of top_label * Attempt number 10 * Rework. Now we will a compiler which wraps and uses info from a evaluation simulator. * Get rid of outcome * Introduce StartedWith * Actually use started_with. * Mirror label_stack. * Avoid using frame_type. * Finally get rid from frame_type. * Extract compilation * Refactoring cleaning * Validation separated from compilation. * Move sink to FunctionReader * Rename to compiler. * fmt * Move push_label under validation context. * Add Validation traits * Express the compiler using validation trait * Move code under prepare * Comments. * WIP * The great move of validation * Make validation compile * Make it compile. * Format it. * Fix warnings. * Clean. * Make it work under no_std * Move deny_floating_point to wasmi * Rename validate_module2 → validate_module * Make validation tests work * Make wasmi compilation tests work * Renamings. * Get rid of memory_units dependency in validation * Rename. * Clean. * Estimate capacity. * fmt. * Clean and detail End opcode. * Add comment about top_label safety * Remove another TODO * Comment access to require_target * Remove redundant PartialEq * Print value that can't be coerced to u32 * s/with_instruction_capacity/with_capacity * fmt. * fmt * Proofs * Add better proof * Get rid of unreachable in StackValueType * Propagate error if frame stack overflown on create * use checked sub instead of - * Keep::count
2019-04-19 14:05:09 +00:00
fn compile(module: &CompiledModule) -> (Vec<isa::Instruction>, Vec<u32>) {
2018-12-11 11:54:06 +00:00
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call")
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (result i32)
i32.const 0
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (param i32)
)
)
"#,
2018-12-11 11:54:06 +00:00
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::None,
}),]
)
}
#[test]
fn get_local() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
return
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (param i32) (param i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (param i32)
(local i32)
get_local 0
set_local 1
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
i32.const 1
if
i32.const 2
return
end
i32.const 3
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
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
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 2
else
i32.const 3
end
drop
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
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
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
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
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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_() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call")
loop (result i32)
i32.const 1
br_if 0
i32.const 2
end
drop
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call")
loop
end
)
)
"#,
2018-12-11 11:54:06 +00:00
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),]
)
}
Extract validation into a separate crate (#176) * Add some docs. * return_type isn't failable * Add comment about safety of top_label * Attempt number 10 * Rework. Now we will a compiler which wraps and uses info from a evaluation simulator. * Get rid of outcome * Introduce StartedWith * Actually use started_with. * Mirror label_stack. * Avoid using frame_type. * Finally get rid from frame_type. * Extract compilation * Refactoring cleaning * Validation separated from compilation. * Move sink to FunctionReader * Rename to compiler. * fmt * Move push_label under validation context. * Add Validation traits * Express the compiler using validation trait * Move code under prepare * Comments. * WIP * The great move of validation * Make validation compile * Make it compile. * Format it. * Fix warnings. * Clean. * Make it work under no_std * Move deny_floating_point to wasmi * Rename validate_module2 → validate_module * Make validation tests work * Make wasmi compilation tests work * Renamings. * Get rid of memory_units dependency in validation * Rename. * Clean. * Estimate capacity. * fmt. * Clean and detail End opcode. * Add comment about top_label safety * Remove another TODO * Comment access to require_target * Remove redundant PartialEq * Print value that can't be coerced to u32 * s/with_instruction_capacity/with_capacity * fmt. * fmt * Proofs * Add better proof * Get rid of unreachable in StackValueType * Propagate error if frame stack overflown on create * use checked sub instead of - * Keep::count
2019-04-19 14:05:09 +00:00
#[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() {
2018-12-11 11:54:06 +00:00
let module = validate(
r#"
(module
(func (export "call")
block $1
loop $2
i32.const 0
br_table $2 $1
end
end
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
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
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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() {
2018-12-11 11:54:06 +00:00
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
)
)
"#,
2018-12-11 11:54:06 +00:00
);
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,
}),
]
)
}