From 010f2d85695ca3be61693b7c4a01dbe8b5a757dd Mon Sep 17 00:00:00 2001 From: Jef Date: Tue, 8 Jan 2019 09:45:39 +0100 Subject: [PATCH] Fix comparisons, add more tests for binops --- src/backend.rs | 110 ++++++++++++++++++------------- src/function_body.rs | 1 + src/tests.rs | 152 ++++++++++++++++++++++++++++++++++--------- 3 files changed, 186 insertions(+), 77 deletions(-) diff --git a/src/backend.rs b/src/backend.rs index 6b7f47a8f5..8b86bb3c52 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -749,7 +749,14 @@ macro_rules! commutative_binop_i64 { ; $instr Rq(op1), i as i32 ); } else { - unimplemented!(concat!("Unsupported `", stringify!($instr), "` with large 64-bit immediate operand")); + let scratch = ctx.block_state.regs.take_scratch_gpr(); + + dynasm!(ctx.asm + ; mov Rq(scratch), QWORD i + ; $instr Rq(op1), Rq(scratch) + ); + + ctx.block_state.regs.release_scratch_gpr(scratch); } } } @@ -1028,7 +1035,7 @@ pub fn literal_i64(ctx: &mut Context, imm: i64) { } macro_rules! cmp_i32 { - ($name:ident, $instr:ident, $const_fallback:expr) => { + ($name:ident, $instr:ident, $reverse_instr:ident, $const_fallback:expr) => { pub fn $name(ctx: &mut Context) { let right = pop(ctx); let left = pop(ctx); @@ -1050,7 +1057,7 @@ macro_rules! cmp_i32 { dynasm!(ctx.asm ; xor Rd(result), Rd(result) ; cmp Rd(rreg), i as i32 - ; $instr Rb(result) + ; $reverse_instr Rb(result) ); Value::Temp(result) } @@ -1098,7 +1105,7 @@ macro_rules! cmp_i32 { } macro_rules! cmp_i64 { - ($name:ident, $instr:ident, $const_fallback:expr) => { + ($name:ident, $instr:ident, $reverse_instr:ident, $const_fallback:expr) => { pub fn $name(ctx: &mut Context) { let right = pop(ctx); let left = pop(ctx); @@ -1108,20 +1115,28 @@ macro_rules! cmp_i64 { ValueLocation::Stack(offset) => { let result = ctx.block_state.regs.take_scratch_gpr(); let offset = adjusted_offset(ctx, offset); - dynasm!(ctx.asm - ; xor Rd(result), Rd(result) - ; cmp QWORD [rsp + offset], i as i32 - ; $instr Rb(result) - ); + if (i as u64) <= u32::max_value() as u64 { + dynasm!(ctx.asm + ; xor Rd(result), Rd(result) + ; cmp QWORD [rsp + offset], i as i32 + ; $instr Rb(result) + ); + } else { + unimplemented!("Unsupported `cmp` with large 64-bit immediate operand"); + } Value::Temp(result) } ValueLocation::Reg(rreg) => { let result = ctx.block_state.regs.take_scratch_gpr(); - dynasm!(ctx.asm - ; xor Rd(result), Rd(result) - ; cmp Rq(rreg), i as i32 - ; $instr Rb(result) - ); + if (i as u64) <= u32::max_value() as u64 { + dynasm!(ctx.asm + ; xor Rd(result), Rd(result) + ; cmp Rq(rreg), i as i32 + ; $reverse_instr Rb(result) + ); + } else { + unimplemented!("Unsupported `cmp` with large 64-bit immediate operand"); + } Value::Temp(result) } ValueLocation::Immediate(right) => { @@ -1156,7 +1171,7 @@ macro_rules! cmp_i64 { ; $instr Rb(result) ); } else { - unimplemented!("Have yet to implement `cmp` with imm64 operand"); + unimplemented!("Unsupported `cmp` with large 64-bit immediate operand"); } } } @@ -1171,29 +1186,29 @@ macro_rules! cmp_i64 { } } -cmp_i32!(i32_eq, sete, |a, b| a == b); -cmp_i32!(i32_neq, setne, |a, b| a != b); +cmp_i32!(i32_eq, sete, sete, |a, b| a == b); +cmp_i32!(i32_neq, setne, setne, |a, b| a != b); // `dynasm-rs` inexplicably doesn't support setb but `setnae` (and `setc`) are synonymous -cmp_i32!(i32_lt_u, setnae, |a, b| (a as u32) < (b as u32)); -cmp_i32!(i32_le_u, setbe, |a, b| (a as u32) <= (b as u32)); -cmp_i32!(i32_gt_u, seta, |a, b| (a as u32) > (b as u32)); -cmp_i32!(i32_ge_u, setae, |a, b| (a as u32) >= (b as u32)); -cmp_i32!(i32_lt_s, setl, |a, b| a < b); -cmp_i32!(i32_le_s, setle, |a, b| a <= b); -cmp_i32!(i32_gt_s, setg, |a, b| a == b); -cmp_i32!(i32_ge_s, setge, |a, b| a == b); - -cmp_i64!(i64_eq, sete, |a, b| a == b); -cmp_i64!(i64_neq, setne, |a, b| a != b); +cmp_i32!(i32_lt_u, setnae, seta, |a, b| (a as u32) < (b as u32)); +cmp_i32!(i32_le_u, setbe, setae, |a, b| (a as u32) <= (b as u32)); +cmp_i32!(i32_gt_u, seta, setnae, |a, b| (a as u32) > (b as u32)); +cmp_i32!(i32_ge_u, setae, setna, |a, b| (a as u32) >= (b as u32)); +cmp_i32!(i32_lt_s, setl, setnle, |a, b| a < b); +cmp_i32!(i32_le_s, setle, setnl, |a, b| a <= b); +cmp_i32!(i32_gt_s, setg, setnge, |a, b| a > b); +cmp_i32!(i32_ge_s, setge, setng, |a, b| a >= b); + +cmp_i64!(i64_eq, sete, sete, |a, b| a == b); +cmp_i64!(i64_neq, setne, setne, |a, b| a != b); // `dynasm-rs` inexplicably doesn't support setb but `setnae` (and `setc`) are synonymous -cmp_i64!(i64_lt_u, setnae, |a, b| (a as u64) < (b as u64)); -cmp_i64!(i64_le_u, setbe, |a, b| (a as u64) <= (b as u64)); -cmp_i64!(i64_gt_u, seta, |a, b| (a as u64) > (b as u64)); -cmp_i64!(i64_ge_u, setae, |a, b| (a as u64) >= (b as u64)); -cmp_i64!(i64_lt_s, setl, |a, b| a < b); -cmp_i64!(i64_le_s, setle, |a, b| a <= b); -cmp_i64!(i64_gt_s, setg, |a, b| a == b); -cmp_i64!(i64_ge_s, setge, |a, b| a == b); +cmp_i64!(i64_lt_u, setnae, seta, |a, b| (a as u64) < (b as u64)); +cmp_i64!(i64_le_u, setbe, setae, |a, b| (a as u64) <= (b as u64)); +cmp_i64!(i64_gt_u, seta, setnae, |a, b| (a as u64) > (b as u64)); +cmp_i64!(i64_ge_u, setae, setna, |a, b| (a as u64) >= (b as u64)); +cmp_i64!(i64_lt_s, setl, setnle, |a, b| a < b); +cmp_i64!(i64_le_s, setle, setnl, |a, b| a <= b); +cmp_i64!(i64_gt_s, setg, setnge, |a, b| a > b); +cmp_i64!(i64_ge_s, setge, setng, |a, b| a >= b); /// Pops i32 predicate and branches to the specified label /// if the predicate is equal to zero. @@ -1219,12 +1234,10 @@ fn immediate_to_reg(ctx: &mut Context, reg: GPR, val: i64) { dynasm!(ctx.asm ; mov Rd(reg), val as i32 ); - } else if reg == RAX { + } else { dynasm!(ctx.asm - ; movabs rax, val + ; mov Rq(reg), QWORD val ); - } else { - unimplemented!("dynasm doesn't yet support mov r64, imm64"); } } @@ -1250,14 +1263,19 @@ fn copy_value(ctx: &mut Context, src: ValueLocation, dst: ValueLocation) { } (ValueLocation::Immediate(i), ValueLocation::Stack(out_offset)) => { let out_offset = adjusted_offset(ctx, out_offset); - dynasm!(ctx.asm - ; mov DWORD [rsp + out_offset], i as i32 - ); - if (i as u64) > u32::max_value() as u64 { - let i = (i >> 4) as i32; + if (i as u64) <= u32::max_value() as u64 { dynasm!(ctx.asm - ; mov DWORD [rsp + out_offset + 4], i + ; mov DWORD [rsp + out_offset], i as i32 ); + } else { + let scratch = ctx.block_state.regs.take_scratch_gpr(); + + dynasm!(ctx.asm + ; mov Rq(scratch), QWORD i + ; mov [rsp + out_offset], Rq(scratch) + ); + + ctx.block_state.regs.release_scratch_gpr(scratch); } } (ValueLocation::Stack(in_offset), ValueLocation::Reg(out_reg)) => { diff --git a/src/function_body.rs b/src/function_body.rs index 92f6dbee72..c40db65fe3 100644 --- a/src/function_body.rs +++ b/src/function_body.rs @@ -330,6 +330,7 @@ pub fn translate( Operator::SetLocal { local_index } => set_local_i32(ctx, local_index), Operator::GetLocal { local_index } => get_local_i32(ctx, local_index), Operator::I32Const { value } => literal_i32(ctx, value), + Operator::I64Const { value } => literal_i64(ctx, value), Operator::Call { function_index } => { let callee_ty = translation_ctx.func_type(function_index); diff --git a/src/tests.rs b/src/tests.rs index 6d7d515e76..971f092f83 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -23,19 +23,54 @@ mod op32 { macro_rules! binop_test { ($op:ident, $func:expr) => { - quickcheck! { - fn $op(a: u32, b: u32) -> bool { - static CODE: &str = concat!( - "(module (func (param i32) (param i32) (result i32) (i32.", - stringify!($op), - " (get_local 0) (get_local 1))))" - ); - - lazy_static! { - static ref TRANSLATED: TranslatedModule = translate_wat(CODE); + mod $op { + use super::{translate_wat, TranslatedModule}; + + 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: u32, b: u32) -> bool { + unsafe { AS_PARAMS.execute_func::<(u32, u32), u32>(0, (a, b)) == $func(a, b) } + } + + fn lit_lit(a: u32, b: u32) -> bool { + unsafe { + translate_wat(&format!(" + (module (func (result i32) + (i32.{op} (i32.const {left}) (i32.const {right})))) + ", op = OP, left = a, right = b)).execute_func::<(), u32>(0, ()) == $func(a, b) + } } - unsafe { TRANSLATED.execute_func::<(u32, u32), u32>(0, (a, b)) == $func(a, b) } + fn lit_reg(a: u32, b: u32) -> bool { + use std::sync::Once; + + 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::<(u32,), u32>(0, (b,)) == $func(a, b) + } + } + + fn reg_lit(a: u32, b: u32) -> bool { + unsafe { + translate_wat(&format!(" + (module (func (param i32) (result i32) + (i32.{op} (get_local 0) (i32.const {right})))) + ", op = OP, right = b)).execute_func::<(u32,), u32>(0, (a,)) == $func(a, b) + } + } } } }; @@ -47,6 +82,8 @@ mod op32 { binop_test!(or, std::ops::BitOr::bitor); binop_test!(xor, std::ops::BitXor::bitxor); binop_test!(mul, u32::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 < b { 1 } else { 0 }); binop_test!(le_u, |a, b| if a <= b { 1 } else { 0 }); binop_test!(gt_u, |a, b| if a > b { 1 } else { 0 }); @@ -65,21 +102,55 @@ mod op64 { binop_test!($op, $func, i64); }; ($op:ident, $func:expr, $retty:ident) => { - quickcheck! { - fn $op(a: u64, b: u64) -> bool { - static CODE: &str = concat!( - "(module (func (param i64) (param i64) (result ", - stringify!($retty), - ") (i64.", - stringify!($op), - " (get_local 0) (get_local 1))))" - ); - - lazy_static! { - static ref TRANSLATED: TranslatedModule = translate_wat(CODE); + 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: u64, b: u64) -> bool { + unsafe { AS_PARAMS.execute_func::<(u64, u64), $retty>(0, (a, b)) == ($func(a, b) as $retty) } + } + + fn lit_lit(a: u64, b: u64) -> 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) + } } - unsafe { TRANSLATED.execute_func::<(u64, u64), u64>(0, (a, b)) == $func(a, b) } + fn lit_reg(a: u64, b: u64) -> 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::<(u64,), $retty>(0, (b,)) == ($func(a, b) as $retty) + } + } + + fn reg_lit(a: u64, b: u64) -> 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::<(u64,), $retty>(0, (a,)) == ($func(a, b) as $retty) + } + } } } }; @@ -91,18 +162,36 @@ mod op64 { binop_test!(or, std::ops::BitOr::bitor); binop_test!(xor, std::ops::BitXor::bitxor); binop_test!(mul, u64::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 < b { 1 } else { 0 }, i32); binop_test!(le_u, |a, b| if a <= b { 1 } else { 0 }, i32); binop_test!(gt_u, |a, b| if a > b { 1 } else { 0 }, i32); binop_test!(ge_u, |a, b| if a >= b { 1 } else { 0 }, i32); - binop_test!(lt_s, |a, b| if (a as i64) < (b as i64) { 1 } else { 0 }, i32); - binop_test!(le_s, |a, b| if (a as i64) <= (b as i64) { 1 } else { 0 }, i32); - binop_test!(gt_s, |a, b| if (a as i64) > (b as i64) { 1 } else { 0 }, i32); - binop_test!(ge_s, |a, b| if (a as i64) >= (b as i64) { 1 } else { 0 }, i32); + binop_test!( + lt_s, + |a, b| if (a as i64) < (b as i64) { 1 } else { 0 }, + i32 + ); + binop_test!( + le_s, + |a, b| if (a as i64) <= (b as i64) { 1 } else { 0 }, + i32 + ); + binop_test!( + gt_s, + |a, b| if (a as i64) > (b as i64) { 1 } else { 0 }, + i32 + ); + binop_test!( + ge_s, + |a, b| if (a as i64) >= (b as i64) { 1 } else { 0 }, + i32 + ); } quickcheck! { - fn relop_eq(a: u32, b: u32) -> bool{ + 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))) @@ -433,7 +522,7 @@ fn spec_loop() { } quickcheck! { - fn spec_fac(n: i32) -> bool { + fn spec_fac(n: i8) -> bool { const CODE: &str = r#" (module (func (param i32) (result i32) @@ -505,9 +594,10 @@ quickcheck! { }; } + let n = n as i32; unsafe { TRANSLATED.execute_func::<(i32,), i32>(0, (n,)) == fac(n) - } + } } }