Skip to content

Commit

Permalink
Auto merge of rust-lang#126608 - tgross35:f16-f128-library, r=Mark-Si…
Browse files Browse the repository at this point in the history
…mulacrum

Add more constants, functions, and tests for `f16` and `f128`

This adds everything that was in some way blocked on const eval, since rust-lang#126429 landed. There is a lot of `cfg(bootstrap)` since that is a fairly recent change.

`f128` tests are disabled on everything except x86_64 and Linux aarch64, which are two platforms I know have "good" support for these types - meaning basic math symbols are available and LLVM doesn't hit selection crashes. `f16` tests are enabled on almost everything except for known LLVM crashes. Doctests are only enabled on x86_64.

Tracking issue: rust-lang#116909
  • Loading branch information
bors committed Jun 26, 2024
2 parents c14a130 + 19cfdb2 commit a8b311e
Show file tree
Hide file tree
Showing 12 changed files with 2,638 additions and 148 deletions.
674 changes: 672 additions & 2 deletions core/src/num/f128.rs

Large diffs are not rendered by default.

646 changes: 644 additions & 2 deletions core/src/num/f16.rs

Large diffs are not rendered by default.

55 changes: 30 additions & 25 deletions core/src/num/f32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,21 @@ impl f32 {
#[stable(feature = "assoc_int_consts", since = "1.43.0")]
pub const NEG_INFINITY: f32 = -1.0_f32 / 0.0_f32;

/// Sign bit
const SIGN_MASK: u32 = 0x8000_0000;

/// Exponent mask
const EXP_MASK: u32 = 0x7f80_0000;

/// Mantissa mask
const MAN_MASK: u32 = 0x007f_ffff;

/// Minimum representable positive value (min subnormal)
const TINY_BITS: u32 = 0x1;

/// Minimum representable negative value (min negative subnormal)
const NEG_TINY_BITS: u32 = Self::TINY_BITS | Self::SIGN_MASK;

/// Returns `true` if this value is NaN.
///
/// ```
Expand All @@ -515,7 +530,7 @@ impl f32 {
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
pub(crate) const fn abs_private(self) -> f32 {
// SAFETY: This transmutation is fine. Probably. For the reasons std is using it.
unsafe { mem::transmute::<u32, f32>(mem::transmute::<f32, u32>(self) & 0x7fff_ffff) }
unsafe { mem::transmute::<u32, f32>(mem::transmute::<f32, u32>(self) & !Self::SIGN_MASK) }
}

/// Returns `true` if this value is positive infinity or negative infinity, and
Expand Down Expand Up @@ -682,12 +697,9 @@ impl f32 {
// runtime-deviating logic which may or may not be acceptable.
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
const unsafe fn partial_classify(self) -> FpCategory {
const EXP_MASK: u32 = 0x7f800000;
const MAN_MASK: u32 = 0x007fffff;

// SAFETY: The caller is not asking questions for which this will tell lies.
let b = unsafe { mem::transmute::<f32, u32>(self) };
match (b & MAN_MASK, b & EXP_MASK) {
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
(0, 0) => FpCategory::Zero,
(_, 0) => FpCategory::Subnormal,
_ => FpCategory::Normal,
Expand All @@ -699,12 +711,9 @@ impl f32 {
// plus a transmute. We do not live in a just world, but we can make it more so.
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
const fn classify_bits(b: u32) -> FpCategory {
const EXP_MASK: u32 = 0x7f800000;
const MAN_MASK: u32 = 0x007fffff;

match (b & MAN_MASK, b & EXP_MASK) {
(0, EXP_MASK) => FpCategory::Infinite,
(_, EXP_MASK) => FpCategory::Nan,
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
(0, Self::EXP_MASK) => FpCategory::Infinite,
(_, Self::EXP_MASK) => FpCategory::Nan,
(0, 0) => FpCategory::Zero,
(_, 0) => FpCategory::Subnormal,
_ => FpCategory::Normal,
Expand Down Expand Up @@ -787,19 +796,17 @@ impl f32 {
#[unstable(feature = "float_next_up_down", issue = "91399")]
#[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")]
pub const fn next_up(self) -> Self {
// We must use strictly integer arithmetic to prevent denormals from
// flushing to zero after an arithmetic operation on some platforms.
const TINY_BITS: u32 = 0x1; // Smallest positive f32.
const CLEAR_SIGN_MASK: u32 = 0x7fff_ffff;

// Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
// denormals to zero. This is in general unsound and unsupported, but here
// we do our best to still produce the correct result on such targets.
let bits = self.to_bits();
if self.is_nan() || bits == Self::INFINITY.to_bits() {
return self;
}

let abs = bits & CLEAR_SIGN_MASK;
let abs = bits & !Self::SIGN_MASK;
let next_bits = if abs == 0 {
TINY_BITS
Self::TINY_BITS
} else if bits == abs {
bits + 1
} else {
Expand Down Expand Up @@ -837,19 +844,17 @@ impl f32 {
#[unstable(feature = "float_next_up_down", issue = "91399")]
#[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")]
pub const fn next_down(self) -> Self {
// We must use strictly integer arithmetic to prevent denormals from
// flushing to zero after an arithmetic operation on some platforms.
const NEG_TINY_BITS: u32 = 0x8000_0001; // Smallest (in magnitude) negative f32.
const CLEAR_SIGN_MASK: u32 = 0x7fff_ffff;

// Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
// denormals to zero. This is in general unsound and unsupported, but here
// we do our best to still produce the correct result on such targets.
let bits = self.to_bits();
if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() {
return self;
}

let abs = bits & CLEAR_SIGN_MASK;
let abs = bits & !Self::SIGN_MASK;
let next_bits = if abs == 0 {
NEG_TINY_BITS
Self::NEG_TINY_BITS
} else if bits == abs {
bits - 1
} else {
Expand Down
61 changes: 32 additions & 29 deletions core/src/num/f64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,21 @@ impl f64 {
#[stable(feature = "assoc_int_consts", since = "1.43.0")]
pub const NEG_INFINITY: f64 = -1.0_f64 / 0.0_f64;

/// Sign bit
const SIGN_MASK: u64 = 0x8000_0000_0000_0000;

/// Exponent mask
const EXP_MASK: u64 = 0x7ff0_0000_0000_0000;

/// Mantissa mask
const MAN_MASK: u64 = 0x000f_ffff_ffff_ffff;

/// Minimum representable positive value (min subnormal)
const TINY_BITS: u64 = 0x1;

/// Minimum representable negative value (min negative subnormal)
const NEG_TINY_BITS: u64 = Self::TINY_BITS | Self::SIGN_MASK;

/// Returns `true` if this value is NaN.
///
/// ```
Expand All @@ -514,9 +529,7 @@ impl f64 {
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
pub(crate) const fn abs_private(self) -> f64 {
// SAFETY: This transmutation is fine. Probably. For the reasons std is using it.
unsafe {
mem::transmute::<u64, f64>(mem::transmute::<f64, u64>(self) & 0x7fff_ffff_ffff_ffff)
}
unsafe { mem::transmute::<u64, f64>(mem::transmute::<f64, u64>(self) & !Self::SIGN_MASK) }
}

/// Returns `true` if this value is positive infinity or negative infinity, and
Expand Down Expand Up @@ -673,13 +686,10 @@ impl f64 {
// and some normal floating point numbers truncated from an x87 FPU.
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
const unsafe fn partial_classify(self) -> FpCategory {
const EXP_MASK: u64 = 0x7ff0000000000000;
const MAN_MASK: u64 = 0x000fffffffffffff;

// SAFETY: The caller is not asking questions for which this will tell lies.
let b = unsafe { mem::transmute::<f64, u64>(self) };
match (b & MAN_MASK, b & EXP_MASK) {
(0, EXP_MASK) => FpCategory::Infinite,
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
(0, Self::EXP_MASK) => FpCategory::Infinite,
(0, 0) => FpCategory::Zero,
(_, 0) => FpCategory::Subnormal,
_ => FpCategory::Normal,
Expand All @@ -691,12 +701,9 @@ impl f64 {
// plus a transmute. We do not live in a just world, but we can make it more so.
#[rustc_const_unstable(feature = "const_float_classify", issue = "72505")]
const fn classify_bits(b: u64) -> FpCategory {
const EXP_MASK: u64 = 0x7ff0000000000000;
const MAN_MASK: u64 = 0x000fffffffffffff;

match (b & MAN_MASK, b & EXP_MASK) {
(0, EXP_MASK) => FpCategory::Infinite,
(_, EXP_MASK) => FpCategory::Nan,
match (b & Self::MAN_MASK, b & Self::EXP_MASK) {
(0, Self::EXP_MASK) => FpCategory::Infinite,
(_, Self::EXP_MASK) => FpCategory::Nan,
(0, 0) => FpCategory::Zero,
(_, 0) => FpCategory::Subnormal,
_ => FpCategory::Normal,
Expand Down Expand Up @@ -756,7 +763,7 @@ impl f64 {
// IEEE754 says: isSignMinus(x) is true if and only if x has negative sign. isSignMinus
// applies to zeros and NaNs as well.
// SAFETY: This is just transmuting to get the sign bit, it's fine.
unsafe { mem::transmute::<f64, u64>(self) & 0x8000_0000_0000_0000 != 0 }
unsafe { mem::transmute::<f64, u64>(self) & Self::SIGN_MASK != 0 }
}

#[must_use]
Expand Down Expand Up @@ -797,19 +804,17 @@ impl f64 {
#[unstable(feature = "float_next_up_down", issue = "91399")]
#[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")]
pub const fn next_up(self) -> Self {
// We must use strictly integer arithmetic to prevent denormals from
// flushing to zero after an arithmetic operation on some platforms.
const TINY_BITS: u64 = 0x1; // Smallest positive f64.
const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff;

// Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
// denormals to zero. This is in general unsound and unsupported, but here
// we do our best to still produce the correct result on such targets.
let bits = self.to_bits();
if self.is_nan() || bits == Self::INFINITY.to_bits() {
return self;
}

let abs = bits & CLEAR_SIGN_MASK;
let abs = bits & !Self::SIGN_MASK;
let next_bits = if abs == 0 {
TINY_BITS
Self::TINY_BITS
} else if bits == abs {
bits + 1
} else {
Expand Down Expand Up @@ -847,19 +852,17 @@ impl f64 {
#[unstable(feature = "float_next_up_down", issue = "91399")]
#[rustc_const_unstable(feature = "float_next_up_down", issue = "91399")]
pub const fn next_down(self) -> Self {
// We must use strictly integer arithmetic to prevent denormals from
// flushing to zero after an arithmetic operation on some platforms.
const NEG_TINY_BITS: u64 = 0x8000_0000_0000_0001; // Smallest (in magnitude) negative f64.
const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff;

// Some targets violate Rust's assumption of IEEE semantics, e.g. by flushing
// denormals to zero. This is in general unsound and unsupported, but here
// we do our best to still produce the correct result on such targets.
let bits = self.to_bits();
if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() {
return self;
}

let abs = bits & CLEAR_SIGN_MASK;
let abs = bits & !Self::SIGN_MASK;
let next_bits = if abs == 0 {
NEG_TINY_BITS
Self::NEG_TINY_BITS
} else if bits == abs {
bits - 1
} else {
Expand Down
62 changes: 62 additions & 0 deletions std/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ fn main() {
let target_vendor =
env::var("CARGO_CFG_TARGET_VENDOR").expect("CARGO_CFG_TARGET_VENDOR was not set");
let target_env = env::var("CARGO_CFG_TARGET_ENV").expect("CARGO_CFG_TARGET_ENV was not set");
let target_pointer_width: u32 = env::var("CARGO_CFG_TARGET_POINTER_WIDTH")
.expect("CARGO_CFG_TARGET_POINTER_WIDTH was not set")
.parse()
.unwrap();

println!("cargo:rustc-check-cfg=cfg(netbsd10)");
if target_os == "netbsd" && env::var("RUSTC_STD_NETBSD10").is_ok() {
Expand Down Expand Up @@ -70,4 +74,62 @@ fn main() {
println!("cargo:rustc-cfg=backtrace_in_libstd");

println!("cargo:rustc-env=STD_ENV_ARCH={}", env::var("CARGO_CFG_TARGET_ARCH").unwrap());

// Emit these on platforms that have no known ABI bugs, LLVM selection bugs, lowering bugs,
// missing symbols, or other problems, to determine when tests get run.
// If more broken platforms are found, please update the tracking issue at
// <https://github.com/rust-lang/rust/issues/116909>
//
// Some of these match arms are redundant; the goal is to separate reasons that the type is
// unreliable, even when multiple reasons might fail the same platform.
println!("cargo:rustc-check-cfg=cfg(reliable_f16)");
println!("cargo:rustc-check-cfg=cfg(reliable_f128)");

let has_reliable_f16 = match (target_arch.as_str(), target_os.as_str()) {
// Selection failure until recent LLVM <https://github.com/llvm/llvm-project/issues/93894>
// FIXME(llvm19): can probably be removed at the version bump
("loongarch64", _) => false,
// Selection failure <https://github.com/llvm/llvm-project/issues/50374>
("s390x", _) => false,
// Unsupported <https://github.com/llvm/llvm-project/issues/94434>
("arm64ec", _) => false,
// MinGW ABI bugs <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115054>
("x86", "windows") => false,
// x86 has ABI bugs that show up with optimizations. This should be partially fixed with
// the compiler-builtins update. <https://github.com/rust-lang/rust/issues/123885>
("x86" | "x86_64", _) => false,
// Missing `__gnu_h2f_ieee` and `__gnu_f2h_ieee`
("powerpc" | "powerpc64" | "powerpc64le", _) => false,
// Missing `__extendhfsf` and `__truncsfhf`
("riscv32" | "riscv64", _) => false,
// Most OSs are missing `__extendhfsf` and `__truncsfhf`
(_, "linux" | "macos") => true,
// Almost all OSs besides Linux and MacOS are missing symbols until compiler-builtins can
// be updated. <https://github.com/rust-lang/rust/pull/125016> will get some of these, the
// next CB update should get the rest.
_ => false,
};

let has_reliable_f128 = match (target_arch.as_str(), target_os.as_str()) {
// Unsupported <https://github.com/llvm/llvm-project/issues/94434>
("arm64ec", _) => false,
// ABI and precision bugs <https://github.com/rust-lang/rust/issues/125109>
// <https://github.com/rust-lang/rust/issues/125102>
("powerpc" | "powerpc64", _) => false,
// Selection bug <https://github.com/llvm/llvm-project/issues/95471>
("nvptx64", _) => false,
// ABI unsupported <https://github.com/llvm/llvm-project/issues/41838>
("sparc", _) => false,
// 64-bit Linux is about the only platform to have f128 symbols by default
(_, "linux") if target_pointer_width == 64 => true,
// Same as for f16, except MacOS is also missing f128 symbols.
_ => false,
};

if has_reliable_f16 {
println!("cargo:rustc-cfg=reliable_f16");
}
if has_reliable_f128 {
println!("cargo:rustc-cfg=reliable_f128");
}
}
30 changes: 30 additions & 0 deletions std/src/f128.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,34 @@ impl f128 {
pub fn powi(self, n: i32) -> f128 {
unsafe { intrinsics::powif128(self, n) }
}

/// Computes the absolute value of `self`.
///
/// This function always returns the precise result.
///
/// # Examples
///
/// ```
/// #![feature(f128)]
/// # #[cfg(reliable_f128)] { // FIXME(f16_f128): reliable_f128
///
/// let x = 3.5_f128;
/// let y = -3.5_f128;
///
/// assert_eq!(x.abs(), x);
/// assert_eq!(y.abs(), -y);
///
/// assert!(f128::NAN.abs().is_nan());
/// # }
/// ```
#[inline]
#[cfg(not(bootstrap))]
#[rustc_allow_incoherent_impl]
#[unstable(feature = "f128", issue = "116909")]
#[must_use = "method returns a new number and does not mutate the original value"]
pub fn abs(self) -> Self {
// FIXME(f16_f128): replace with `intrinsics::fabsf128` when available
// We don't do this now because LLVM has lowering bugs for f128 math.
Self::from_bits(self.to_bits() & !(1 << 127))
}
}
Loading

0 comments on commit a8b311e

Please sign in to comment.