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

231 lines
8.9 KiB
Rust

//! Execute raw RIR so every select path is tested independently of if-conversion.
#![cfg(target_arch = "x86_64")]
use relax_backend::{exec::Code, rir::*, x64};
fn push(f: &mut Func, i: Inst) {
f.blocks[0].insts.push(i);
f.blocks[0].pos.push(0);
}
fn compile(w: bool, cond: Option<Cond>, swap_cmp: bool, swap_select: bool, shared: u8, alias: u8, generic: u8, regs: bool) -> Code {
let cls = if w { Cls::F64 } else { Cls::F32 };
let mut f = Func::new("float_select".into(), 0);
let a = f.vreg(cls);
let b = f.vreg(cls);
let flag = f.vreg(Cls::I);
f.params = vec![a, b, flag];
f.rets = vec![cls];
f.block();
let c = f.vreg(Cls::I);
let (x, y) = if swap_cmp { (b, a) } else { (a, b) };
if let Some(cc) = cond {
push(&mut f, Inst::FCmp(cc, w, c, x, y));
} else if generic == 1 {
push(&mut f, Inst::ICmpI(Cond::Lt, true, c, flag, 0));
} else if generic == 2 {
push(&mut f, Inst::ICmp(Cond::Lt, true, c, flag, flag));
} else {
push(&mut f, Inst::Mov(c, flag));
}
if shared == 2 {
let gap = f.vreg(Cls::I);
push(&mut f, Inst::IBinI(IOp::Add, IntTy { bits: 64, signed: false }, gap, flag, 1));
}
let d = match alias {
1 => a,
2 => b,
_ => f.vreg(cls),
};
let (t, e) = if swap_select { (b, a) } else { (a, b) };
push(&mut f, Inst::Sel(d, c, t, e));
if shared != 0 {
// A second use keeps the materialized compare alive.
let extra = f.vreg(Cls::I);
push(&mut f, Inst::Sel(extra, c, flag, c));
f.rets.push(Cls::I);
f.blocks[0].term = Term::Ret(vec![d, extra]);
} else {
f.blocks[0].term = Term::Ret(vec![d]);
}
f.verify().unwrap();
let env = x64::Env {
slots: relax_backend::rir::SlotsPtr::NONE,
tls_key: 0,
tls_slow: 0,
resume: 0,
opt_regs: regs,
syms: 0,
sym_class: 0,
near: false,
};
let c = x64::compile(&relax_session::Options::default(), &f, &env).unwrap();
assert!(c.relocs.is_empty());
Code::new(&c.code).unwrap()
}
fn compare(c: Cond, a: f64, b: f64) -> bool {
match c {
Cond::Gt => a > b,
Cond::Ge => a >= b,
Cond::Lt => a < b,
Cond::Le => a <= b,
Cond::Eq => a == b,
Cond::Ne => a != b,
}
}
#[test]
fn scalar_select_bits_and_aliases() {
let values = [0.0, -0.0, 1.0, -1.0, f64::INFINITY, f64::NEG_INFINITY, f64::from_bits(0x7ff8000000001234), -f64::NAN];
for w in [false, true] {
for regs in [false, true] {
for cc in [Cond::Gt, Cond::Ge, Cond::Lt, Cond::Le, Cond::Eq, Cond::Ne] {
for swap_cmp in [false, true] {
for swap_select in [false, true] {
for shared in 0..3 {
for alias in 0..3 {
let code = compile(w, Some(cc), swap_cmp, swap_select, shared, alias, 0, regs);
for a in values {
for b in values {
let (x, y) = if swap_cmp { (b, a) } else { (a, b) };
let take = compare(cc, x, y) != swap_select;
if w {
let run: extern "sysv64" fn(f64, f64, i64) -> f64 = unsafe { std::mem::transmute(code.ptr()) };
assert_eq!(
run(a, b, 7).to_bits(),
(if take { a } else { b }).to_bits(),
"{cc:?} {swap_cmp} {swap_select} {shared} {alias} {regs}"
);
} else {
let run: extern "sysv64" fn(f32, f32, i64) -> f32 = unsafe { std::mem::transmute(code.ptr()) };
assert_eq!(
run(a as f32, b as f32, 7).to_bits(),
(if take { a as f32 } else { b as f32 }).to_bits(),
"{cc:?} {swap_cmp} {swap_select} {shared} {alias} {regs}"
);
}
}
}
}
}
}
}
}
}
}
}
#[test]
fn generic_and_integer_compare_selects() {
for w in [false, true] {
for regs in [false, true] {
for generic in 0..3 {
for alias in 0..3 {
let code = compile(w, None, false, false, 0, alias, generic, regs);
for flag in [-7i64, 0, 1] {
let take = match generic {
1 => flag < 0,
2 => false,
_ => flag != 0,
};
if w {
let run: extern "sysv64" fn(f64, f64, i64) -> f64 = unsafe { std::mem::transmute(code.ptr()) };
assert_eq!(run(-0.0, f64::NAN, flag).to_bits(), (if take { -0.0f64 } else { f64::NAN }).to_bits());
} else {
let run: extern "sysv64" fn(f32, f32, i64) -> f32 = unsafe { std::mem::transmute(code.ptr()) };
assert_eq!(run(-0.0, f32::NAN, flag).to_bits(), (if take { -0.0f32 } else { f32::NAN }).to_bits());
}
}
}
}
}
}
}
#[test]
fn unrelated_float_compare_and_redefined_input() {
for cc in [Cond::Gt, Cond::Ge, Cond::Lt, Cond::Le, Cond::Eq, Cond::Ne] {
for redefine in [false, true] {
let mut f = Func::new("unrelated_select".into(), 0);
let a = f.vreg(Cls::F64);
let b = f.vreg(Cls::F64);
let x = f.vreg(Cls::F64);
let y = f.vreg(Cls::F64);
let c = f.vreg(Cls::I);
let d = f.vreg(Cls::F64);
f.params = vec![a, b, x, y];
f.rets = vec![Cls::F64];
f.block();
push(&mut f, Inst::FCmp(cc, true, c, if redefine { a } else { x }, if redefine { b } else { y }));
if redefine {
push(&mut f, Inst::Fconst(a, 0, true));
}
push(&mut f, Inst::Sel(d, c, a, b));
f.blocks[0].term = Term::Ret(vec![d]);
f.verify().unwrap();
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 compiled = x64::compile(&relax_session::Options::default(), &f, &env).unwrap();
let code = Code::new(&compiled.code).unwrap();
let run: extern "sysv64" fn(f64, f64, f64, f64) -> f64 = unsafe { std::mem::transmute(code.ptr()) };
for x in [0.0, -0.0, 1.0, f64::NAN] {
for y in [0.0, -1.0, 1.0, f64::NAN] {
let expected = if redefine {
if compare(cc, 2.0, 1.0) {
0.0f64
} else {
1.0
}
} else if compare(cc, x, y) {
2.0
} else {
1.0
};
assert_eq!(run(2.0, 1.0, x, y).to_bits(), expected.to_bits());
}
}
}
}
}
#[test]
fn f64_compare_selects_f32() {
let mut f = Func::new("mixed_width_select".into(), 0);
let a = f.vreg(Cls::F32);
let b = f.vreg(Cls::F32);
let x = f.vreg(Cls::F64);
let y = f.vreg(Cls::F64);
let c = f.vreg(Cls::I);
let d = f.vreg(Cls::F32);
f.params = vec![a, b, x, y];
f.rets = vec![Cls::F32];
f.block();
push(&mut f, Inst::FCmp(Cond::Gt, true, c, x, y));
push(&mut f, Inst::Sel(d, c, a, b));
f.blocks[0].term = Term::Ret(vec![d]);
f.verify().unwrap();
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 compiled = x64::compile(&relax_session::Options::default(), &f, &env).unwrap();
let code = Code::new(&compiled.code).unwrap();
let run: extern "sysv64" fn(f32, f32, f64, f64) -> f32 = unsafe { std::mem::transmute(code.ptr()) };
for (x, y) in [(2.0, 1.0), (1.0, 2.0), (f64::NAN, 0.0), (0.0, f64::NAN), (0.0, -0.0)] {
assert_eq!(run(-0.0, f32::NAN, x, y).to_bits(), (if x > y { -0.0f32 } else { f32::NAN }).to_bits());
}
}