Add more float operations

This commit is contained in:
Jef
2019-02-27 11:29:23 +01:00
parent f726a8f36d
commit 15bf933be7
3 changed files with 585 additions and 154 deletions

View File

@@ -497,7 +497,7 @@ impl<'a, M> CodeGenSession<'a, M> {
Context { Context {
asm: &mut self.assembler, asm: &mut self.assembler,
func_starts: &self.func_starts, func_starts: &self.func_starts,
trap_label: None, labels: Default::default(),
block_state: Default::default(), block_state: Default::default(),
module_context: self.module_context, module_context: self.module_context,
} }
@@ -627,13 +627,21 @@ pub enum MemoryAccessMode {
Unchecked, Unchecked,
} }
// TODO: We can share one trap/constant for all functions by reusing this struct
#[derive(Default)]
struct Labels {
trap: Option<Label>,
neg_const_f32: Option<Label>,
neg_const_f64: Option<Label>,
}
pub struct Context<'a, M> { pub struct Context<'a, M> {
asm: &'a mut Assembler, asm: &'a mut Assembler,
module_context: &'a M, module_context: &'a M,
func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>, func_starts: &'a Vec<(Option<AssemblyOffset>, DynamicLabel)>,
/// Each push and pop on the value stack increments or decrements this value by 1 respectively. /// Each push and pop on the value stack increments or decrements this value by 1 respectively.
pub block_state: BlockState, pub block_state: BlockState,
trap_label: Option<Label>, labels: Labels,
} }
/// Label in code. /// Label in code.
@@ -855,7 +863,7 @@ macro_rules! cmp_i64 {
let out = if let Some(i) = left.imm_i64() { let out = if let Some(i) = left.imm_i64() {
match right { match right {
ValueLocation::Stack(offset) => { ValueLocation::Stack(offset) => {
let result = self.block_state.regs.take(I64); let result = self.block_state.regs.take(I32);
let offset = self.adjusted_offset(offset); let offset = self.adjusted_offset(offset);
if let Some(i) = i.try_into() { if let Some(i) = i.try_into() {
dynasm!(self.asm dynasm!(self.asm
@@ -869,7 +877,7 @@ macro_rules! cmp_i64 {
ValueLocation::Reg(result) ValueLocation::Reg(result)
} }
ValueLocation::Reg(rreg) => { ValueLocation::Reg(rreg) => {
let result = self.block_state.regs.take(I64); let result = self.block_state.regs.take(I32);
if let Some(i) = i.try_into() { if let Some(i) = i.try_into() {
dynasm!(self.asm dynasm!(self.asm
; xor Rd(result.rq().unwrap()), Rd(result.rq().unwrap()) ; xor Rd(result.rq().unwrap()), Rd(result.rq().unwrap())
@@ -895,7 +903,7 @@ macro_rules! cmp_i64 {
let lreg = self.into_reg(I64, left); let lreg = self.into_reg(I64, left);
left = ValueLocation::Reg(lreg); left = ValueLocation::Reg(lreg);
let result = self.block_state.regs.take(I64); let result = self.block_state.regs.take(I32);
match right { match right {
ValueLocation::Stack(offset) => { ValueLocation::Stack(offset) => {
@@ -937,6 +945,135 @@ macro_rules! cmp_i64 {
} }
} }
macro_rules! cmp_f32 {
($name:ident, $reverse_name:ident, $instr:ident, $const_fallback:expr) => {
cmp_float!(
comiss,
f32,
imm_f32,
$name,
$reverse_name,
$instr,
$const_fallback
);
};
}
macro_rules! cmp_f64 {
($name:ident, $reverse_name:ident, $instr:ident, $const_fallback:expr) => {
cmp_float!(
comisd,
f64,
imm_f64,
$name,
$reverse_name,
$instr,
$const_fallback
);
};
}
macro_rules! cmp_float {
(@helper $cmp_instr:ident, $ty:ty, $imm_fn:ident, $self:expr, $left:expr, $right:expr, $instr:ident, $const_fallback:expr) => {{
let (left, right, this) = ($left, $right, $self);
if let (Some(left), Some(right)) = (left.$imm_fn(), right.$imm_fn()) {
if $const_fallback(<$ty>::from_bits(left.bits()), <$ty>::from_bits(right.bits())) {
ValueLocation::Immediate(1i32.into())
} else {
ValueLocation::Immediate(0i32.into())
}
} else {
let lreg = this.into_reg(GPRType::Rx, *left);
*left = ValueLocation::Reg(lreg);
let result = this.block_state.regs.take(I32);
match right {
ValueLocation::Stack(offset) => {
let offset = this.adjusted_offset(*offset);
dynasm!(this.asm
; xor Rq(result.rq().unwrap()), Rq(result.rq().unwrap())
; $cmp_instr Rx(lreg.rx().unwrap()), [rsp + offset]
; $instr Rb(result.rq().unwrap())
);
}
right => {
let rreg = this.into_reg(GPRType::Rx, *right);
*right = ValueLocation::Reg(rreg);
dynasm!(this.asm
; xor Rq(result.rq().unwrap()), Rq(result.rq().unwrap())
; $cmp_instr Rx(lreg.rx().unwrap()), Rx(rreg.rx().unwrap())
; $instr Rb(result.rq().unwrap())
);
}
}
ValueLocation::Reg(result)
}
}};
($cmp_instr:ident, $ty:ty, $imm_fn:ident, $name:ident, $reverse_name:ident, $instr:ident, $const_fallback:expr) => {
pub fn $name(&mut self) {
let mut right = self.pop();
let mut left = self.pop();
let out = cmp_float!(@helper
$cmp_instr,
$ty,
$imm_fn,
&mut *self,
&mut left,
&mut right,
$instr,
$const_fallback
);
self.free_value(left);
self.free_value(right);
self.push(out);
}
pub fn $reverse_name(&mut self) {
let mut right = self.pop();
let mut left = self.pop();
let out = cmp_float!(@helper
$cmp_instr,
$ty,
$imm_fn,
&mut *self,
&mut right,
&mut left,
$instr,
$const_fallback
);
self.free_value(left);
self.free_value(right);
self.push(out);
}
};
}
macro_rules! binop_i32 {
($name:ident, $instr:ident, $const_fallback:expr) => {
binop!(
$name,
$instr,
$const_fallback,
Rd,
rq,
I32,
imm_i32,
|this: &mut Context<_>, op1: GPR, i| dynasm!(this.asm
; $instr Rd(op1.rq().unwrap()), i
)
);
};
}
macro_rules! commutative_binop_i32 { macro_rules! commutative_binop_i32 {
($name:ident, $instr:ident, $const_fallback:expr) => { ($name:ident, $instr:ident, $const_fallback:expr) => {
commutative_binop!( commutative_binop!(
@@ -954,6 +1091,23 @@ macro_rules! commutative_binop_i32 {
}; };
} }
macro_rules! binop_i64 {
($name:ident, $instr:ident, $const_fallback:expr) => {
binop!(
$name,
$instr,
$const_fallback,
Rq,
rq,
I64,
imm_i64,
|this: &mut Context<_>, op1: GPR, i| dynasm!(this.asm
; $instr Rq(op1.rq().unwrap()), i
)
);
};
}
macro_rules! commutative_binop_i64 { macro_rules! commutative_binop_i64 {
($name:ident, $instr:ident, $const_fallback:expr) => { ($name:ident, $instr:ident, $const_fallback:expr) => {
commutative_binop!( commutative_binop!(
@@ -971,12 +1125,31 @@ macro_rules! commutative_binop_i64 {
}; };
} }
macro_rules! binop_f32 {
($name:ident, $instr:ident, $const_fallback:expr) => {
binop!(
$name,
$instr,
|a: wasmparser::Ieee32, b: wasmparser::Ieee32| wasmparser::Ieee32(
$const_fallback(f32::from_bits(a.bits()), f32::from_bits(b.bits())).to_bits()
),
Rx,
rx,
F32,
imm_f32,
|_, _, _| unreachable!()
);
};
}
macro_rules! commutative_binop_f32 { macro_rules! commutative_binop_f32 {
($name:ident, $instr:ident, $const_fallback:expr) => { ($name:ident, $instr:ident, $const_fallback:expr) => {
commutative_binop!( commutative_binop!(
$name, $name,
$instr, $instr,
$const_fallback, |a: wasmparser::Ieee32, b: wasmparser::Ieee32| wasmparser::Ieee32(
$const_fallback(f32::from_bits(a.bits()), f32::from_bits(b.bits())).to_bits()
),
Rx, Rx,
rx, rx,
F32, F32,
@@ -986,12 +1159,31 @@ macro_rules! commutative_binop_f32 {
}; };
} }
macro_rules! binop_f64 {
($name:ident, $instr:ident, $const_fallback:expr) => {
binop!(
$name,
$instr,
|a: wasmparser::Ieee64, b: wasmparser::Ieee64| wasmparser::Ieee64(
$const_fallback(f64::from_bits(a.bits()), f64::from_bits(b.bits())).to_bits()
),
Rx,
rx,
F64,
imm_f64,
|_, _, _| unreachable!()
);
};
}
macro_rules! commutative_binop_f64 { macro_rules! commutative_binop_f64 {
($name:ident, $instr:ident, $const_fallback:expr) => { ($name:ident, $instr:ident, $const_fallback:expr) => {
commutative_binop!( commutative_binop!(
$name, $name,
$instr, $instr,
$const_fallback, |a: wasmparser::Ieee64, b: wasmparser::Ieee64| wasmparser::Ieee64(
$const_fallback(f64::from_bits(a.bits()), f64::from_bits(b.bits())).to_bits()
),
Rx, Rx,
rx, rx,
F64, F64,
@@ -1000,9 +1192,36 @@ macro_rules! commutative_binop_f64 {
); );
}; };
} }
macro_rules! commutative_binop { macro_rules! commutative_binop {
($name:ident, $instr:ident, $const_fallback:expr, $reg_ty:ident, $reg_fn:ident, $ty:expr, $imm_fn:ident, $direct_imm:expr) => { ($name:ident, $instr:ident, $const_fallback:expr, $reg_ty:ident, $reg_fn:ident, $ty:expr, $imm_fn:ident, $direct_imm:expr) => {
binop!(
$name,
$instr,
$const_fallback,
$reg_ty,
$reg_fn,
$ty,
$imm_fn,
$direct_imm,
|op1: ValueLocation, op0: ValueLocation| match op1 {
ValueLocation::Reg(_) => (op1, op0),
_ => {
if op0.immediate().is_some() {
(op1, op0)
} else {
(op0, op1)
}
}
}
);
};
}
macro_rules! binop {
($name:ident, $instr:ident, $const_fallback:expr, $reg_ty:ident, $reg_fn:ident, $ty:expr, $imm_fn:ident, $direct_imm:expr) => {
binop!($name, $instr, $const_fallback, $reg_ty, $reg_fn, $ty, $imm_fn, $direct_imm, |a, b| (a, b));
};
($name:ident, $instr:ident, $const_fallback:expr, $reg_ty:ident, $reg_fn:ident, $ty:expr, $imm_fn:ident, $direct_imm:expr, $map_op:expr) => {
pub fn $name(&mut self) { pub fn $name(&mut self) {
let op0 = self.pop(); let op0 = self.pop();
let op1 = self.pop(); let op1 = self.pop();
@@ -1014,14 +1233,8 @@ macro_rules! commutative_binop {
} }
} }
let (op1, op0) = match op1 { let (op1, op0) = $map_op(op1, op0);
ValueLocation::Reg(_) => (self.into_temp_reg($ty, op1), op0), let op1 = self.into_temp_reg($ty, op1);
_ => if op0.immediate().is_some() {
(self.into_temp_reg($ty, op1), op0)
} else {
(self.into_temp_reg($ty, op0), op1)
}
};
match op0 { match op0 {
ValueLocation::Reg(reg) => { ValueLocation::Reg(reg) => {
@@ -1252,6 +1465,12 @@ impl<M: ModuleContext> Context<'_, M> {
cmp_i64!(i64_gt_s, setg, setnge, |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); cmp_i64!(i64_ge_s, setge, setng, |a, b| a >= b);
cmp_f32!(f32_gt, f32_lt, seta, |a, b| a > b);
cmp_f32!(f32_ge, f32_le, setnc, |a, b| a >= b);
cmp_f64!(f64_gt, f64_lt, seta, |a, b| a > b);
cmp_f64!(f64_ge, f64_le, setnc, |a, b| a >= b);
// TODO: Should we do this logic in `eq` and just have this delegate to `eq`? // TODO: Should we do this logic in `eq` and just have this delegate to `eq`?
// That would mean that `eqz` and `eq` with a const 0 argument don't // That would mean that `eqz` and `eq` with a const 0 argument don't
// result in different code. It would also allow us to generate better // result in different code. It would also allow us to generate better
@@ -1710,6 +1929,48 @@ impl<M: ModuleContext> Context<'_, M> {
} }
} }
pub fn f32_neg(&mut self) {
let val = self.pop();
let out = if let Some(i) = val.imm_f32() {
ValueLocation::Immediate(
wasmparser::Ieee32((-f32::from_bits(i.bits())).to_bits()).into(),
)
} else {
let reg = self.into_temp_reg(GPRType::Rx, val);
let const_label = self.neg_const_f32_label();
dynasm!(self.asm
; xorps Rx(reg.rx().unwrap()), [=>const_label.0]
);
ValueLocation::Reg(reg)
};
self.push(out);
}
pub fn f64_neg(&mut self) {
let val = self.pop();
let out = if let Some(i) = val.imm_f64() {
ValueLocation::Immediate(
wasmparser::Ieee64((-f64::from_bits(i.bits())).to_bits()).into(),
)
} else {
let reg = self.into_temp_reg(GPRType::Rx, val);
let const_label = self.neg_const_f64_label();
dynasm!(self.asm
; xorpd Rx(reg.rx().unwrap()), [=>const_label.0]
);
ValueLocation::Reg(reg)
};
self.push(out);
}
unop!(i32_clz, lzcnt, Rd, u32, u32::leading_zeros); unop!(i32_clz, lzcnt, Rd, u32, u32::leading_zeros);
unop!(i64_clz, lzcnt, Rq, u64, |a: u64| a.leading_zeros() as u64); unop!(i64_clz, lzcnt, Rq, u64, |a: u64| a.leading_zeros() as u64);
unop!(i32_ctz, tzcnt, Rd, u32, u32::trailing_zeros); unop!(i32_ctz, tzcnt, Rd, u32, u32::trailing_zeros);
@@ -1719,22 +1980,25 @@ impl<M: ModuleContext> Context<'_, M> {
// TODO: Use `lea` when the LHS operand isn't a temporary but both of the operands // TODO: Use `lea` when the LHS operand isn't a temporary but both of the operands
// are in registers. // are in registers.
commutative_binop_i32!(i32_add, add, |a, b| (a as i32).wrapping_add(b as i32)); commutative_binop_i32!(i32_add, add, i32::wrapping_add);
commutative_binop_i32!(i32_and, and, |a, b| a & b); commutative_binop_i32!(i32_and, and, |a, b| a & b);
commutative_binop_i32!(i32_or, or, |a, b| a | b); commutative_binop_i32!(i32_or, or, |a, b| a | b);
commutative_binop_i32!(i32_xor, xor, |a, b| a ^ b); commutative_binop_i32!(i32_xor, xor, |a, b| a ^ b);
binop_i32!(i32_sub, sub, i32::wrapping_sub);
commutative_binop_i64!(i64_add, add, i64::wrapping_add); commutative_binop_i64!(i64_add, add, i64::wrapping_add);
commutative_binop_i64!(i64_and, and, |a, b| a & b); commutative_binop_i64!(i64_and, and, |a, b| a & b);
commutative_binop_i64!(i64_or, or, |a, b| a | b); commutative_binop_i64!(i64_or, or, |a, b| a | b);
commutative_binop_i64!(i64_xor, xor, |a, b| a ^ b); commutative_binop_i64!(i64_xor, xor, |a, b| a ^ b);
binop_i64!(i64_sub, sub, i64::wrapping_sub);
commutative_binop_f32!(f32_add, addss, |a: wasmparser::Ieee32, b: wasmparser::Ieee32| wasmparser::Ieee32( commutative_binop_f32!(f32_add, addss, |a, b| a + b);
(f32::from_bits(a.bits()) + f32::from_bits(b.bits())).to_bits() commutative_binop_f32!(f32_mul, mulss, |a, b| a * b);
)); binop_f32!(f32_sub, subss, |a, b| a - b);
commutative_binop_f64!(f64_add, addsd, |a: wasmparser::Ieee64, b: wasmparser::Ieee64| wasmparser::Ieee64(
(f64::from_bits(a.bits()) + f64::from_bits(b.bits())).to_bits() commutative_binop_f64!(f64_add, addsd, |a, b| a + b);
)); commutative_binop_f64!(f64_mul, mulsd, |a, b| a * b);
binop_f64!(f64_sub, subsd, |a, b| a - b);
shift!( shift!(
i32_shl, i32_shl,
@@ -1808,52 +2072,6 @@ impl<M: ModuleContext> Context<'_, M> {
I64 I64
); );
// `sub` is not commutative, so we have to handle it differently (we _must_ use the `op1`
// temp register as the output)
pub fn i64_sub(&mut self) {
let op0 = self.pop();
let op1 = self.pop();
if let Some(i1) = op1.immediate() {
if let Some(i0) = op0.immediate() {
self.push(ValueLocation::Immediate(
(i1.as_i64().unwrap() - i0.as_i64().unwrap()).into(),
));
return;
}
}
let op1 = self.into_temp_reg(I32, op1);
match op0 {
ValueLocation::Reg(reg) => {
dynasm!(self.asm
; sub Rq(op1.rq().unwrap()), Rq(reg.rq().unwrap())
);
}
ValueLocation::Stack(offset) => {
let offset = self.adjusted_offset(offset);
dynasm!(self.asm
; sub Rq(op1.rq().unwrap()), [rsp + offset]
);
}
ValueLocation::Immediate(i) => {
let i = i.as_int().unwrap();
if let Some(i) = i.try_into() {
dynasm!(self.asm
; sub Rq(op1.rq().unwrap()), i
);
} else {
unimplemented!(concat!(
"Unsupported `sub` with large 64-bit immediate operand"
));
}
}
}
self.push(ValueLocation::Reg(op1));
self.free_value(op0);
}
// `i64_mul` needs to be seperate because the immediate form of the instruction // `i64_mul` needs to be seperate because the immediate form of the instruction
// has a different syntax to the immediate form of the other instructions. // has a different syntax to the immediate form of the other instructions.
pub fn i64_mul(&mut self) { pub fn i64_mul(&mut self) {
@@ -1910,45 +2128,6 @@ impl<M: ModuleContext> Context<'_, M> {
self.free_value(op0); self.free_value(op0);
} }
// `sub` is not commutative, so we have to handle it differently (we _must_ use the `op1`
// temp register as the output)
pub fn i32_sub(&mut self) {
let op0 = self.pop();
let op1 = self.pop();
if let Some(i1) = op1.imm_i32() {
if let Some(i0) = op0.imm_i32() {
self.block_state
.stack
.push(ValueLocation::Immediate(i1.wrapping_sub(i0).into()));
return;
}
}
let op1 = self.into_temp_reg(I64, op1);
match op0 {
ValueLocation::Reg(reg) => {
dynasm!(self.asm
; sub Rd(op1.rq().unwrap()), Rd(reg.rq().unwrap())
);
}
ValueLocation::Stack(offset) => {
let offset = self.adjusted_offset(offset);
dynasm!(self.asm
; sub Rd(op1.rq().unwrap()), [rsp + offset]
);
}
ValueLocation::Immediate(i) => {
dynasm!(self.asm
; sub Rd(op1.rq().unwrap()), i.as_i32().unwrap()
);
}
}
self.push(ValueLocation::Reg(op1));
self.free_value(op0);
}
// `i32_mul` needs to be seperate because the immediate form of the instruction // `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. // has a different syntax to the immediate form of the other instructions.
pub fn i32_mul(&mut self) { pub fn i32_mul(&mut self) {
@@ -2302,15 +2481,46 @@ impl<M: ModuleContext> Context<'_, M> {
); );
} }
fn align(&mut self, align_to: u32) {
while self.asm.offset().0 % align_to as usize != 0 {
dynasm!(self.asm
; .byte 0
);
}
}
/// Writes the function epilogue (right now all this does is add the trap label that the /// Writes the function epilogue (right now all this does is add the trap label that the
/// conditional traps in `call_indirect` use) /// conditional traps in `call_indirect` use)
pub fn epilogue(&mut self) { pub fn epilogue(&mut self) {
if let Some(l) = self.trap_label { // TODO: We don't want to redefine this label if we're sharing it between functions
if let Some(l) = self.labels.trap {
self.define_label(l); self.define_label(l);
dynasm!(self.asm dynasm!(self.asm
; ud2 ; ud2
); );
} }
if let Some(l) = self.labels.neg_const_f32 {
self.align(16);
self.define_label(l);
dynasm!(self.asm
; .dword -2147483648
; .dword 0
; .dword 0
; .dword 0
);
}
if let Some(l) = self.labels.neg_const_f64 {
self.align(16);
self.define_label(l);
dynasm!(self.asm
; .dword 0
; .dword -2147483648
; .dword 0
; .dword 0
);
}
} }
pub fn trap(&mut self) { pub fn trap(&mut self) {
@@ -2321,12 +2531,34 @@ impl<M: ModuleContext> Context<'_, M> {
#[must_use] #[must_use]
fn trap_label(&mut self) -> Label { fn trap_label(&mut self) -> Label {
if let Some(l) = self.trap_label { if let Some(l) = self.labels.trap {
return l; return l;
} }
let label = self.create_label(); let label = self.create_label();
self.trap_label = Some(label); self.labels.trap = Some(label);
label
}
#[must_use]
fn neg_const_f32_label(&mut self) -> Label {
if let Some(l) = self.labels.neg_const_f32 {
return l;
}
let label = self.create_label();
self.labels.neg_const_f32 = Some(label);
label
}
#[must_use]
fn neg_const_f64_label(&mut self) -> Label {
if let Some(l) = self.labels.neg_const_f64 {
return l;
}
let label = self.create_label();
self.labels.neg_const_f64 = Some(label);
label label
} }
} }

View File

@@ -355,8 +355,22 @@ where
Operator::Clz(Size::_64) => ctx.i64_clz(), Operator::Clz(Size::_64) => ctx.i64_clz(),
Operator::Ctz(Size::_64) => ctx.i64_ctz(), Operator::Ctz(Size::_64) => ctx.i64_ctz(),
Operator::Popcnt(Size::_64) => ctx.i64_popcnt(), Operator::Popcnt(Size::_64) => ctx.i64_popcnt(),
Operator::Add(F64) => ctx.f64_add(),
Operator::Add(F32) => ctx.f32_add(), Operator::Add(F32) => ctx.f32_add(),
Operator::Mul(F32) => ctx.f32_mul(),
Operator::Sub(F32) => ctx.f32_sub(),
Operator::Neg(Size::_32) => ctx.f32_neg(),
Operator::Gt(SF32) => ctx.f32_gt(),
Operator::Ge(SF32) => ctx.f32_ge(),
Operator::Lt(SF32) => ctx.f32_lt(),
Operator::Le(SF32) => ctx.f32_le(),
Operator::Add(F64) => ctx.f64_add(),
Operator::Mul(F64) => ctx.f64_mul(),
Operator::Sub(F64) => ctx.f64_sub(),
Operator::Neg(Size::_64) => ctx.f64_neg(),
Operator::Gt(SF64) => ctx.f64_gt(),
Operator::Ge(SF64) => ctx.f64_ge(),
Operator::Lt(SF64) => ctx.f64_lt(),
Operator::Le(SF64) => ctx.f64_le(),
Operator::Drop(range) => ctx.drop(range), Operator::Drop(range) => ctx.drop(range),
Operator::Const(val) => ctx.const_(val), Operator::Const(val) => ctx.const_(val),
Operator::Load { ty: I32, memarg } => ctx.i32_load(memarg.offset)?, Operator::Load { ty: I32, memarg } => ctx.i32_load(memarg.offset)?,

View File

@@ -291,50 +291,235 @@ mod op64 {
} }
mod opf32 { mod opf32 {
use super::translate_wat; use super::{translate_wat, ExecutableModule};
quickcheck! { macro_rules! binop_test {
fn as_params(a: f64, b: f64) -> bool { ($op:ident, $func:expr) => {
const CODE: &str = r#"(module binop_test!($op, $func, f32);
(func (param f64) (param f64) (result f64) };
(f64.add (get_local 0) (get_local 1)))) ($op:ident, $func:expr, $retty:ident) => {
"#; mod $op {
translate_wat(CODE).execute_func::<(f64, f64), f64>(0, (a, b)) == Ok(a + b) use super::{translate_wat, ExecutableModule};
}
fn lit_lit(a: f64, b: f64) -> bool { const RETTY: &str = stringify!($retty);
translate_wat(&format!(" const OP: &str = stringify!($op);
(module (func (result f64)
(f64.add (f64.const {left}) (f64.const {right}))))
", left = a, right = b)).execute_func::<(), f64>(0, ()) == Ok(a + b)
}
fn lit_reg(a: f64, b: f64) -> bool { lazy_static! {
use std::sync::Once; static ref AS_PARAMS: ExecutableModule = translate_wat(&format!("
(module (func (param f32) (param f32) (result {retty})
(f32.{op} (get_local 0) (get_local 1))))
", retty = RETTY, op = OP));
}
let translated = translate_wat(&format!(" quickcheck! {
(module (func (param f64) (result f64) fn as_params(a: f32, b: f32) -> bool {
(f64.add (f64.const {left}) (get_local 0)))) AS_PARAMS.execute_func::<(f32, f32), $retty>(0, (a, b)) == Ok($func(a, b) as $retty)
", left = a)); }
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(f64,), f64>(0, (b,)) == Ok(a + b) fn lit_lit(a: f32, b: f32) -> bool {
} translate_wat(&format!("
(module (func (result {retty})
(f32.{op} (f32.const {left}) (f32.const {right}))))
", retty = RETTY, op = OP, left = a, right = b)).execute_func::<(), $retty>(0, ()) == Ok($func(a, b) as $retty)
}
fn reg_lit(a: f64, b: f64) -> bool { fn lit_reg(a: f32, b: f32) -> bool {
use std::sync::Once; use std::sync::Once;
let translated = translate_wat(&format!(" let translated = translate_wat(&format!("
(module (func (param f64) (result f64) (module (func (param f32) (result {retty})
(f64.add (get_local 0) (f64.const {right})))) (f32.{op} (f32.const {left}) (get_local 0))))
", right = b)); ", retty = RETTY, op = OP, left = a));
static ONCE: Once = Once::new(); static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble()); ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(f64,), f64>(0, (a,)) == Ok(a + b) translated.execute_func::<(f32,), $retty>(0, (b,)) == Ok($func(a, b) as $retty)
} }
fn reg_lit(a: f32, b: f32) -> bool {
use std::sync::Once;
let translated = translate_wat(&format!("
(module (func (param f32) (result {retty})
(f32.{op} (get_local 0) (f32.const {right}))))
", retty = RETTY, op = OP, right = b));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(f32,), $retty>(0, (a,)) == Ok($func(a, b) as $retty)
}
}
}
};
} }
macro_rules! unop_test {
($name:ident, $func:expr) => {
unop_test!($name, $func, f32);
};
($name:ident, $func:expr, $out_ty:ty) => {
mod $name {
use super::{translate_wat, ExecutableModule};
use std::sync::Once;
lazy_static! {
static ref AS_PARAM: ExecutableModule = translate_wat(concat!(
"(module (func (param f32) (result ",
stringify!($out_ty),
")
(f32.",
stringify!($name),
" (get_local 0))))"
),);
}
quickcheck! {
fn as_param(a: f32) -> bool {
static ONCE: Once = Once::new();
ONCE.call_once(|| AS_PARAM.disassemble());
AS_PARAM.execute_func::<(f32,), $out_ty>(0, (a,)) == Ok($func(a))
}
fn lit(a: f32) -> bool {
let translated = translate_wat(&format!(concat!("
(module (func (result ",stringify!($out_ty),")
(f32.",stringify!($name)," (f32.const {val}))))
"), val = a));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(), $out_ty>(0, ()) == Ok($func(a))
}
}
}
};
}
binop_test!(add, |a, b| a + b);
binop_test!(mul, |a, b| a * b);
binop_test!(sub, |a, b| a - b);
binop_test!(gt, |a, b| a > b, i32);
binop_test!(lt, |a, b| a < b, i32);
binop_test!(ge, |a, b| a >= b, i32);
binop_test!(le, |a, b| a <= b, i32);
unop_test!(neg, |a: f32| -a);
}
mod opf64 {
use super::{translate_wat, ExecutableModule};
macro_rules! binop_test {
($op:ident, $func:expr) => {
binop_test!($op, $func, f64);
};
($op:ident, $func:expr, $retty:ident) => {
mod $op {
use super::{translate_wat, ExecutableModule};
const RETTY: &str = stringify!($retty);
const OP: &str = stringify!($op);
lazy_static! {
static ref AS_PARAMS: ExecutableModule = translate_wat(&format!("
(module (func (param f64) (param f64) (result {retty})
(f64.{op} (get_local 0) (get_local 1))))
", retty = RETTY, op = OP));
}
quickcheck! {
fn as_params(a: f64, b: f64) -> bool {
AS_PARAMS.execute_func::<(f64, f64), $retty>(0, (a, b)) == Ok($func(a, b) as $retty)
}
fn lit_lit(a: f64, b: f64) -> bool {
translate_wat(&format!("
(module (func (result {retty})
(f64.{op} (f64.const {left}) (f64.const {right}))))
", retty = RETTY, op = OP, left = a, right = b)).execute_func::<(), $retty>(0, ()) == Ok($func(a, b) as $retty)
}
fn lit_reg(a: f64, b: f64) -> bool {
use std::sync::Once;
let translated = translate_wat(&format!("
(module (func (param f64) (result {retty})
(f64.{op} (f64.const {left}) (get_local 0))))
", retty = RETTY, op = OP, left = a));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(f64,), $retty>(0, (b,)) == Ok($func(a, b) as $retty)
}
fn reg_lit(a: f64, b: f64) -> bool {
use std::sync::Once;
let translated = translate_wat(&format!("
(module (func (param f64) (result {retty})
(f64.{op} (get_local 0) (f64.const {right}))))
", retty = RETTY, op = OP, right = b));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(f64,), $retty>(0, (a,)) == Ok($func(a, b) as $retty)
}
}
}
};
}
macro_rules! unop_test {
($name:ident, $func:expr) => {
unop_test!($name, $func, f64);
};
($name:ident, $func:expr, $out_ty:ty) => {
mod $name {
use super::{translate_wat, ExecutableModule};
use std::sync::Once;
lazy_static! {
static ref AS_PARAM: ExecutableModule = translate_wat(concat!(
"(module (func (param f64) (result ",
stringify!($out_ty),
")
(f64.",
stringify!($name),
" (get_local 0))))"
),);
}
quickcheck! {
fn as_param(a: f64) -> bool {
static ONCE: Once = Once::new();
ONCE.call_once(|| AS_PARAM.disassemble());
AS_PARAM.execute_func::<(f64,), $out_ty>(0, (a,)) == Ok($func(a))
}
fn lit(a: f64) -> bool {
let translated = translate_wat(&format!(concat!("
(module (func (result ",stringify!($out_ty),")
(f64.",stringify!($name)," (f64.const {val}))))
"), val = a));
static ONCE: Once = Once::new();
ONCE.call_once(|| translated.disassemble());
translated.execute_func::<(), $out_ty>(0, ()) == Ok($func(a))
}
}
}
};
}
binop_test!(add, |a, b| a + b);
binop_test!(mul, |a, b| a * b);
binop_test!(sub, |a, b| a - b);
binop_test!(gt, |a, b| a > b, i32);
binop_test!(lt, |a, b| a < b, i32);
binop_test!(ge, |a, b| a >= b, i32);
binop_test!(le, |a, b| a <= b, i32);
unop_test!(neg, |a: f64| -a);
} }
quickcheck! { quickcheck! {