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

122 lines
4.9 KiB
Rust

//! `x / c` for a constant c = +-2^k whose reciprocal is exactly representable (normal or
//! subnormal) is `x * (1 / c)`: bit-equal for every x (NaN, inf, zeros, subnormal and
//! overflowing / underflowing results), and no divide is left. Other constants keep the divide.
#![cfg(target_arch = "x86_64")]
use relax_backend::{exec::Code, rir::*, x64};
use relax_backend::passes;
fn build(w: bool, c: u64) -> (Func, bool) {
let cls = if w { Cls::F64 } else { Cls::F32 };
let mut f = Func::new("recip_div".into(), 0);
let x = f.vreg(cls);
f.params = vec![x];
f.rets = vec![cls];
f.block();
let k = f.vreg(cls);
let d = f.vreg(cls);
for i in [Inst::Fconst(k, c, w), Inst::FBin(FOp::Div, w, d, x, k)] {
f.blocks[0].insts.push(i);
f.blocks[0].pos.push(0);
}
f.blocks[0].term = Term::Ret(vec![d]);
f.verify().unwrap();
let options = relax_session::Options::default();
passes::simplify(&options, &mut f, None, true, true);
let divides = f.blocks.iter().any(|b| b.insts.iter().any(|i| matches!(i, Inst::FBin(FOp::Div, ..))));
(f, divides)
}
fn run(f: &Func) -> Code {
let env = x64::Env { slots: SlotsPtr::NONE, tls_key: 0, tls_slow: 0, resume: 0, opt_regs: true, 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 next(s: &mut u64) -> u64 {
*s ^= *s << 13;
*s ^= *s >> 7;
*s ^= *s << 17;
*s
}
#[test]
fn f32_power_of_two_divisors() {
// (constant, folds): 2^-149 has no representable reciprocal; 2^127 -> 2^-127 is subnormal
let cases: [(f32, bool); 20] = [
(2.0, true),
(-2.0, true),
(0.5, true),
(-0.5, true),
(1.0, true),
(4.0, true),
(1024.0, true),
(f32::from_bits(0x7f000000), true), // 2^127
(f32::from_bits(0x00800000), true), // 2^-126
(f32::from_bits(0x00400000), true), // 2^-127 (subnormal): reciprocal 2^127
(f32::from_bits(0x00200000), false), // 2^-128: reciprocal 2^128 overflows
(f32::from_bits(0x00000001), false), // 2^-149
(f32::from_bits(0x80000001), false),
(3.0, false),
(0.1, false),
(0.0, false),
(-0.0, false),
(f32::INFINITY, false),
(f32::NAN, false),
(f32::from_bits(0x00600000), false), // 1.5 * 2^-126: not a power of two
];
let mut s = 0x9e3779b97f4a7c15u64;
for (c, folds) in cases {
let (f, divides) = build(false, c.to_bits() as u64);
assert_eq!(!divides, folds, "f32 {c:e}");
let code = run(&f);
let go: extern "sysv64" fn(f32) -> f32 = unsafe { std::mem::transmute(code.ptr()) };
let mut xs: Vec<f32> = vec![0.0, -0.0, 1.0, -1.0, 3.0, f32::INFINITY, f32::NEG_INFINITY, f32::MAX, f32::MIN, f32::MIN_POSITIVE, f32::from_bits(1), f32::from_bits(3), f32::from_bits(0x007fffff), f32::from_bits(0x00800001), f32::from_bits(0x7f7fffff)];
for _ in 0..4000 {
xs.push(f32::from_bits(next(&mut s) as u32));
}
for x in xs {
let want = x / c;
let got = go(x);
assert!(got.to_bits() == want.to_bits() || (got.is_nan() && want.is_nan()), "f32 {x:e} / {c:e}: {got:e} vs {want:e}");
}
}
}
#[test]
fn f64_power_of_two_divisors() {
let cases: [(f64, bool); 16] = [
(2.0, true),
(-2.0, true),
(0.5, true),
(8.0, true),
(f64::from_bits(0x7fe0000000000000), true), // 2^1023 -> 2^-1023 (subnormal)
(f64::from_bits(0x0010000000000000), true), // 2^-1022
(f64::from_bits(0x0008000000000000), true), // 2^-1023 (subnormal): reciprocal 2^1023
(f64::from_bits(0x0004000000000000), false), // 2^-1024: reciprocal overflows
(f64::from_bits(1), false),
(f64::from_bits(0x8000000000000001), false),
(3.0, false),
(0.1, false),
(0.0, false),
(f64::INFINITY, false),
(f64::NAN, false),
(f64::from_bits(0x000c000000000000), false),
];
let mut s = 0x2545f4914f6cdd1du64;
for (c, folds) in cases {
let (f, divides) = build(true, c.to_bits());
assert_eq!(!divides, folds, "f64 {c:e}");
let code = run(&f);
let go: extern "sysv64" fn(f64) -> f64 = unsafe { std::mem::transmute(code.ptr()) };
let mut xs: Vec<f64> = vec![0.0, -0.0, 1.0, -1.0, 3.0, f64::INFINITY, f64::NEG_INFINITY, f64::MAX, f64::MIN, f64::MIN_POSITIVE, f64::from_bits(1), f64::from_bits(3), f64::from_bits(0x000fffffffffffff), f64::from_bits(0x0010000000000001)];
for _ in 0..4000 {
xs.push(f64::from_bits(next(&mut s)));
}
for x in xs {
let want = x / c;
let got = go(x);
assert!(got.to_bits() == want.to_bits() || (got.is_nan() && want.is_nan()), "f64 {x:e} / {c:e}: {got:e} vs {want:e}");
}
}
}