diff --git a/src/backend.rs b/src/backend.rs index 8b86bb3c52..589709fd05 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -744,9 +744,9 @@ macro_rules! commutative_binop_i64 { ); } ValueLocation::Immediate(i) => { - if (i as u64) <= u32::max_value() as u64 { + if let Some(i) = i.try_into() { dynasm!(ctx.asm - ; $instr Rq(op1), i as i32 + ; $instr Rq(op1), i ); } else { let scratch = ctx.block_state.regs.take_scratch_gpr(); @@ -766,6 +766,7 @@ macro_rules! commutative_binop_i64 { } } } + // TODO: Use `inc`/`dec` where possible? commutative_binop_i32!(i32_add, add, |a, b| (a as i32).wrapping_add(b as i32)); commutative_binop_i32!(i32_and, and, |a, b| a & b); @@ -777,53 +778,21 @@ commutative_binop_i64!(i64_and, and, |a, b| a & b); commutative_binop_i64!(i64_or, or, |a, b| a | b); commutative_binop_i64!(i64_xor, xor, |a, b| a ^ b); -// `i32_mul` needs to be seperate because the immediate form of the instruction -// has a different syntax to the immediate form of the other instructions. -pub fn i32_mul(ctx: &mut Context) { - let op0 = pop(ctx); - let op1 = pop(ctx); +trait TryInto { + fn try_into(self) -> Option; +} - if let Some(i1) = op1.immediate() { - if let Some(i0) = op0.immediate() { - ctx.block_state.stack.push(StackValue::Immediate( - i32::wrapping_mul(i1 as i32, i0 as i32) as _, - )); - return; +impl TryInto for i64 { + fn try_into(self) -> Option { + let min = i32::min_value() as i64; + let max = i32::max_value() as i64; + + if self > min && self < max { + Some(self as i32) + } else { + None } } - - let (op1, op0) = match op1 { - Value::Temp(reg) => (reg, op0), - _ => { - if op0.immediate().is_some() { - (into_temp_reg(ctx, op1), op0) - } else { - (into_temp_reg(ctx, op0), op1) - } - } - }; - - match op0.location(&ctx.block_state.locals) { - ValueLocation::Reg(reg) => { - dynasm!(ctx.asm - ; imul Rd(op1), Rd(reg) - ); - } - ValueLocation::Stack(offset) => { - let offset = adjusted_offset(ctx, offset); - dynasm!(ctx.asm - ; imul Rd(op1), [rsp + offset] - ); - } - ValueLocation::Immediate(i) => { - dynasm!(ctx.asm - ; imul Rd(op1), Rd(op1), i as i32 - ); - } - } - - ctx.block_state.stack.push(StackValue::Temp(op1)); - free_value(ctx, op0); } // `sub` is not commutative, so we have to handle it differently (we _must_ use the `op1` @@ -853,9 +822,9 @@ pub fn i64_sub(ctx: &mut Context) { ); } ValueLocation::Immediate(i) => { - if (i as u64) <= u32::max_value() as u64 { + if let Some(i) = i.try_into() { dynasm!(ctx.asm - ; sub Rq(op1), i as i32 + ; sub Rq(op1), i ); } else { unimplemented!(concat!( @@ -908,9 +877,9 @@ pub fn i64_mul(ctx: &mut Context) { ); } ValueLocation::Immediate(i) => { - if (i as u64) <= u32::max_value() as u64 { + if let Some(i) = i.try_into() { dynasm!(ctx.asm - ; imul Rq(op1), Rq(op1), i as i32 + ; imul Rq(op1), Rq(op1), i ); } else { unimplemented!(concat!( @@ -951,8 +920,63 @@ pub fn i32_sub(ctx: &mut Context) { ); } ValueLocation::Immediate(i) => { + if i == 1 { + dynasm!(ctx.asm + ; dec Rd(op1) + ); + } else { + dynasm!(ctx.asm + ; sub Rd(op1), i as i32 + ); + } + } + } + + ctx.block_state.stack.push(StackValue::Temp(op1)); + free_value(ctx, op0); +} + +// `i32_mul` needs to be seperate because the immediate form of the instruction +// has a different syntax to the immediate form of the other instructions. +pub fn i32_mul(ctx: &mut Context) { + let op0 = pop(ctx); + let op1 = pop(ctx); + + if let Some(i1) = op1.immediate() { + if let Some(i0) = op0.immediate() { + ctx.block_state.stack.push(StackValue::Immediate( + i32::wrapping_mul(i1 as i32, i0 as i32) as _, + )); + return; + } + } + + let (op1, op0) = match op1 { + Value::Temp(reg) => (reg, op0), + _ => { + if op0.immediate().is_some() { + (into_temp_reg(ctx, op1), op0) + } else { + (into_temp_reg(ctx, op0), op1) + } + } + }; + + match op0.location(&ctx.block_state.locals) { + ValueLocation::Reg(reg) => { dynasm!(ctx.asm - ; sub Rd(op1), i as i32 + ; imul Rd(op1), Rd(reg) + ); + } + ValueLocation::Stack(offset) => { + let offset = adjusted_offset(ctx, offset); + dynasm!(ctx.asm + ; imul Rd(op1), [rsp + offset] + ); + } + ValueLocation::Immediate(i) => { + dynasm!(ctx.asm + ; imul Rd(op1), Rd(op1), i as i32 ); } } @@ -1115,10 +1139,10 @@ macro_rules! cmp_i64 { ValueLocation::Stack(offset) => { let result = ctx.block_state.regs.take_scratch_gpr(); let offset = adjusted_offset(ctx, offset); - if (i as u64) <= u32::max_value() as u64 { + if let Some(i) = i.try_into() { dynasm!(ctx.asm ; xor Rd(result), Rd(result) - ; cmp QWORD [rsp + offset], i as i32 + ; cmp QWORD [rsp + offset], i ; $instr Rb(result) ); } else { @@ -1128,10 +1152,10 @@ macro_rules! cmp_i64 { } ValueLocation::Reg(rreg) => { let result = ctx.block_state.regs.take_scratch_gpr(); - if (i as u64) <= u32::max_value() as u64 { + if let Some(i) = i.try_into() { dynasm!(ctx.asm ; xor Rd(result), Rd(result) - ; cmp Rq(rreg), i as i32 + ; cmp Rq(rreg), i ; $reverse_instr Rb(result) ); } else { @@ -1164,10 +1188,10 @@ macro_rules! cmp_i64 { ); } ValueLocation::Immediate(i) => { - if (i as u64) <= u32::max_value() as u64 { + if let Some(i) = i.try_into() { dynasm!(ctx.asm ; xor Rd(result), Rd(result) - ; cmp Rq(lreg), i as i32 + ; cmp Rq(lreg), i ; $instr Rb(result) ); } else { @@ -1575,3 +1599,4 @@ pub fn trap(ctx: &mut Context) { ; ud2 ); } + diff --git a/src/tests.rs b/src/tests.rs index 971f092f83..bfd525c7ee 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -36,20 +36,20 @@ mod op32 { } quickcheck! { - fn as_params(a: u32, b: u32) -> bool { - unsafe { AS_PARAMS.execute_func::<(u32, u32), u32>(0, (a, b)) == $func(a, b) } + 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: u32, b: u32) -> bool { + fn lit_lit(a: i32, b: i32) -> 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) + ", op = OP, left = a, right = b)).execute_func::<(), i32>(0, ()) == $func(a, b) } } - fn lit_reg(a: u32, b: u32) -> bool { + fn lit_reg(a: i32, b: i32) -> bool { use std::sync::Once; let translated = translate_wat(&format!(" @@ -59,16 +59,16 @@ mod op32 { static ONCE: Once = Once::new(); ONCE.call_once(|| translated.disassemble()); unsafe { - translated.execute_func::<(u32,), u32>(0, (b,)) == $func(a, b) + translated.execute_func::<(i32,), i32>(0, (b,)) == $func(a, b) } } - fn reg_lit(a: u32, b: u32) -> bool { + fn reg_lit(a: i32, b: i32) -> 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) + ", op = OP, right = b)).execute_func::<(i32,), i32>(0, (a,)) == $func(a, b) } } } @@ -76,22 +76,22 @@ mod op32 { }; } - binop_test!(add, u32::wrapping_add); - binop_test!(sub, u32::wrapping_sub); + 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, u32::wrapping_mul); + 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 < 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 }); - binop_test!(ge_u, |a, b| if a >= b { 1 } else { 0 }); - binop_test!(lt_s, |a, b| if (a as i32) < (b as i32) { 1 } else { 0 }); - binop_test!(le_s, |a, b| if (a as i32) <= (b as i32) { 1 } else { 0 }); - binop_test!(gt_s, |a, b| if (a as i32) > (b as i32) { 1 } else { 0 }); - binop_test!(ge_s, |a, b| if (a as i32) >= (b as i32) { 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 { @@ -116,11 +116,11 @@ mod op64 { } 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 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: u64, b: u64) -> bool { + fn lit_lit(a: i64, b: i64) -> bool { unsafe { translate_wat(&format!(" (module (func (result {retty}) @@ -129,7 +129,7 @@ mod op64 { } } - fn lit_reg(a: u64, b: u64) -> bool { + fn lit_reg(a: i64, b: i64) -> bool { use std::sync::Once; let translated = translate_wat(&format!(" @@ -139,16 +139,16 @@ mod op64 { static ONCE: Once = Once::new(); ONCE.call_once(|| translated.disassemble()); unsafe { - translated.execute_func::<(u64,), $retty>(0, (b,)) == ($func(a, b) as $retty) + translated.execute_func::<(i64,), $retty>(0, (b,)) == ($func(a, b) as $retty) } } - fn reg_lit(a: u64, b: u64) -> bool { + 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::<(u64,), $retty>(0, (a,)) == ($func(a, b) as $retty) + ", retty = RETTY, op = OP, right = b)).execute_func::<(i64,), $retty>(0, (a,)) == ($func(a, b) as $retty) } } } @@ -156,38 +156,38 @@ mod op64 { }; } - binop_test!(add, u64::wrapping_add); - binop_test!(sub, u64::wrapping_sub); + 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, u64::wrapping_mul); + 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 < 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 }, + lt_u, + |a, b| if (a as u64) < (b as u64) { 1 } else { 0 }, i32 ); binop_test!( - le_s, - |a, b| if (a as i64) <= (b as i64) { 1 } else { 0 }, + le_u, + |a, b| if (a as u64) <= (b as u64) { 1 } else { 0 }, i32 ); binop_test!( - gt_s, - |a, b| if (a as i64) > (b as i64) { 1 } else { 0 }, + gt_u, + |a, b| if (a as u64) > (b as u64) { 1 } else { 0 }, i32 ); binop_test!( - ge_s, - |a, b| if (a as i64) >= (b as i64) { 1 } else { 0 }, + 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! { @@ -348,14 +348,17 @@ fn function_read_args_spill_to_stack() { ); } -#[test] -fn function_write_args_spill_to_stack() { - let code = r#" +macro_rules! mk_function_write_args_spill_to_stack { + ($name:ident, $typ:ty) => { + #[test] + fn $name() { + let code = format!( + " (module - (func (param i32) (param i32) (param i32) (param i32) - (param i32) (param i32) (param i32) (param i32) - (param i32) (param i32) (param i32) (param i32) - (result i32) + (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) @@ -374,10 +377,10 @@ fn function_write_args_spill_to_stack() { ) (func $called - (param i32) (param i32) (param i32) (param i32) - (param i32) (param i32) (param i32) (param i32) - (param i32) (param i32) (param i32) (param i32) - (result i32) + (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) @@ -385,26 +388,39 @@ fn function_write_args_spill_to_stack() { (get_local 0) ) - (func $assert_zero (param $v i32) - (local i32) - (if (get_local $v) + (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: u32 = - unsafe { translated.execute_func(0, (11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) }; - out - }, - 11 - ); + 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() {