Files
wasmtime/src/tests.rs
Jef 1eebc65c9e First baby steps to supporting memory accesses
Currently we don't actually sandbox the memory at all, so you can do evil things
like read and write the host's memory. We also don't support growing memory or
cranelift-compatible ABI that passes the memory offset as an argument.

We also always immediately allocate the buffer when encountering a memory section,
there is preliminary support for translating a buffer which can then have the real
offset replaced using relocations (and returning a different type when doing so)
but I haven't written the code that actually does relocation so it doesn't work yet.
2019-01-11 15:20:32 +01:00

799 lines
21 KiB
Rust

use super::{translate, TranslatedModule};
use wabt;
fn translate_wat(wat: &str) -> TranslatedModule {
let wasm = wabt::wat2wasm(wat).unwrap();
let compiled = translate(&wasm).unwrap();
compiled
}
/// Execute the first function in the module.
fn execute_wat(wat: &str, a: u32, b: u32) -> u32 {
let translated = translate_wat(wat);
unsafe { translated.execute_func(0, (a, b)) }
}
#[test]
fn empty() {
let _ = translate_wat("(module (func))");
}
mod op32 {
use super::{translate_wat, TranslatedModule};
macro_rules! binop_test {
($op:ident, $func:expr) => {
mod $op {
use super::{translate_wat, TranslatedModule};
use std::sync::Once;
const OP: &str = stringify!($op);
lazy_static! {
static ref AS_PARAMS: TranslatedModule = translate_wat(&format!("
(module (func (param i32) (param i32) (result i32)
(i32.{op} (get_local 0) (get_local 1))))
", op = OP));
}
quickcheck! {
fn as_params(a: i32, b: i32) -> bool {
unsafe { AS_PARAMS.execute_func::<(i32, i32), i32>(0, (a, b)) == $func(a, b) }
}
fn lit_lit(a: i32, b: i32) -> bool {
let translated = translate_wat(&format!("
(module (func (result i32)
(i32.{op} (i32.const {left}) (i32.const {right}))))
", op = OP, left = a, right = b));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
unsafe {
translated.execute_func::<(), i32>(0, ()) == $func(a, b)
}
}
fn lit_reg(a: i32, b: i32) -> bool {
let translated = translate_wat(&format!("
(module (func (param i32) (result i32)
(i32.{op} (i32.const {left}) (get_local 0))))
", op = OP, left = a));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
unsafe {
translated.execute_func::<(i32,), i32>(0, (b,)) == $func(a, b)
}
}
fn reg_lit(a: i32, b: i32) -> bool {
let translated = translate_wat(&format!("
(module (func (param i32) (result i32)
(i32.{op} (get_local 0) (i32.const {right}))))
", op = OP, right = b));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
unsafe {
translated.execute_func::<(i32,), i32>(0, (a,)) == $func(a, b)
}
}
}
}
};
}
binop_test!(add, i32::wrapping_add);
binop_test!(sub, i32::wrapping_sub);
binop_test!(and, std::ops::BitAnd::bitand);
binop_test!(or, std::ops::BitOr::bitor);
binop_test!(xor, std::ops::BitXor::bitxor);
binop_test!(mul, i32::wrapping_mul);
binop_test!(eq, |a, b| if a == b { 1 } else { 0 });
binop_test!(ne, |a, b| if a != b { 1 } else { 0 });
binop_test!(lt_u, |a, b| if (a as u32) < (b as u32) { 1 } else { 0 });
binop_test!(le_u, |a, b| if (a as u32) <= (b as u32) { 1 } else { 0 });
binop_test!(gt_u, |a, b| if (a as u32) > (b as u32) { 1 } else { 0 });
binop_test!(ge_u, |a, b| if (a as u32) >= (b as u32) { 1 } else { 0 });
binop_test!(lt_s, |a, b| if a < b { 1 } else { 0 });
binop_test!(le_s, |a, b| if a <= b { 1 } else { 0 });
binop_test!(gt_s, |a, b| if a > b { 1 } else { 0 });
binop_test!(ge_s, |a, b| if a >= b { 1 } else { 0 });
}
mod op64 {
use super::{translate_wat, TranslatedModule};
macro_rules! binop_test {
($op:ident, $func:expr) => {
binop_test!($op, $func, i64);
};
($op:ident, $func:expr, $retty:ident) => {
mod $op {
use super::{translate_wat, TranslatedModule};
const RETTY: &str = stringify!($retty);
const OP: &str = stringify!($op);
lazy_static! {
static ref AS_PARAMS: TranslatedModule = translate_wat(&format!("
(module (func (param i64) (param i64) (result {retty})
(i64.{op} (get_local 0) (get_local 1))))
", retty = RETTY, op = OP));
}
quickcheck! {
fn as_params(a: i64, b: i64) -> bool {
unsafe { AS_PARAMS.execute_func::<(i64, i64), $retty>(0, (a, b)) == ($func(a, b) as $retty) }
}
fn lit_lit(a: i64, b: i64) -> bool {
unsafe {
translate_wat(&format!("
(module (func (result {retty})
(i64.{op} (i64.const {left}) (i64.const {right}))))
", retty = RETTY, op = OP, left = a, right = b)).execute_func::<(), $retty>(0, ()) == ($func(a, b) as $retty)
}
}
fn lit_reg(a: i64, b: i64) -> bool {
use std::sync::Once;
let translated = translate_wat(&format!("
(module (func (param i64) (result {retty})
(i64.{op} (i64.const {left}) (get_local 0))))
", retty = RETTY, op = OP, left = a));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
unsafe {
translated.execute_func::<(i64,), $retty>(0, (b,)) == ($func(a, b) as $retty)
}
}
fn reg_lit(a: i64, b: i64) -> bool {
unsafe {
translate_wat(&format!("
(module (func (param i64) (result {retty})
(i64.{op} (get_local 0) (i64.const {right}))))
", retty = RETTY, op = OP, right = b)).execute_func::<(i64,), $retty>(0, (a,)) == ($func(a, b) as $retty)
}
}
}
}
};
}
binop_test!(add, i64::wrapping_add);
binop_test!(sub, i64::wrapping_sub);
binop_test!(and, std::ops::BitAnd::bitand);
binop_test!(or, std::ops::BitOr::bitor);
binop_test!(xor, std::ops::BitXor::bitxor);
binop_test!(mul, i64::wrapping_mul);
binop_test!(eq, |a, b| if a == b { 1 } else { 0 }, i32);
binop_test!(ne, |a, b| if a != b { 1 } else { 0 }, i32);
binop_test!(
lt_u,
|a, b| if (a as u64) < (b as u64) { 1 } else { 0 },
i32
);
binop_test!(
le_u,
|a, b| if (a as u64) <= (b as u64) { 1 } else { 0 },
i32
);
binop_test!(
gt_u,
|a, b| if (a as u64) > (b as u64) { 1 } else { 0 },
i32
);
binop_test!(
ge_u,
|a, b| if (a as u64) >= (b as u64) { 1 } else { 0 },
i32
);
binop_test!(lt_s, |a, b| if a < b { 1 } else { 0 }, i32);
binop_test!(le_s, |a, b| if a <= b { 1 } else { 0 }, i32);
binop_test!(gt_s, |a, b| if a > b { 1 } else { 0 }, i32);
binop_test!(ge_s, |a, b| if a >= b { 1 } else { 0 }, i32);
}
quickcheck! {
fn relop_eq(a: u32, b: u32) -> bool {
static CODE: &str = r#"
(module
(func (param i32) (param i32) (result i32) (i32.eq (get_local 0) (get_local 1)))
)
"#;
lazy_static! {
static ref TRANSLATED: TranslatedModule = translate_wat(CODE);
}
let out = unsafe { TRANSLATED.execute_func::<(u32, u32), u32>(0, (a, b)) };
(a == b) == (out == 1)
}
}
quickcheck! {
fn if_then_else(a: u32, b: u32) -> bool {
const CODE: &str = r#"
(module
(func (param i32) (param i32) (result i32)
(if (result i32)
(i32.eq
(get_local 0)
(get_local 1)
)
(then (get_local 0))
(else (get_local 1))
)
)
)
"#;
lazy_static! {
static ref TRANSLATED: TranslatedModule = translate_wat(CODE);
}
let out = unsafe { TRANSLATED.execute_func::<(u32, u32), u32>(0, (a, b)) };
out == (if a == b { a } else { b })
}
}
#[test]
fn if_without_result() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(if
(i32.eq
(get_local 0)
(get_local 1)
)
(then (unreachable))
)
(get_local 0)
)
)
"#;
assert_eq!(execute_wat(code, 2, 3), 2);
}
#[test]
fn function_call() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(call $assert_zero
(get_local 1)
)
(get_local 0)
)
(func $assert_zero (param $v i32)
(local i32)
(if (get_local $v)
(unreachable)
)
)
)
"#;
assert_eq!(execute_wat(code, 2, 0), 2);
}
#[test]
fn large_function() {
let code = r#"
(module
(func (param i32) (param i32) (param i32) (param i32)
(param i32) (param i32)
(result i32)
(call $assert_zero
(get_local 5)
)
(get_local 0)
)
(func $assert_zero (param $v i32)
(local i32)
(if (get_local $v)
(unreachable)
)
)
)
"#;
assert_eq!(
{
let translated = translate_wat(code);
translated.disassemble();
let out: u32 = unsafe { translated.execute_func(0, (5, 4, 3, 2, 1, 0)) };
out
},
5
);
}
#[test]
fn function_read_args_spill_to_stack() {
let code = r#"
(module
(func (param i32) (param i32) (param i32) (param i32)
(param i32) (param i32) (param i32) (param i32)
(result i32)
(call $assert_zero
(get_local 7)
)
(get_local 0)
)
(func $assert_zero (param $v i32)
(local i32)
(if (get_local $v)
(unreachable)
)
)
)
"#;
assert_eq!(
{
let translated = translate_wat(code);
translated.disassemble();
let out: u32 = unsafe {
translated.execute_func(0, (7u32, 6u32, 5u32, 4u32, 3u32, 2u32, 1u32, 0u32))
};
out
},
7
);
}
macro_rules! mk_function_write_args_spill_to_stack {
($name:ident, $typ:ty) => {
#[test]
fn $name() {
let code = format!(
"
(module
(func (param {typ}) (param {typ}) (param {typ}) (param {typ})
(param {typ}) (param {typ}) (param {typ}) (param {typ})
(param {typ}) (param {typ}) (param {typ}) (param {typ})
(result {typ})
(call $called
(get_local 0)
(get_local 1)
(get_local 2)
(get_local 3)
(get_local 4)
(get_local 5)
(get_local 6)
(get_local 7)
(get_local 8)
(get_local 9)
(get_local 10)
(get_local 11)
)
)
(func $called
(param {typ}) (param {typ}) (param {typ}) (param {typ})
(param {typ}) (param {typ}) (param {typ}) (param {typ})
(param {typ}) (param {typ}) (param {typ}) (param {typ})
(result {typ})
(call $assert_zero
(get_local 11)
)
(get_local 0)
)
(func $assert_zero (param $v {typ})
(local {typ})
(if ({typ}.ne (get_local $v) ({typ}.const 0))
(unreachable)
)
)
)
",
typ = stringify!($typ)
);
assert_eq!(
{
let translated = translate_wat(&code);
translated.disassemble();
let out: $typ = unsafe {
translated.execute_func(
0,
(
11 as $typ, 10 as $typ, 9 as $typ, 8 as $typ, 7 as $typ, 6 as $typ,
5 as $typ, 4 as $typ, 3 as $typ, 2 as $typ, 1 as $typ, 0 as $typ,
),
)
};
out
},
11
);
}
};
}
mk_function_write_args_spill_to_stack!(function_write_args_spill_to_stack_i32, i32);
mk_function_write_args_spill_to_stack!(function_write_args_spill_to_stack_i64, i64);
#[test]
fn block() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(block (result i32)
get_local 0
)
)
)
"#;
assert_eq!(execute_wat(code, 10, 20), 10);
}
#[test]
fn br_block() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
get_local 1
(block (result i32)
get_local 0
get_local 0
br 0
unreachable
)
i32.add
)
)
"#;
let translated = translate_wat(code);
translated.disassemble();
assert_eq!(unsafe { translated.execute_func::<(i32, i32), i32>(0, (5, 7)) }, 12);
}
// Tests discarding values on the value stack, while
// carrying over the result using a conditional branch.
#[test]
fn brif_block() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
get_local 1
(block (result i32)
get_local 0
get_local 0
br_if 0
unreachable
)
i32.add
)
)
"#;
assert_eq!(execute_wat(code, 5, 7), 12);
}
#[test]
fn spec_loop() {
let code = r#"
(module
(func
(call $assert-eq (call $as-binary-operand) (i32.const 12))
(call $assert-eq (call $break-bare) (i32.const 19))
(call $assert-eq (call $break-value) (i32.const 18))
(call $assert-eq (call $break-repeated) (i32.const 18))
(call $assert-eq (call $break-inner) (i32.const 0x7))
)
(func $dummy)
(func $as-binary-operand (result i32)
(i32.mul
(loop (result i32) (call $dummy) (i32.const 3))
(loop (result i32) (call $dummy) (i32.const 4))
)
)
(func $break-bare (result i32)
(block (loop (br 1) (br 0) (unreachable)))
(block (loop (br_if 1 (i32.const 1)) (unreachable)))
(i32.const 19)
)
(func $break-value (result i32)
(block (result i32)
(loop (result i32) (br 1 (i32.const 18)) (br 0) (i32.const 19))
)
)
(func $break-repeated (result i32)
(block (result i32)
(loop (result i32)
(br 1 (i32.const 18))
(br 1 (i32.const 19))
(drop (br_if 1 (i32.const 20) (i32.const 0)))
(drop (br_if 1 (i32.const 20) (i32.const 1)))
(br 1 (i32.const 21))
(i32.const 21)
)
)
)
(func $break-inner (result i32)
(local i32)
(set_local 0 (i32.const 0))
(set_local 0 (i32.add (get_local 0) (block (result i32) (loop (result i32) (block (result i32) (br 2 (i32.const 0x1)))))))
(set_local 0 (i32.add (get_local 0) (block (result i32) (loop (result i32) (loop (result i32) (br 2 (i32.const 0x2)))))))
(set_local 0 (i32.add (get_local 0) (block (result i32) (loop (result i32) (block (result i32) (loop (result i32) (br 1 (i32.const 0x4))))))))
(get_local 0)
)
(func $assert-eq (param i32) (param i32)
(if (i32.ne (get_local 0) (get_local 1))
(unreachable)
)
)
)
"#;
let translated = translate_wat(code);
translated.disassemble();
unsafe { translated.execute_func::<(), ()>(0, ()) }
}
quickcheck! {
fn spec_fac(n: i8) -> bool {
const CODE: &str = r#"
(module
(func (param i32) (result i32)
(local i32)
(set_local 1 (call $fac-iter (get_local 0)))
(call $assert-eq (get_local 1) (call $fac-opt (get_local 0)))
(get_local 1)
)
(func $assert-eq (param i32) (param i32)
(if (i32.ne (get_local 0) (get_local 1))
(unreachable)
)
)
;; Iterative factorial
(func $fac-iter (param i32) (result i32)
(local i32 i32)
(set_local 1 (get_local 0))
(set_local 2 (i32.const 1))
(block
(loop
(if
(i32.lt_s (get_local 1) (i32.const 2))
(then (br 2))
(else
(set_local 2 (i32.mul (get_local 1) (get_local 2)))
(set_local 1 (i32.sub (get_local 1) (i32.const 1)))
)
)
(br 0)
)
)
(get_local 2)
)
;; Optimized factorial.
(func $fac-opt (param i32) (result i32)
(local i32)
(set_local 1 (i32.const 1))
(block
(br_if 0 (i32.lt_s (get_local 0) (i32.const 2)))
(loop
(set_local 1 (i32.mul (get_local 1) (get_local 0)))
(set_local 0 (i32.add (get_local 0) (i32.const -1)))
(br_if 0 (i32.gt_s (get_local 0) (i32.const 1)))
)
)
(get_local 1)
)
)"#;
fn fac(mut n: i32) -> i32 {
let mut a = 1i32;
while n > 1 {
a = a.wrapping_mul(n);
n -= 1;
}
a
}
lazy_static! {
static ref TRANSLATED: TranslatedModule = {
let out = translate_wat(CODE);
out.disassemble();
out
};
}
let n = n as i32;
unsafe {
TRANSLATED.execute_func::<(i32,), i32>(0, (n,)) == fac(n)
}
}
}
// Tests that br_if keeps values in the case if the branch
// hasn't been taken.
#[test]
fn brif_block_passthru() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(block (result i32)
get_local 1
get_local 0
br_if 0
get_local 1
i32.add
)
)
)
"#;
assert_eq!(execute_wat(code, 0, 3), 6);
}
#[test]
fn literals() {
let code = r#"
(module
(func (param i32) (param i32) (result i32)
(i32.const 228)
)
)
"#;
assert_eq!(execute_wat(code, 0, 0), 228);
}
const FIBONACCI: &str = r#"
(module
(func $fib (param $n i32) (result i32)
(if (result i32)
(i32.eq
(i32.const 0)
(get_local $n)
)
(then
(i32.const 1)
)
(else
(if (result i32)
(i32.eq
(i32.const 1)
(get_local $n)
)
(then
(i32.const 1)
)
(else
(i32.add
;; fib(n - 1)
(call $fib
(i32.add
(get_local $n)
(i32.const -1)
)
)
;; fib(n - 2)
(call $fib
(i32.add
(get_local $n)
(i32.const -2)
)
)
)
)
)
)
)
)
)
"#;
#[test]
fn fib() {
fn fib(n: u32) -> u32 {
let (mut a, mut b) = (1, 1);
for _ in 0..n {
let old_a = a;
a = b;
b += old_a;
}
a
}
let translated = translate_wat(FIBONACCI);
translated.disassemble();
for x in 0..30 {
unsafe {
assert_eq!(
translated.execute_func::<_, u32>(0, (x,)),
fib(x),
"Failed for x={}",
x
);
}
}
}
#[test]
fn storage() {
const CODE: &str = r#"
(module
(memory 1 1)
(func (result i32)
(local i32 i32 i32)
(set_local 0 (i32.const 10))
(block
(loop
(if
(i32.eq (get_local 0) (i32.const 0))
(then (br 2))
)
(set_local 2 (i32.mul (get_local 0) (i32.const 4)))
(i32.store (get_local 2) (get_local 0))
(set_local 1 (i32.load (get_local 2)))
(if
(i32.ne (get_local 0) (get_local 1))
(then (return (i32.const 0)))
)
(set_local 0 (i32.sub (get_local 0) (i32.const 1)))
(br 0)
)
)
(i32.const 1)
)
)"#;
let translated = translate_wat(CODE);
translated.disassemble();
assert_eq!(unsafe { translated.execute_func::<(), i32>(0, ()) }, 1);
}
#[bench]
fn bench_fibonacci_compile(b: &mut test::Bencher) {
let wasm = wabt::wat2wasm(FIBONACCI).unwrap();
b.iter(|| test::black_box(translate(&wasm).unwrap()));
}
#[bench]
fn bench_fibonacci_run(b: &mut test::Bencher) {
let wasm = wabt::wat2wasm(FIBONACCI).unwrap();
let module = translate(&wasm).unwrap();
b.iter(|| unsafe { module.execute_func::<_, u32>(0, (20,)) });
}
#[bench]
fn bench_fibonacci_baseline(b: &mut test::Bencher) {
fn fib(n: i32) -> i32 {
if n == 0 || n == 1 {
1
} else {
fib(n - 1) + fib(n - 2)
}
}
b.iter(|| test::black_box(fib(test::black_box(20))));
}