diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 287b790ea6d67..269ec9d5bc62e 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -973,6 +973,7 @@ symbols! { fields, file, final_associated_functions, + float_mul_add_relaxed, float_to_int_unchecked, floorf16, floorf32, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index c5da5055e16d9..db74834596852 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -1475,6 +1475,9 @@ pub const fn fmaf128(a: f128, b: f128, c: f128) -> f128; /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f16::mul_add_relaxed`](../../std/primitive.f16.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1491,6 +1494,9 @@ pub const fn fmuladdf16(a: f16, b: f16, c: f16) -> f16 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f32::mul_add_relaxed`](../../std/primitive.f32.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1507,6 +1513,9 @@ pub const fn fmuladdf32(a: f32, b: f32, c: f32) -> f32 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f64::mul_add_relaxed`](../../std/primitive.f64.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] @@ -1523,6 +1532,9 @@ pub const fn fmuladdf64(a: f64, b: f64, c: f64) -> f64 { /// and add instructions. It is unspecified whether or not a fused operation /// is selected, and that may depend on optimization level and context, for /// example. +/// +/// The stabilized version of this intrinsic is +/// [`f128::mul_add_relaxed`](../../std/primitive.f128.html#method.mul_add_relaxed) #[inline] #[rustc_intrinsic] #[rustc_nounwind] diff --git a/library/core/src/num/f128.rs b/library/core/src/num/f128.rs index 4875835695e69..30dc135938e91 100644 --- a/library/core/src/num/f128.rs +++ b/library/core/src/num/f128.rs @@ -1836,6 +1836,38 @@ impl f128 { intrinsics::fmaf128(self, a, b) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`](Self::mul_add), but the intermediate + /// result may be rounded differently depending on the implementation. + /// The operation is either executed as a single fused multiply-add + /// instruction, or as separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Examples + /// + /// ``` + /// #![feature(f128)] + /// #![feature(float_mul_add_relaxed)] + /// # #[cfg(any(miri, target_has_reliable_f128_math))] { // Miri uses softfloats, always works + /// + /// let result = 1.0f128.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn mul_add_relaxed(self, a: f128, b: f128) -> f128 { + intrinsics::fmuladdf128(self, a, b) + } + /// Calculates Euclidean division, the matching method for `rem_euclid`. /// /// This computes the integer `n` such that diff --git a/library/core/src/num/f16.rs b/library/core/src/num/f16.rs index 186945db9ae92..6865c41b75d6d 100644 --- a/library/core/src/num/f16.rs +++ b/library/core/src/num/f16.rs @@ -1822,6 +1822,38 @@ impl f16 { intrinsics::fmaf16(self, a, b) } + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`](Self::mul_add), but the intermediate + /// result may be rounded differently depending on the implementation. + /// The operation is either executed as a single fused multiply-add + /// instruction, or as separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Examples + /// + /// ``` + /// #![feature(f16)] + /// #![feature(float_mul_add_relaxed)] + /// # #[cfg(target_has_reliable_f16)] { + /// + /// let result = 1.0f16.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// # } + /// ``` + #[inline] + #[rustc_allow_incoherent_impl] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[must_use = "method returns a new number and does not mutate the original value"] + pub const fn mul_add_relaxed(self, a: f16, b: f16) -> f16 { + intrinsics::fmuladdf16(self, a, b) + } + /// Calculates Euclidean division, the matching method for `rem_euclid`. /// /// This computes the integer `n` such that diff --git a/library/core/src/num/f32.rs b/library/core/src/num/f32.rs index 97bf54ecde47a..2ae8129866098 100644 --- a/library/core/src/num/f32.rs +++ b/library/core/src/num/f32.rs @@ -1744,6 +1744,50 @@ impl f32 { pub const fn algebraic_rem(self, rhs: f32) -> f32 { intrinsics::frem_algebraic(self, rhs) } + + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`], but the intermediate result may be + /// rounded differently depending on the implementation. The operation is + /// either executed as a single fused multiply-add instruction, or as + /// separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Precision + /// + /// The result of this operation is not guaranteed: it is either the result + /// of [`mul_add`] (one rounding of the infinite-precision result) or of + /// `self * a + b` (two roundings, with an intermediate rounding of the + /// product). + /// + /// # Examples + /// + /// ``` + /// #![feature(float_mul_add_relaxed)] + /// + /// let result = 1.0f32.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 5.2154064e-10 is the fused result (one rounding) + /// // - 9.313226e-10 is the unfused result (two roundings) + /// let r = 0.1_f32.mul_add_relaxed(0.1_f32, -0.01_f32); + /// assert!(r == 5.2154064e-10 || r == 9.313226e-10); + /// ``` + /// + /// [`mul_add`]: ../std/primitive.f32.html#method.mul_add + #[must_use = "method returns a new number and does not mutate the original value"] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[inline] + pub const fn mul_add_relaxed(self, a: f32, b: f32) -> f32 { + intrinsics::fmuladdf32(self, a, b) + } } /// Experimental implementations of floating point functions in `core`. diff --git a/library/core/src/num/f64.rs b/library/core/src/num/f64.rs index 15ad61fe3e6e3..73dd4ce4466a3 100644 --- a/library/core/src/num/f64.rs +++ b/library/core/src/num/f64.rs @@ -1724,6 +1724,50 @@ impl f64 { pub const fn algebraic_rem(self, rhs: f64) -> f64 { intrinsics::frem_algebraic(self, rhs) } + + /// Computes `(self * a) + b` with nondeterministic rounding. + /// + /// This is similar to [`mul_add`], but the intermediate result may be + /// rounded differently depending on the implementation. The operation is + /// either executed as a single fused multiply-add instruction, or as + /// separate multiply and add instructions. + /// + /// The choice of which one is used is unspecified and non-deterministic: + /// it may vary by target, optimization level, and surrounding code, and + /// two evaluations of the same operation may even produce different + /// results. + /// + /// # Precision + /// + /// The result of this operation is not guaranteed: it is either the result + /// of [`mul_add`] (one rounding of the infinite-precision result) or of + /// `self * a + b` (two roundings, with an intermediate rounding of the + /// product). + /// + /// # Examples + /// + /// ``` + /// #![feature(float_mul_add_relaxed)] + /// + /// let result = 1.0f64.mul_add_relaxed(2.0, 3.0); + /// assert_eq!(result, 5.0); + /// + /// // When the fused and unfused operations round differently, either + /// // result may be returned: + /// // - 9.020562075079397e-19 is the fused result (one rounding) + /// // - 1.734723475976807e-18 is the unfused result (two roundings) + /// let r = 0.1_f64.mul_add_relaxed(0.1_f64, -0.01_f64); + /// assert!(r == 9.020562075079397e-19 || r == 1.734723475976807e-18); + /// ``` + /// + /// [`mul_add`]: ../std/primitive.f64.html#method.mul_add + #[must_use = "method returns a new number and does not mutate the original value"] + #[doc(alias = "fmuladd")] + #[unstable(feature = "float_mul_add_relaxed", issue = "151770")] + #[inline] + pub const fn mul_add_relaxed(self, a: f64, b: f64) -> f64 { + intrinsics::fmuladdf64(self, a, b) + } } #[unstable(feature = "core_float_math", issue = "137578")] diff --git a/tests/ui/intrinsics/float-mul-add-relaxed.rs b/tests/ui/intrinsics/float-mul-add-relaxed.rs new file mode 100644 index 0000000000000..ad222b7be2c6a --- /dev/null +++ b/tests/ui/intrinsics/float-mul-add-relaxed.rs @@ -0,0 +1,76 @@ +//@ run-pass +//@ compile-flags: -O + +// Check that `mul_add_relaxed` returns either the fused result (one rounding) +// or the unfused result (two roundings), including with optimizations enabled +// where the operation may be const-folded or lowered to a fused instruction. + +#![feature(float_mul_add_relaxed)] +#![feature(f16)] +#![feature(f128)] +#![feature(cfg_target_has_reliable_f16_f128)] +// `f16`/`f128` go unused on targets without reliable f16/f128 math, where the +// gated test functions below are compiled out. +#![allow(unused_features)] +// `target_has_reliable_*` are not "known" configs since they are unstable. +#![expect(unexpected_cfgs)] + +use std::hint::black_box; + +fn main() { + test_f32(); + test_f64(); + #[cfg(target_has_reliable_f16_math)] + test_f16(); + #[cfg(target_has_reliable_f128_math)] + test_f128(); +} + +fn test_f32() { + // Exactly representable results are the same whether or not the + // operation is fused. + assert_eq!(black_box(2.0_f32).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f32).mul_add_relaxed(1.0, 1.0), 2.0); + + // `0.1 * 0.1` is inexact, so the fused (one rounding) and unfused (two + // roundings) results differ; either is allowed. + let r = black_box(0.1_f32).mul_add_relaxed(0.1, -0.01); + assert!(r == 5.2154064e-10 || r == 9.313226e-10); + + // Edge cases behave like `a * b + c` regardless of fusion. + assert!(black_box(f32::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f32::INFINITY).mul_add_relaxed(2.0, 1.0), f32::INFINITY); + assert!(black_box(0.0_f32).mul_add_relaxed(f32::INFINITY, 1.0).is_nan()); +} + +fn test_f64() { + assert_eq!(black_box(2.0_f64).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f64).mul_add_relaxed(1.0, 1.0), 2.0); + + let r = black_box(0.1_f64).mul_add_relaxed(0.1, -0.01); + assert!(r == 9.020562075079397e-19 || r == 1.734723475976807e-18); + + assert!(black_box(f64::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f64::INFINITY).mul_add_relaxed(2.0, 1.0), f64::INFINITY); + assert!(black_box(0.0_f64).mul_add_relaxed(f64::INFINITY, 1.0).is_nan()); +} + +#[cfg(target_has_reliable_f16_math)] +fn test_f16() { + assert_eq!(black_box(2.0_f16).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f16).mul_add_relaxed(1.0, 1.0), 2.0); + + assert!(black_box(f16::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f16::INFINITY).mul_add_relaxed(2.0, 1.0), f16::INFINITY); + assert!(black_box(0.0_f16).mul_add_relaxed(f16::INFINITY, 1.0).is_nan()); +} + +#[cfg(target_has_reliable_f128_math)] +fn test_f128() { + assert_eq!(black_box(2.0_f128).mul_add_relaxed(3.0, 4.0), 10.0); + assert_eq!(black_box(1.0_f128).mul_add_relaxed(1.0, 1.0), 2.0); + + assert!(black_box(f128::NAN).mul_add_relaxed(1.0, 1.0).is_nan()); + assert_eq!(black_box(f128::INFINITY).mul_add_relaxed(2.0, 1.0), f128::INFINITY); + assert!(black_box(0.0_f128).mul_add_relaxed(f128::INFINITY, 1.0).is_nan()); +}