85 lines
4 KiB
Rust
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);
|
|
}
|