Trevor Gross e0e6243b21 Update float conversion tests
Since there are more platforms that do not have symbols present, we need
to use `rustc_apfloat` for more conversion tests. Make use of the
fallback like other tests, and refactor so each test gets its own
function.

Previously we were testing both apfloat and system conversion methods
when possible. This changes to only test one or the other, depending on
whether or not the system version is available. This seems reasonable
because it is consistent with all other tests, but we should consider
updating all tests to check both at some point.

This also includes an adjustment of PowerPC configuration to account for
the linking errors at [1].

[1]: https://github.com/rust-lang/compiler-builtins/issues/655
2024-08-03 02:07:06 -04:00

322 lines
12 KiB
Rust

#![cfg_attr(not(feature = "no-f16-f128"), feature(f16))]
#![cfg_attr(not(feature = "no-f16-f128"), feature(f128))]
// makes configuration easier
#![allow(unused_macros)]
#![allow(unused_imports)]
use compiler_builtins::float::Float;
use rustc_apfloat::{Float as _, FloatConvert as _};
use testcrate::*;
mod int_to_float {
use super::*;
macro_rules! i_to_f {
($($from:ty, $into:ty, $fn:ident);*;) => {
$(
#[test]
fn $fn() {
use compiler_builtins::float::conv::$fn;
use compiler_builtins::int::Int;
fuzz(N, |x: $from| {
let f0 = x as $into;
let f1: $into = $fn(x);
// This makes sure that the conversion produced the best rounding possible, and does
// this independent of `x as $into` rounding correctly.
// This assumes that float to integer conversion is correct.
let y_minus_ulp = <$into>::from_bits(f1.to_bits().wrapping_sub(1)) as $from;
let y = f1 as $from;
let y_plus_ulp = <$into>::from_bits(f1.to_bits().wrapping_add(1)) as $from;
let error_minus = <$from as Int>::abs_diff(y_minus_ulp, x);
let error = <$from as Int>::abs_diff(y, x);
let error_plus = <$from as Int>::abs_diff(y_plus_ulp, x);
// The first two conditions check that none of the two closest float values are
// strictly closer in representation to `x`. The second makes sure that rounding is
// towards even significand if two float values are equally close to the integer.
if error_minus < error
|| error_plus < error
|| ((error_minus == error || error_plus == error)
&& ((f0.to_bits() & 1) != 0))
{
if !cfg!(any(
target_arch = "powerpc",
target_arch = "powerpc64"
)) {
panic!(
"incorrect rounding by {}({}): {}, ({}, {}, {}), errors ({}, {}, {})",
stringify!($fn),
x,
f1.to_bits(),
y_minus_ulp,
y,
y_plus_ulp,
error_minus,
error,
error_plus,
);
}
}
// Test against native conversion. We disable testing on all `x86` because of
// rounding bugs with `i686`. `powerpc` also has the same rounding bug.
if f0 != f1 && !cfg!(any(
target_arch = "x86",
target_arch = "powerpc",
target_arch = "powerpc64"
)) {
panic!(
"{}({}): std: {}, builtins: {}",
stringify!($fn),
x,
f0,
f1,
);
}
});
}
)*
};
}
i_to_f! {
u32, f32, __floatunsisf;
u32, f64, __floatunsidf;
i32, f32, __floatsisf;
i32, f64, __floatsidf;
u64, f32, __floatundisf;
u64, f64, __floatundidf;
i64, f32, __floatdisf;
i64, f64, __floatdidf;
u128, f32, __floatuntisf;
u128, f64, __floatuntidf;
i128, f32, __floattisf;
i128, f64, __floattidf;
}
}
// PowerPC tests are failing on LLVM 13: https://github.com/rust-lang/rust/issues/88520
#[cfg(not(target_arch = "powerpc64"))]
mod f_to_i {
use super::*;
macro_rules! f_to_i {
($x:ident, $f_ty:ty, $apfloat_ty:ident, $sys_available:meta, $($i_ty:ty, $fn:ident);*;) => {
$(
// it is undefined behavior in the first place to do conversions with NaNs
if !apfloat_fallback!(
$f_ty, $apfloat_ty, $sys_available, |x: FloatTy| x.is_nan() => no_convert, $x
) {
let conv0 = apfloat_fallback!(
$f_ty, $apfloat_ty, $sys_available,
// Use an `as` cast when the builtin is available on the system.
|x| x as $i_ty;
// When the builtin is not available, we need to use a different conversion
// method (since apfloat doesn't support `as` casting).
|x: $f_ty| {
use compiler_builtins::int::MinInt;
let apf = FloatTy::from_bits(x.to_bits().into());
let bits: usize = <$i_ty>::BITS.try_into().unwrap();
let err_fn = || panic!(
"Unable to convert value {x:?} to type {}:", stringify!($i_ty)
);
if <$i_ty>::SIGNED {
<$i_ty>::try_from(apf.to_i128(bits).value).ok().unwrap_or_else(err_fn)
} else {
<$i_ty>::try_from(apf.to_u128(bits).value).ok().unwrap_or_else(err_fn)
}
},
$x
);
let conv1: $i_ty = $fn($x);
if conv0 != conv1 {
panic!("{}({:?}): std: {:?}, builtins: {:?}", stringify!($fn), $x, conv0, conv1);
}
}
)*
};
}
#[test]
fn f32_to_int() {
use compiler_builtins::float::conv::{
__fixsfdi, __fixsfsi, __fixsfti, __fixunssfdi, __fixunssfsi, __fixunssfti,
};
fuzz_float(N, |x: f32| {
f_to_i!(x, f32, Single, all(),
u32, __fixunssfsi;
u64, __fixunssfdi;
u128, __fixunssfti;
i32, __fixsfsi;
i64, __fixsfdi;
i128, __fixsfti;
);
});
}
#[test]
fn f64_to_int() {
use compiler_builtins::float::conv::{
__fixdfdi, __fixdfsi, __fixdfti, __fixunsdfdi, __fixunsdfsi, __fixunsdfti,
};
fuzz_float(N, |x: f64| {
f_to_i!(x, f64, Double, all(),
u32, __fixunsdfsi;
u64, __fixunsdfdi;
u128, __fixunsdfti;
i32, __fixdfsi;
i64, __fixdfdi;
i128, __fixdfti;
);
});
}
#[test]
#[cfg(not(feature = "no-f16-f128"))]
fn f128_to_int() {
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
use compiler_builtins::float::conv::{
__fixkfdi as __fixtfdi, __fixkfsi as __fixtfsi, __fixkfti as __fixtfti,
__fixunskfdi as __fixunstfdi, __fixunskfsi as __fixunstfsi,
__fixunskfti as __fixunstfti,
};
#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
use compiler_builtins::float::conv::{
__fixtfdi, __fixtfsi, __fixtfti, __fixunstfdi, __fixunstfsi, __fixunstfti,
};
fuzz_float(N, |x: f128| {
f_to_i!(
x,
f128,
Quad,
not(feature = "no-sys-f128-int-convert"),
u32, __fixunstfsi;
u64, __fixunstfdi;
u128, __fixunstfti;
i32, __fixtfsi;
i64, __fixtfdi;
i128, __fixtfti;
);
});
}
}
macro_rules! f_to_f {
(
$mod:ident,
$(
$from_ty:ty => $to_ty:ty,
$from_ap_ty:ident => $to_ap_ty:ident,
$fn:ident, $sys_available:meta
);+;
) => {$(
#[test]
fn $fn() {
use compiler_builtins::float::{$mod::$fn, Float};
use rustc_apfloat::ieee::{$from_ap_ty, $to_ap_ty};
fuzz_float(N, |x: $from_ty| {
let tmp0: $to_ty = apfloat_fallback!(
$from_ty,
$from_ap_ty,
$sys_available,
|x: $from_ty| x as $to_ty;
|x: $from_ty| {
let from_apf = FloatTy::from_bits(x.to_bits().into());
// Get `value` directly to ignore INVALID_OP
let to_apf: $to_ap_ty = from_apf.convert(&mut false).value;
<$to_ty>::from_bits(to_apf.to_bits().try_into().unwrap())
},
x
);
let tmp1: $to_ty = $fn(x);
if !Float::eq_repr(tmp0, tmp1) {
panic!(
"{}({:?}): std: {:?}, builtins: {:?}",
stringify!($fn),
x,
tmp0,
tmp1
);
}
})
}
)+};
}
mod extend {
use super::*;
f_to_f! {
extend,
f32 => f64, Single => Double, __extendsfdf2, all();
}
#[cfg(target_arch = "arm")]
f_to_f! {
extend,
f32 => f64, Single => Double, __extendsfdf2vfp, all();
}
#[cfg(not(feature = "no-f16-f128"))]
#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
f_to_f! {
extend,
f16 => f32, Half => Single, __extendhfsf2, not(feature = "no-sys-f16");
f16 => f32, Half => Single, __gnu_h2f_ieee, not(feature = "no-sys-f16");
f16 => f128, Half => Quad, __extendhftf2, not(feature = "no-sys-f16-f128-convert");
f32 => f128, Single => Quad, __extendsftf2, not(feature = "no-sys-f128");
f64 => f128, Double => Quad, __extenddftf2, not(feature = "no-sys-f128");
}
#[cfg(not(feature = "no-f16-f128"))]
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
f_to_f! {
extend,
// FIXME(#655): `f16` tests disabled until we can bootstrap symbols
f32 => f128, Single => Quad, __extendsfkf2, not(feature = "no-sys-f128");
f64 => f128, Double => Quad, __extenddfkf2, not(feature = "no-sys-f128");
}
}
mod trunc {
use super::*;
f_to_f! {
trunc,
f64 => f32, Double => Single, __truncdfsf2, all();
}
#[cfg(target_arch = "arm")]
f_to_f! {
trunc,
f64 => f32, Double => Single, __truncdfsf2vfp, all();
}
#[cfg(not(feature = "no-f16-f128"))]
#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
f_to_f! {
trunc,
f32 => f16, Single => Half, __truncsfhf2, not(feature = "no-sys-f16");
f32 => f16, Single => Half, __gnu_f2h_ieee, not(feature = "no-sys-f16");
f128 => f16, Quad => Half, __trunctfhf2, not(feature = "no-sys-f16-f128-convert");
f128 => f32, Quad => Single, __trunctfsf2, not(feature = "no-sys-f128");
f128 => f64, Quad => Double, __trunctfdf2, not(feature = "no-sys-f128");
}
#[cfg(not(feature = "no-f16-f128"))]
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
f_to_f! {
trunc,
// FIXME(#655): `f16` tests disabled until we can bootstrap symbols
f128 => f32, Quad => Single, __trunckfsf2, not(feature = "no-sys-f128");
f128 => f64, Quad => Double, __trunckfdf2, not(feature = "no-sys-f128");
}
}