makepad/libs/relax/backend/tests/fload_cmp.rs

85 lines
4 KiB
Rust

//! `t = load f32/f64 [p + o]; branch on fcmp x, y` with t one of them (used once) compiles to
//! `ucomiss/ucomisd reg, [p + o]` where t is the operand the compare reads from r/m: the branch
//! must decide as the two-register compare does for every condition, operand order and value,
//! NaN and signed zeros included (and stay a register compare where the load is the other side).
#![cfg(target_arch = "x86_64")]
use relax_backend::rir::*;
use relax_backend::x64;
fn push(f: &mut Func, b: u32, i: Inst) {
f.blocks[b as usize].insts.push(i);
f.blocks[b as usize].pos.push(0);
}
/// f(p) = if (a = load(p), t = load(p + 8); cond on (a, t) or (t, a)) { 1 } else { 2 }
fn cmp_fn(f64_: bool, cond: Cond, t_first: bool) -> Func {
let mut f = Func::new("fload_cmp".to_string(), 0);
let p = f.vreg(Cls::I);
f.params = vec![p];
f.rets = vec![Cls::I];
let (b0, b1, b2) = (f.block(), f.block(), f.block());
let fc = if f64_ { Cls::F64 } else { Cls::F32 };
let (a, t, c, one, two) = (f.vreg(fc), f.vreg(fc), f.vreg(Cls::I), f.vreg(Cls::I), f.vreg(Cls::I));
let m = if f64_ { Mem::F64 } else { Mem::F32 };
push(&mut f, b0, Inst::Load(m, a, p, 0));
push(&mut f, b0, Inst::Load(m, t, p, 8));
let (x, y) = if t_first { (t, a) } else { (a, t) };
push(&mut f, b0, Inst::FCmp(cond, f64_, c, x, y));
f.blocks[b0 as usize].term = Term::Branch(c, b1, b2);
push(&mut f, b1, Inst::Iconst(one, 1));
f.blocks[b1 as usize].term = Term::Ret(vec![one]);
push(&mut f, b2, Inst::Iconst(two, 2));
f.blocks[b2 as usize].term = Term::Ret(vec![two]);
f.verify().unwrap();
f
}
fn holds(c: Cond, x: f64, y: f64) -> bool {
match c {
Cond::Eq => x == y,
Cond::Ne => x != y,
Cond::Lt => x < y,
Cond::Le => x <= y,
Cond::Gt => x > y,
Cond::Ge => x >= y,
}
}
#[test]
fn float_load_compare_branches_as_the_register_compare() {
let env = x64::Env { slots: relax_backend::rir::SlotsPtr::NONE, tls_key: 0, tls_slow: 0, resume: 0, opt_regs: true, syms: 0, sym_class: 0, near: false };
let vals: [f64; 8] = [0.0, -0.0, 1.0, -1.5, 2.5, f64::INFINITY, f64::NEG_INFINITY, f64::NAN];
let conds = [Cond::Eq, Cond::Ne, Cond::Lt, Cond::Le, Cond::Gt, Cond::Ge];
let mut n = 0;
for f64_ in [false, true] {
for cond in conds {
for t_first in [false, true] {
let f = cmp_fn(f64_, cond, t_first);
let c = x64::compile(&relax_session::Options::default(), &f, &env).unwrap();
// ucomis* reg, [p + 8] (0F 2E, modrm mod 01, disp8 8) exactly where t is the r/m operand
let fused = c.code.windows(4).any(|w| w[0] == 0x0f && w[1] == 0x2e && w[2] >> 6 == 1 && w[3] == 8);
let want_fused = match cond { Cond::Eq | Cond::Ne => true, Cond::Lt | Cond::Le => t_first, Cond::Gt | Cond::Ge => !t_first };
assert_eq!(fused, want_fused, "f64 {f64_} {cond:?} t_first {t_first}");
let code = relax_backend::exec::Code::new(&c.code).unwrap();
let run: extern "sysv64" fn(*const u8) -> u64 = unsafe { std::mem::transmute(code.ptr()) };
for &av in &vals {
for &tv in &vals {
let mut buf = [0u8; 16];
if f64_ {
buf[0..8].copy_from_slice(&av.to_le_bytes());
buf[8..16].copy_from_slice(&tv.to_le_bytes());
} else {
buf[0..4].copy_from_slice(&(av as f32).to_le_bytes());
buf[8..12].copy_from_slice(&(tv as f32).to_le_bytes());
}
let (x, y) = if t_first { (tv, av) } else { (av, tv) };
let want = if holds(cond, x, y) { 1 } else { 2 };
assert_eq!(run(buf.as_ptr()), want, "f64 {f64_} {cond:?} t_first {t_first} a {av} t {tv}");
n += 1;
}
}
}
}
}
assert!(n > 0);
}