rust/library/compiler-builtins/builtins-test/tests/conv.rs
Trevor Gross 92b1e8454d Rename testcrate to builtins-test
The repo will soon have `libm` as a top-level crate, so make it clear
that this is only the test crate for `compiler-builtins`.
2025-04-18 21:14:41 -04:00

365 lines
14 KiB
Rust

#![cfg_attr(f128_enabled, feature(f128))]
#![cfg_attr(f16_enabled, feature(f16))]
// makes configuration easier
#![allow(unused_macros)]
#![allow(unused_imports)]
use builtins_test::*;
use compiler_builtins::float::Float;
use rustc_apfloat::{Float as _, FloatConvert as _};
mod i_to_f {
use super::*;
macro_rules! i_to_f {
($f_ty:ty, $apfloat_ty:ident, $sys_available:meta, $($i_ty:ty, $fn:ident);*;) => {
$(
#[test]
fn $fn() {
use compiler_builtins::float::conv::$fn;
use compiler_builtins::int::Int;
fuzz(N, |x: $i_ty| {
let f0 = apfloat_fallback!(
$f_ty, $apfloat_ty, $sys_available,
|x| x as $f_ty;
// When the builtin is not available, we need to use a different conversion
// method (since apfloat doesn't support `as` casting).
|x: $i_ty| {
use compiler_builtins::int::MinInt;
let apf = if <$i_ty>::SIGNED {
FloatTy::from_i128(x.try_into().unwrap()).value
} else {
FloatTy::from_u128(x.try_into().unwrap()).value
};
<$f_ty>::from_bits(apf.to_bits())
},
x
);
let f1: $f_ty = $fn(x);
#[cfg($sys_available)] {
// 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 = <$f_ty>::from_bits(f1.to_bits().wrapping_sub(1)) as $i_ty;
let y = f1 as $i_ty;
let y_plus_ulp = <$f_ty>::from_bits(f1.to_bits().wrapping_add(1)) as $i_ty;
let error_minus = <$i_ty as Int>::abs_diff(y_minus_ulp, x);
let error = <$i_ty as Int>::abs_diff(y, x);
let error_plus = <$i_ty 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 !Float::eq_repr(f0, f1) && !cfg!(any(
target_arch = "x86",
target_arch = "powerpc",
target_arch = "powerpc64"
)) {
panic!(
"{}({}): std: {:?}, builtins: {:?}",
stringify!($fn),
x,
f0,
f1,
);
}
});
}
)*
};
}
i_to_f! { f32, Single, all(),
u32, __floatunsisf;
i32, __floatsisf;
u64, __floatundisf;
i64, __floatdisf;
u128, __floatuntisf;
i128, __floattisf;
}
i_to_f! { f64, Double, all(),
u32, __floatunsidf;
i32, __floatsidf;
u64, __floatundidf;
i64, __floatdidf;
u128, __floatuntidf;
i128, __floattidf;
}
#[cfg(not(feature = "no-f16-f128"))]
#[cfg(not(any(target_arch = "powerpc", target_arch = "powerpc64")))]
i_to_f! { f128, Quad, not(feature = "no-sys-f128-int-convert"),
u32, __floatunsitf;
i32, __floatsitf;
u64, __floatunditf;
i64, __floatditf;
u128, __floatuntitf;
i128, __floattitf;
}
#[cfg(not(feature = "no-f16-f128"))]
#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))]
i_to_f! { f128, Quad, not(feature = "no-sys-f128-int-convert"),
u32, __floatunsikf;
i32, __floatsikf;
u64, __floatundikf;
i64, __floatdikf;
u128, __floatuntikf;
i128, __floattikf;
}
}
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(f128_enabled)]
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(all(f16_enabled, f128_enabled))]
#[cfg(not(any(
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "loongarch64"
)))]
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 => f64, Half => Double, __extendhfdf2, not(feature = "no-sys-f16-f64-convert");
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(f128_enabled)]
#[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(all(f16_enabled, f128_enabled))]
#[cfg(not(any(
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "loongarch64"
)))]
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");
f64 => f16, Double => Half, __truncdfhf2, not(feature = "no-sys-f16-f64-convert");
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(f128_enabled)]
#[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");
}
}