|  | #![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(f128_enabled)] | 
|  | #[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(f128_enabled)] | 
|  | #[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"); | 
|  | } | 
|  | } |