Refactor float test macros to have a fallback

Change float test macros to fall back to testing against `rustc_apfloat`
when system implementations are not available, rather than just skipping
tests.

This allows for easier debugging where operations may not be supported.
This commit is contained in:
Trevor Gross 2024-05-10 19:01:49 -05:00
parent 23b91f2b58
commit 1be6626307
5 changed files with 127 additions and 36 deletions

View file

@ -263,3 +263,55 @@ pub fn fuzz_float_2<F: Float, E: Fn(F, F)>(n: u32, f: E) {
f(x, y)
}
}
/// Perform an operation using builtin types if available, falling back to apfloat if not.
#[macro_export]
macro_rules! apfloat_fallback {
(
$float_ty:ty,
// Type name in `rustc_apfloat::ieee`. Not a full path, it automatically gets the prefix.
$apfloat_ty:ident,
// Cfg expression for when builtin system operations should be used
$sys_available:meta,
// The expression to run. This expression may use `FloatTy` for its signature.
// Optionally, the final conversion back to a float can be suppressed using
// `=> no_convert` (for e.g. operations that return a bool).
$op:expr $(=> $convert:ident)?,
// Arguments that get passed to `$op` after converting to a float
$($arg:expr),+
$(,)?
) => {{
#[cfg($sys_available)]
let ret = {
type FloatTy = $float_ty;
$op( $($arg),+ )
};
#[cfg(not($sys_available))]
let ret = {
use rustc_apfloat::Float;
type FloatTy = rustc_apfloat::ieee::$apfloat_ty;
let op_res = $op( $(FloatTy::from_bits($arg.to_bits().into())),+ );
apfloat_fallback!(@convert $float_ty, op_res $(,$convert)?)
};
ret
}};
// Operations that do not need converting back to a float
(@convert $float_ty:ty, $val:expr, no_convert) => {
$val
};
// Some apfloat operations return a `StatusAnd` that we need to extract the value from. This
// is the default.
(@convert $float_ty:ty, $val:expr) => {{
// ignore the status, just get the value
let unwrapped = $val.value;
<$float_ty>::from_bits(FloatTy::to_bits(unwrapped).try_into().unwrap())
}};
}

View file

@ -1,5 +1,6 @@
#![allow(unused_macros)]
use core::ops::{Add, Sub};
use testcrate::*;
macro_rules! sum {
@ -71,28 +72,28 @@ fn addsub() {
}
macro_rules! float_sum {
($($f:ty, $fn_add:ident, $fn_sub:ident);*;) => {
($($f:ty, $fn_add:ident, $fn_sub:ident, $apfloat_ty:ident, $sys_available:meta);*;) => {
$(
fuzz_float_2(N, |x: $f, y: $f| {
let add0 = x + y;
let sub0 = x - y;
let add0 = apfloat_fallback!($f, $apfloat_ty, $sys_available, Add::add, x, y);
let sub0 = apfloat_fallback!($f, $apfloat_ty, $sys_available, Sub::sub, x, y);
let add1: $f = $fn_add(x, y);
let sub1: $f = $fn_sub(x, y);
if !Float::eq_repr(add0, add1) {
panic!(
"{}({}, {}): std: {}, builtins: {}",
"{}({:?}, {:?}): std: {:?}, builtins: {:?}",
stringify!($fn_add), x, y, add0, add1
);
}
if !Float::eq_repr(sub0, sub1) {
panic!(
"{}({}, {}): std: {}, builtins: {}",
"{}({:?}, {:?}): std: {:?}, builtins: {:?}",
stringify!($fn_sub), x, y, sub0, sub1
);
}
});
)*
};
}
}
#[cfg(not(all(target_arch = "x86", not(target_feature = "sse"))))]
@ -105,8 +106,8 @@ fn float_addsub() {
};
float_sum!(
f32, __addsf3, __subsf3;
f64, __adddf3, __subdf3;
f32, __addsf3, __subsf3, Single, all();
f64, __adddf3, __subdf3, Double, all();
);
}
@ -120,7 +121,7 @@ fn float_addsub_arm() {
};
float_sum!(
f32, __addsf3vfp, __subsf3vfp;
f64, __adddf3vfp, __subdf3vfp;
f32, __addsf3vfp, __subsf3vfp, Single, all();
f64, __adddf3vfp, __subdf3vfp, Double, all();
);
}

View file

@ -1,23 +1,48 @@
#![allow(unused_macros)]
#![allow(unreachable_code)]
#[cfg(not(target_arch = "powerpc64"))]
use testcrate::*;
macro_rules! cmp {
($x:ident, $y:ident, $($unordered_val:expr, $fn:ident);*;) => {
(
$f:ty, $x:ident, $y:ident, $apfloat_ty:ident, $sys_available:meta,
$($unordered_val:expr, $fn:ident);*;
) => {
$(
let cmp0 = if $x.is_nan() || $y.is_nan() {
let cmp0 = if apfloat_fallback!(
$f, $apfloat_ty, $sys_available,
|x: FloatTy| x.is_nan() => no_convert,
$x
) || apfloat_fallback!(
$f, $apfloat_ty, $sys_available,
|y: FloatTy| y.is_nan() => no_convert,
$y
)
{
$unordered_val
} else if $x < $y {
} else if apfloat_fallback!(
$f, $apfloat_ty, $sys_available,
|x, y| x < y => no_convert,
$x, $y
) {
-1
} else if $x == $y {
} else if apfloat_fallback!(
$f, $apfloat_ty, $sys_available,
|x, y| x == y => no_convert,
$x, $y
) {
0
} else {
1
};
let cmp1 = $fn($x, $y);
if cmp0 != cmp1 {
panic!("{}({}, {}): std: {}, builtins: {}", stringify!($fn_builtins), $x, $y, cmp0, cmp1);
panic!(
"{}({:?}, {:?}): std: {:?}, builtins: {:?}",
stringify!($fn), $x, $y, cmp0, cmp1
);
}
)*
};
@ -34,7 +59,7 @@ fn float_comparisons() {
fuzz_float_2(N, |x: f32, y: f32| {
assert_eq!(__unordsf2(x, y) != 0, x.is_nan() || y.is_nan());
cmp!(x, y,
cmp!(f32, x, y, Single, all(),
1, __ltsf2;
1, __lesf2;
1, __eqsf2;
@ -45,7 +70,7 @@ fn float_comparisons() {
});
fuzz_float_2(N, |x: f64, y: f64| {
assert_eq!(__unorddf2(x, y) != 0, x.is_nan() || y.is_nan());
cmp!(x, y,
cmp!(f64, x, y, Double, all(),
1, __ltdf2;
1, __ledf2;
1, __eqdf2;

View file

@ -2,6 +2,7 @@
use compiler_builtins::int::sdiv::{__divmoddi4, __divmodsi4, __divmodti4};
use compiler_builtins::int::udiv::{__udivmoddi4, __udivmodsi4, __udivmodti4, u128_divide_sparc};
use testcrate::*;
// Division algorithms have by far the nastiest and largest number of edge cases, and experience shows
@ -104,16 +105,20 @@ fn divide_sparc() {
}
macro_rules! float {
($($i:ty, $fn:ident);*;) => {
($($f:ty, $fn:ident, $apfloat_ty:ident, $sys_available:meta);*;) => {
$(
fuzz_float_2(N, |x: $i, y: $i| {
let quo0 = x / y;
let quo1: $i = $fn(x, y);
fuzz_float_2(N, |x: $f, y: $f| {
let quo0: $f = apfloat_fallback!($f, $apfloat_ty, $sys_available, Div::div, x, y);
let quo1: $f = $fn(x, y);
#[cfg(not(target_arch = "arm"))]
if !Float::eq_repr(quo0, quo1) {
panic!(
"{}({}, {}): std: {}, builtins: {}",
stringify!($fn), x, y, quo0, quo1
"{}({:?}, {:?}): std: {:?}, builtins: {:?}",
stringify!($fn),
x,
y,
quo0,
quo1
);
}
@ -122,8 +127,12 @@ macro_rules! float {
if !(Float::is_subnormal(quo0) || Float::is_subnormal(quo1)) {
if !Float::eq_repr(quo0, quo1) {
panic!(
"{}({}, {}): std: {}, builtins: {}",
stringify!($fn), x, y, quo0, quo1
"{}({:?}, {:?}): std: {:?}, builtins: {:?}",
stringify!($fn),
x,
y,
quo0,
quo1
);
}
}
@ -139,10 +148,11 @@ fn float_div() {
div::{__divdf3, __divsf3},
Float,
};
use core::ops::Div;
float!(
f32, __divsf3;
f64, __divdf3;
f32, __divsf3, Single, all();
f64, __divdf3, Double, all();
);
}
@ -153,9 +163,10 @@ fn float_div_arm() {
div::{__divdf3vfp, __divsf3vfp},
Float,
};
use core::ops::Div;
float!(
f32, __divsf3vfp;
f64, __divdf3vfp;
f32, __divsf3vfp, Single, all();
f64, __divdf3vfp, Double, all();
);
}

View file

@ -82,16 +82,16 @@ fn overflowing_mul() {
}
macro_rules! float_mul {
($($f:ty, $fn:ident);*;) => {
($($f:ty, $fn:ident, $apfloat_ty:ident, $sys_available:meta);*;) => {
$(
fuzz_float_2(N, |x: $f, y: $f| {
let mul0 = x * y;
let mul0 = apfloat_fallback!($f, $apfloat_ty, $sys_available, Mul::mul, x, y);
let mul1: $f = $fn(x, y);
// multiplication of subnormals is not currently handled
if !(Float::is_subnormal(mul0) || Float::is_subnormal(mul1)) {
if !Float::eq_repr(mul0, mul1) {
panic!(
"{}({}, {}): std: {}, builtins: {}",
"{}({:?}, {:?}): std: {:?}, builtins: {:?}",
stringify!($fn), x, y, mul0, mul1
);
}
@ -108,10 +108,11 @@ fn float_mul() {
mul::{__muldf3, __mulsf3},
Float,
};
use core::ops::Mul;
float_mul!(
f32, __mulsf3;
f64, __muldf3;
f32, __mulsf3, Single, all();
f64, __muldf3, Double, all();
);
}
@ -122,9 +123,10 @@ fn float_mul_arm() {
mul::{__muldf3vfp, __mulsf3vfp},
Float,
};
use core::ops::Mul;
float_mul!(
f32, __mulsf3vfp;
f64, __muldf3vfp;
f32, __mulsf3vfp, Single, all();
f64, __muldf3vfp, Double, all();
);
}