106 lines
4.5 KiB
Rust
106 lines
4.5 KiB
Rust
//! `if !(a <= b)` and friends: a compare that only feeds a negated branch condition branches on
|
|
//! the flags directly (no materialised bool), and NaN operands keep Rust's semantics: every
|
|
//! ordered compare is false on NaN so its negation is true; `!=` is true on NaN so its negation
|
|
//! is false.
|
|
#![cfg(target_arch = "x86_64")]
|
|
use relax_backend::{exec::Code, rir::*, x64};
|
|
use relax_backend::passes;
|
|
|
|
const CONDS: [Cond; 6] = [Cond::Lt, Cond::Le, Cond::Gt, Cond::Ge, Cond::Eq, Cond::Ne];
|
|
|
|
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(a, b) = if NOT(a cc b) { 11 } else { 22 }, the NOT spelt `form`: 0 `== 0`, 1 `^ 1`, 2 none
|
|
/// (a plain branch), 3 `!= 0`.
|
|
fn build(w: bool, cc: Cond, form: u8, order: bool) -> Func {
|
|
let cls = if w { Cls::F64 } else { Cls::F32 };
|
|
let mut f = Func::new("branch_not".into(), 0);
|
|
let (a, b) = (f.vreg(cls), f.vreg(cls));
|
|
f.params = vec![a, b];
|
|
f.rets = vec![Cls::I];
|
|
let (b0, b1, b2) = (f.block(), f.block(), f.block());
|
|
let (c, d, k) = (f.vreg(Cls::I), f.vreg(Cls::I), f.vreg(Cls::I));
|
|
push(&mut f, b0, Inst::FCmp(cc, w, c, a, b));
|
|
let cond = match form {
|
|
0 => {
|
|
push(&mut f, b0, Inst::ICmpI(Cond::Eq, false, d, c, 0));
|
|
d
|
|
}
|
|
1 => {
|
|
push(&mut f, b0, Inst::IBinI(IOp::Xor, IntTy { bits: 8, signed: false }, d, c, 1));
|
|
d
|
|
}
|
|
3 => {
|
|
push(&mut f, b0, Inst::ICmpI(Cond::Ne, false, d, c, 0));
|
|
d
|
|
}
|
|
_ => c,
|
|
};
|
|
// block order decides which successor falls through
|
|
f.blocks[b0 as usize].term = if order { Term::Branch(cond, b1, b2) } else { Term::Branch(cond, b2, b1) };
|
|
push(&mut f, b1, Inst::Iconst(k, 11));
|
|
f.blocks[b1 as usize].term = Term::Ret(vec![k]);
|
|
let k2 = f.vreg(Cls::I);
|
|
push(&mut f, b2, Inst::Iconst(k2, 22));
|
|
f.blocks[b2 as usize].term = Term::Ret(vec![k2]);
|
|
f.verify().unwrap();
|
|
f
|
|
}
|
|
|
|
fn truth(cc: Cond, a: f64, b: f64) -> bool {
|
|
match cc {
|
|
Cond::Lt => a < b,
|
|
Cond::Le => a <= b,
|
|
Cond::Gt => a > b,
|
|
Cond::Ge => a >= b,
|
|
Cond::Eq => a == b,
|
|
Cond::Ne => a != b,
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn negated_compare_branches_on_the_flags() {
|
|
let vals = [0.0, -0.0, 1.0, -1.0, 2.5, f64::INFINITY, f64::NEG_INFINITY, f64::NAN, -f64::NAN];
|
|
let options = relax_session::Options::default();
|
|
for w in [false, true] {
|
|
for cc in CONDS {
|
|
for form in 0..4u8 {
|
|
for order in [false, true] {
|
|
let mut f = build(w, cc, form, order);
|
|
passes::simplify(&options, &mut f, None, true, true);
|
|
if form != 2 {
|
|
// the negation is gone: the compare is the block's last instruction and
|
|
// the branch reads it
|
|
let b0 = &f.blocks[0];
|
|
assert!(matches!(b0.insts.last(), Some(Inst::FCmp(..))), "{w} {cc:?} {form}: {:?}", b0.insts);
|
|
assert!(b0.insts.iter().all(|i| !matches!(i, Inst::ICmpI(..) | Inst::IBinI(..))));
|
|
}
|
|
for regs in [false, true] {
|
|
let env = x64::Env { slots: SlotsPtr::NONE, tls_key: 0, tls_slow: 0, resume: 0, opt_regs: regs, syms: 0, sym_class: 0, near: false };
|
|
let c = x64::compile(&options, &f, &env).unwrap();
|
|
let code = Code::new(&c.code).unwrap();
|
|
for a in vals {
|
|
for b in vals {
|
|
let cmp = truth(cc, a, b);
|
|
let negated = form == 0 || form == 1;
|
|
let taken_b1 = if negated { !cmp } else { cmp } == order;
|
|
let want: u64 = if taken_b1 { 11 } else { 22 };
|
|
let got = if w {
|
|
let run: extern "sysv64" fn(f64, f64) -> u64 = unsafe { std::mem::transmute(code.ptr()) };
|
|
run(a, b)
|
|
} else {
|
|
let run: extern "sysv64" fn(f32, f32) -> u64 = unsafe { std::mem::transmute(code.ptr()) };
|
|
run(a as f32, b as f32)
|
|
};
|
|
assert_eq!(got, want, "w={w} {cc:?} form={form} order={order} regs={regs} a={a} b={b}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|