Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 2e4e196

Browse files
authoredApr 5, 2025
Rollup merge of #136457 - calder:master, r=tgross35
Expose algebraic floating point intrinsics # Problem A stable Rust implementation of a simple dot product is 8x slower than C++ on modern x86-64 CPUs. The root cause is an inability to let the compiler reorder floating point operations for better vectorization. See https://github.com/calder/dot-bench for benchmarks. Measurements below were performed on a i7-10875H. ### C++: 10us ✅ With Clang 18.1.3 and `-O2 -march=haswell`: <table> <tr> <th>C++</th> <th>Assembly</th> </tr> <tr> <td> <pre lang="cc"> float dot(float *a, float *b, size_t len) { #pragma clang fp reassociate(on) float sum = 0.0; for (size_t i = 0; i < len; ++i) { sum += a[i] * b[i]; } return sum; } </pre> </td> <td> <img src="https://github.com/user-attachments/assets/739573c0-380a-4d84-9fd9-141343ce7e68" /> </td> </tr> </table> ### Nightly Rust: 10us ✅ With rustc 1.86.0-nightly (8239a37) and `-C opt-level=3 -C target-feature=+avx2,+fma`: <table> <tr> <th>Rust</th> <th>Assembly</th> </tr> <tr> <td> <pre lang="rust"> fn dot(a: &[f32], b: &[f32]) -> f32 { let mut sum = 0.0; for i in 0..a.len() { sum = fadd_algebraic(sum, fmul_algebraic(a[i], b[i])); } sum } </pre> </td> <td> <img src="https://github.com/user-attachments/assets/9dcf953a-2cd7-42f3-bc34-7117de4c5fb9" /> </td> </tr> </table> ### Stable Rust: 84us ❌ With rustc 1.84.1 (e71f9a9) and `-C opt-level=3 -C target-feature=+avx2,+fma`: <table> <tr> <th>Rust</th> <th>Assembly</th> </tr> <tr> <td> <pre lang="rust"> fn dot(a: &[f32], b: &[f32]) -> f32 { let mut sum = 0.0; for i in 0..a.len() { sum += a[i] * b[i]; } sum } </pre> </td> <td> <img src="https://github.com/user-attachments/assets/936a1f7e-33e4-4ff8-a732-c3cdfe068dca" /> </td> </tr> </table> # Proposed Change Add `core::intrinsics::f*_algebraic` wrappers to `f16`, `f32`, `f64`, and `f128` gated on a new `float_algebraic` feature. # Alternatives Considered #21690 has a lot of good discussion of various options for supporting fast math in Rust, but is still open a decade later because any choice that opts in more than individual operations is ultimately contrary to Rust's design principles. In the mean time, processors have evolved and we're leaving major performance on the table by not supporting vectorization. We shouldn't make users choose between an unstable compiler and an 8x performance hit. # References * #21690 * rust-lang/libs-team#532 * #136469 * https://github.com/calder/dot-bench * https://www.felixcloutier.com/x86/vfmadd132ps:vfmadd213ps:vfmadd231ps try-job: x86_64-gnu-nopt try-job: x86_64-gnu-aux
2 parents bad13a9 + 8ff7052 commit 2e4e196

File tree

15 files changed

+544
-24
lines changed

15 files changed

+544
-24
lines changed
 

‎library/core/src/intrinsics/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,35 +2475,35 @@ pub unsafe fn float_to_int_unchecked<Float: Copy, Int: Copy>(value: Float) -> In
24752475

24762476
/// Float addition that allows optimizations based on algebraic rules.
24772477
///
2478-
/// This intrinsic does not have a stable counterpart.
2478+
/// Stabilized as [`f16::algebraic_add`], [`f32::algebraic_add`], [`f64::algebraic_add`] and [`f128::algebraic_add`].
24792479
#[rustc_nounwind]
24802480
#[rustc_intrinsic]
24812481
pub fn fadd_algebraic<T: Copy>(a: T, b: T) -> T;
24822482

24832483
/// Float subtraction that allows optimizations based on algebraic rules.
24842484
///
2485-
/// This intrinsic does not have a stable counterpart.
2485+
/// Stabilized as [`f16::algebraic_sub`], [`f32::algebraic_sub`], [`f64::algebraic_sub`] and [`f128::algebraic_sub`].
24862486
#[rustc_nounwind]
24872487
#[rustc_intrinsic]
24882488
pub fn fsub_algebraic<T: Copy>(a: T, b: T) -> T;
24892489

24902490
/// Float multiplication that allows optimizations based on algebraic rules.
24912491
///
2492-
/// This intrinsic does not have a stable counterpart.
2492+
/// Stabilized as [`f16::algebraic_mul`], [`f32::algebraic_mul`], [`f64::algebraic_mul`] and [`f128::algebraic_mul`].
24932493
#[rustc_nounwind]
24942494
#[rustc_intrinsic]
24952495
pub fn fmul_algebraic<T: Copy>(a: T, b: T) -> T;
24962496

24972497
/// Float division that allows optimizations based on algebraic rules.
24982498
///
2499-
/// This intrinsic does not have a stable counterpart.
2499+
/// Stabilized as [`f16::algebraic_div`], [`f32::algebraic_div`], [`f64::algebraic_div`] and [`f128::algebraic_div`].
25002500
#[rustc_nounwind]
25012501
#[rustc_intrinsic]
25022502
pub fn fdiv_algebraic<T: Copy>(a: T, b: T) -> T;
25032503

25042504
/// Float remainder that allows optimizations based on algebraic rules.
25052505
///
2506-
/// This intrinsic does not have a stable counterpart.
2506+
/// Stabilized as [`f16::algebraic_rem`], [`f32::algebraic_rem`], [`f64::algebraic_rem`] and [`f128::algebraic_rem`].
25072507
#[rustc_nounwind]
25082508
#[rustc_intrinsic]
25092509
pub fn frem_algebraic<T: Copy>(a: T, b: T) -> T;

‎library/core/src/num/f128.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1362,4 +1362,54 @@ impl f128 {
13621362
// SAFETY: this is actually a safe intrinsic
13631363
unsafe { intrinsics::copysignf128(self, sign) }
13641364
}
1365+
1366+
/// Float addition that allows optimizations based on algebraic rules.
1367+
///
1368+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1369+
#[must_use = "method returns a new number and does not mutate the original value"]
1370+
#[unstable(feature = "float_algebraic", issue = "136469")]
1371+
#[inline]
1372+
pub fn algebraic_add(self, rhs: f128) -> f128 {
1373+
intrinsics::fadd_algebraic(self, rhs)
1374+
}
1375+
1376+
/// Float subtraction that allows optimizations based on algebraic rules.
1377+
///
1378+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1379+
#[must_use = "method returns a new number and does not mutate the original value"]
1380+
#[unstable(feature = "float_algebraic", issue = "136469")]
1381+
#[inline]
1382+
pub fn algebraic_sub(self, rhs: f128) -> f128 {
1383+
intrinsics::fsub_algebraic(self, rhs)
1384+
}
1385+
1386+
/// Float multiplication that allows optimizations based on algebraic rules.
1387+
///
1388+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1389+
#[must_use = "method returns a new number and does not mutate the original value"]
1390+
#[unstable(feature = "float_algebraic", issue = "136469")]
1391+
#[inline]
1392+
pub fn algebraic_mul(self, rhs: f128) -> f128 {
1393+
intrinsics::fmul_algebraic(self, rhs)
1394+
}
1395+
1396+
/// Float division that allows optimizations based on algebraic rules.
1397+
///
1398+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1399+
#[must_use = "method returns a new number and does not mutate the original value"]
1400+
#[unstable(feature = "float_algebraic", issue = "136469")]
1401+
#[inline]
1402+
pub fn algebraic_div(self, rhs: f128) -> f128 {
1403+
intrinsics::fdiv_algebraic(self, rhs)
1404+
}
1405+
1406+
/// Float remainder that allows optimizations based on algebraic rules.
1407+
///
1408+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1409+
#[must_use = "method returns a new number and does not mutate the original value"]
1410+
#[unstable(feature = "float_algebraic", issue = "136469")]
1411+
#[inline]
1412+
pub fn algebraic_rem(self, rhs: f128) -> f128 {
1413+
intrinsics::frem_algebraic(self, rhs)
1414+
}
13651415
}

‎library/core/src/num/f16.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,4 +1338,54 @@ impl f16 {
13381338
// SAFETY: this is actually a safe intrinsic
13391339
unsafe { intrinsics::copysignf16(self, sign) }
13401340
}
1341+
1342+
/// Float addition that allows optimizations based on algebraic rules.
1343+
///
1344+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1345+
#[must_use = "method returns a new number and does not mutate the original value"]
1346+
#[unstable(feature = "float_algebraic", issue = "136469")]
1347+
#[inline]
1348+
pub fn algebraic_add(self, rhs: f16) -> f16 {
1349+
intrinsics::fadd_algebraic(self, rhs)
1350+
}
1351+
1352+
/// Float subtraction that allows optimizations based on algebraic rules.
1353+
///
1354+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1355+
#[must_use = "method returns a new number and does not mutate the original value"]
1356+
#[unstable(feature = "float_algebraic", issue = "136469")]
1357+
#[inline]
1358+
pub fn algebraic_sub(self, rhs: f16) -> f16 {
1359+
intrinsics::fsub_algebraic(self, rhs)
1360+
}
1361+
1362+
/// Float multiplication that allows optimizations based on algebraic rules.
1363+
///
1364+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1365+
#[must_use = "method returns a new number and does not mutate the original value"]
1366+
#[unstable(feature = "float_algebraic", issue = "136469")]
1367+
#[inline]
1368+
pub fn algebraic_mul(self, rhs: f16) -> f16 {
1369+
intrinsics::fmul_algebraic(self, rhs)
1370+
}
1371+
1372+
/// Float division that allows optimizations based on algebraic rules.
1373+
///
1374+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1375+
#[must_use = "method returns a new number and does not mutate the original value"]
1376+
#[unstable(feature = "float_algebraic", issue = "136469")]
1377+
#[inline]
1378+
pub fn algebraic_div(self, rhs: f16) -> f16 {
1379+
intrinsics::fdiv_algebraic(self, rhs)
1380+
}
1381+
1382+
/// Float remainder that allows optimizations based on algebraic rules.
1383+
///
1384+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1385+
#[must_use = "method returns a new number and does not mutate the original value"]
1386+
#[unstable(feature = "float_algebraic", issue = "136469")]
1387+
#[inline]
1388+
pub fn algebraic_rem(self, rhs: f16) -> f16 {
1389+
intrinsics::frem_algebraic(self, rhs)
1390+
}
13411391
}

‎library/core/src/num/f32.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,4 +1504,54 @@ impl f32 {
15041504
// SAFETY: this is actually a safe intrinsic
15051505
unsafe { intrinsics::copysignf32(self, sign) }
15061506
}
1507+
1508+
/// Float addition that allows optimizations based on algebraic rules.
1509+
///
1510+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1511+
#[must_use = "method returns a new number and does not mutate the original value"]
1512+
#[unstable(feature = "float_algebraic", issue = "136469")]
1513+
#[inline]
1514+
pub fn algebraic_add(self, rhs: f32) -> f32 {
1515+
intrinsics::fadd_algebraic(self, rhs)
1516+
}
1517+
1518+
/// Float subtraction that allows optimizations based on algebraic rules.
1519+
///
1520+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1521+
#[must_use = "method returns a new number and does not mutate the original value"]
1522+
#[unstable(feature = "float_algebraic", issue = "136469")]
1523+
#[inline]
1524+
pub fn algebraic_sub(self, rhs: f32) -> f32 {
1525+
intrinsics::fsub_algebraic(self, rhs)
1526+
}
1527+
1528+
/// Float multiplication that allows optimizations based on algebraic rules.
1529+
///
1530+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1531+
#[must_use = "method returns a new number and does not mutate the original value"]
1532+
#[unstable(feature = "float_algebraic", issue = "136469")]
1533+
#[inline]
1534+
pub fn algebraic_mul(self, rhs: f32) -> f32 {
1535+
intrinsics::fmul_algebraic(self, rhs)
1536+
}
1537+
1538+
/// Float division that allows optimizations based on algebraic rules.
1539+
///
1540+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1541+
#[must_use = "method returns a new number and does not mutate the original value"]
1542+
#[unstable(feature = "float_algebraic", issue = "136469")]
1543+
#[inline]
1544+
pub fn algebraic_div(self, rhs: f32) -> f32 {
1545+
intrinsics::fdiv_algebraic(self, rhs)
1546+
}
1547+
1548+
/// Float remainder that allows optimizations based on algebraic rules.
1549+
///
1550+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1551+
#[must_use = "method returns a new number and does not mutate the original value"]
1552+
#[unstable(feature = "float_algebraic", issue = "136469")]
1553+
#[inline]
1554+
pub fn algebraic_rem(self, rhs: f32) -> f32 {
1555+
intrinsics::frem_algebraic(self, rhs)
1556+
}
15071557
}

‎library/core/src/num/f64.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,4 +1503,54 @@ impl f64 {
15031503
// SAFETY: this is actually a safe intrinsic
15041504
unsafe { intrinsics::copysignf64(self, sign) }
15051505
}
1506+
1507+
/// Float addition that allows optimizations based on algebraic rules.
1508+
///
1509+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1510+
#[must_use = "method returns a new number and does not mutate the original value"]
1511+
#[unstable(feature = "float_algebraic", issue = "136469")]
1512+
#[inline]
1513+
pub fn algebraic_add(self, rhs: f64) -> f64 {
1514+
intrinsics::fadd_algebraic(self, rhs)
1515+
}
1516+
1517+
/// Float subtraction that allows optimizations based on algebraic rules.
1518+
///
1519+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1520+
#[must_use = "method returns a new number and does not mutate the original value"]
1521+
#[unstable(feature = "float_algebraic", issue = "136469")]
1522+
#[inline]
1523+
pub fn algebraic_sub(self, rhs: f64) -> f64 {
1524+
intrinsics::fsub_algebraic(self, rhs)
1525+
}
1526+
1527+
/// Float multiplication that allows optimizations based on algebraic rules.
1528+
///
1529+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1530+
#[must_use = "method returns a new number and does not mutate the original value"]
1531+
#[unstable(feature = "float_algebraic", issue = "136469")]
1532+
#[inline]
1533+
pub fn algebraic_mul(self, rhs: f64) -> f64 {
1534+
intrinsics::fmul_algebraic(self, rhs)
1535+
}
1536+
1537+
/// Float division that allows optimizations based on algebraic rules.
1538+
///
1539+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1540+
#[must_use = "method returns a new number and does not mutate the original value"]
1541+
#[unstable(feature = "float_algebraic", issue = "136469")]
1542+
#[inline]
1543+
pub fn algebraic_div(self, rhs: f64) -> f64 {
1544+
intrinsics::fdiv_algebraic(self, rhs)
1545+
}
1546+
1547+
/// Float remainder that allows optimizations based on algebraic rules.
1548+
///
1549+
/// See [algebraic operators](primitive@f32#algebraic-operators) for more info.
1550+
#[must_use = "method returns a new number and does not mutate the original value"]
1551+
#[unstable(feature = "float_algebraic", issue = "136469")]
1552+
#[inline]
1553+
pub fn algebraic_rem(self, rhs: f64) -> f64 {
1554+
intrinsics::frem_algebraic(self, rhs)
1555+
}
15061556
}

‎library/core/src/primitive_docs.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,51 @@ mod prim_f16 {}
13131313
/// | `wasm32`, `wasm64` | If all input NaNs are quiet with all-zero payload: None.<br> Otherwise: all possible payloads. |
13141314
///
13151315
/// For targets not in this table, all payloads are possible.
1316+
///
1317+
/// # Algebraic operators
1318+
///
1319+
/// Algebraic operators of the form `a.algebraic_*(b)` allow the compiler to optimize
1320+
/// floating point operations using all the usual algebraic properties of real numbers --
1321+
/// despite the fact that those properties do *not* hold on floating point numbers.
1322+
/// This can give a great performance boost since it may unlock vectorization.
1323+
///
1324+
/// The exact set of optimizations is unspecified but typically allows combining operations,
1325+
/// rearranging series of operations based on mathematical properties, converting between division
1326+
/// and reciprocal multiplication, and disregarding the sign of zero. This means that the results of
1327+
/// elementary operations may have undefined precision, and "non-mathematical" values
1328+
/// such as NaN, +/-Inf, or -0.0 may behave in unexpected ways, but these operations
1329+
/// will never cause undefined behavior.
1330+
///
1331+
/// Because of the unpredictable nature of compiler optimizations, the same inputs may produce
1332+
/// different results even within a single program run. **Unsafe code must not rely on any property
1333+
/// of the return value for soundness.** However, implementations will generally do their best to
1334+
/// pick a reasonable tradeoff between performance and accuracy of the result.
1335+
///
1336+
/// For example:
1337+
///
1338+
/// ```
1339+
/// # #![feature(float_algebraic)]
1340+
/// # #![allow(unused_assignments)]
1341+
/// # let mut x: f32 = 0.0;
1342+
/// # let a: f32 = 1.0;
1343+
/// # let b: f32 = 2.0;
1344+
/// # let c: f32 = 3.0;
1345+
/// # let d: f32 = 4.0;
1346+
/// x = a.algebraic_add(b).algebraic_add(c).algebraic_add(d);
1347+
/// ```
1348+
///
1349+
/// May be rewritten as:
1350+
///
1351+
/// ```
1352+
/// # #![allow(unused_assignments)]
1353+
/// # let mut x: f32 = 0.0;
1354+
/// # let a: f32 = 1.0;
1355+
/// # let b: f32 = 2.0;
1356+
/// # let c: f32 = 3.0;
1357+
/// # let d: f32 = 4.0;
1358+
/// x = a + b + c + d; // As written
1359+
/// x = (a + c) + (b + d); // Reordered to shorten critical path and enable vectorization
1360+
/// ```
13161361
13171362
#[stable(feature = "rust1", since = "1.0.0")]
13181363
mod prim_f32 {}

‎library/std/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@
341341
#![feature(exact_size_is_empty)]
342342
#![feature(exclusive_wrapper)]
343343
#![feature(extend_one)]
344+
#![feature(float_algebraic)]
344345
#![feature(float_gamma)]
345346
#![feature(float_minimum_maximum)]
346347
#![feature(fmt_internals)]

‎library/std/tests/floats/f128.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,25 @@ fn test_total_cmp() {
984984
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
985985
}
986986

987+
#[test]
988+
fn test_algebraic() {
989+
let a: f128 = 123.0;
990+
let b: f128 = 456.0;
991+
992+
// Check that individual operations match their primitive counterparts.
993+
//
994+
// This is a check of current implementations and does NOT imply any form of
995+
// guarantee about future behavior. The compiler reserves the right to make
996+
// these operations inexact matches in the future.
997+
let eps = if cfg!(miri) { 1e-6 } else { 0.0 };
998+
999+
assert_approx_eq!(a.algebraic_add(b), a + b, eps);
1000+
assert_approx_eq!(a.algebraic_sub(b), a - b, eps);
1001+
assert_approx_eq!(a.algebraic_mul(b), a * b, eps);
1002+
assert_approx_eq!(a.algebraic_div(b), a / b, eps);
1003+
assert_approx_eq!(a.algebraic_rem(b), a % b, eps);
1004+
}
1005+
9871006
#[test]
9881007
fn test_from() {
9891008
assert_eq!(f128::from(false), 0.0);

‎library/std/tests/floats/f16.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,6 +954,27 @@ fn test_total_cmp() {
954954
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
955955
}
956956

957+
#[test]
958+
fn test_algebraic() {
959+
let a: f16 = 123.0;
960+
let b: f16 = 456.0;
961+
962+
// Check that individual operations match their primitive counterparts.
963+
//
964+
// This is a check of current implementations and does NOT imply any form of
965+
// guarantee about future behavior. The compiler reserves the right to make
966+
// these operations inexact matches in the future.
967+
let eps_add = if cfg!(miri) { 1e1 } else { 0.0 };
968+
let eps_mul = if cfg!(miri) { 1e3 } else { 0.0 };
969+
let eps_div = if cfg!(miri) { 1e0 } else { 0.0 };
970+
971+
assert_approx_eq!(a.algebraic_add(b), a + b, eps_add);
972+
assert_approx_eq!(a.algebraic_sub(b), a - b, eps_add);
973+
assert_approx_eq!(a.algebraic_mul(b), a * b, eps_mul);
974+
assert_approx_eq!(a.algebraic_div(b), a / b, eps_div);
975+
assert_approx_eq!(a.algebraic_rem(b), a % b, eps_div);
976+
}
977+
957978
#[test]
958979
fn test_from() {
959980
assert_eq!(f16::from(false), 0.0);

‎library/std/tests/floats/f32.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,3 +915,24 @@ fn test_total_cmp() {
915915
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f32::INFINITY));
916916
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
917917
}
918+
919+
#[test]
920+
fn test_algebraic() {
921+
let a: f32 = 123.0;
922+
let b: f32 = 456.0;
923+
924+
// Check that individual operations match their primitive counterparts.
925+
//
926+
// This is a check of current implementations and does NOT imply any form of
927+
// guarantee about future behavior. The compiler reserves the right to make
928+
// these operations inexact matches in the future.
929+
let eps_add = if cfg!(miri) { 1e-3 } else { 0.0 };
930+
let eps_mul = if cfg!(miri) { 1e-1 } else { 0.0 };
931+
let eps_div = if cfg!(miri) { 1e-4 } else { 0.0 };
932+
933+
assert_approx_eq!(a.algebraic_add(b), a + b, eps_add);
934+
assert_approx_eq!(a.algebraic_sub(b), a - b, eps_add);
935+
assert_approx_eq!(a.algebraic_mul(b), a * b, eps_mul);
936+
assert_approx_eq!(a.algebraic_div(b), a / b, eps_div);
937+
assert_approx_eq!(a.algebraic_rem(b), a % b, eps_div);
938+
}

‎library/std/tests/floats/f64.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -894,3 +894,22 @@ fn test_total_cmp() {
894894
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&f64::INFINITY));
895895
assert_eq!(Ordering::Less, (-s_nan()).total_cmp(&s_nan()));
896896
}
897+
898+
#[test]
899+
fn test_algebraic() {
900+
let a: f64 = 123.0;
901+
let b: f64 = 456.0;
902+
903+
// Check that individual operations match their primitive counterparts.
904+
//
905+
// This is a check of current implementations and does NOT imply any form of
906+
// guarantee about future behavior. The compiler reserves the right to make
907+
// these operations inexact matches in the future.
908+
let eps = if cfg!(miri) { 1e-6 } else { 0.0 };
909+
910+
assert_approx_eq!(a.algebraic_add(b), a + b, eps);
911+
assert_approx_eq!(a.algebraic_sub(b), a - b, eps);
912+
assert_approx_eq!(a.algebraic_mul(b), a * b, eps);
913+
assert_approx_eq!(a.algebraic_div(b), a / b, eps);
914+
assert_approx_eq!(a.algebraic_rem(b), a % b, eps);
915+
}

‎library/std/tests/floats/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![feature(f16, f128, float_gamma, float_minimum_maximum)]
1+
#![feature(f16, f128, float_algebraic, float_gamma, float_minimum_maximum)]
22

33
use std::fmt;
44
use std::ops::{Add, Div, Mul, Rem, Sub};
@@ -10,7 +10,7 @@ macro_rules! assert_approx_eq {
1010
let (a, b) = (&$a, &$b);
1111
let diff = (*a - *b).abs();
1212
assert!(
13-
diff < $lim,
13+
diff <= $lim,
1414
"{a:?} is not approximately equal to {b:?} (threshold {lim:?}, difference {diff:?})",
1515
lim = $lim
1616
);

‎src/tools/miri/src/intrinsics/mod.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
411411
};
412412
let res = this.binary_op(op, &a, &b)?;
413413
// `binary_op` already called `generate_nan` if needed.
414-
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
414+
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
415415
// due to optimizations.
416-
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
416+
let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
417417
this.write_immediate(*res, dest)?;
418418
}
419419

@@ -464,9 +464,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
464464
if !float_finite(&res)? {
465465
throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
466466
}
467-
// Apply a relative error of 16ULP to simulate non-deterministic precision loss
467+
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
468468
// due to optimizations.
469-
let res = apply_random_float_error_to_imm(this, res, 4 /* log2(16) */)?;
469+
let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
470470
this.write_immediate(*res, dest)?;
471471
}
472472

‎tests/codegen/float/algebraic.rs

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Verify that algebraic intrinsics generate the correct LLVM calls
2+
3+
// Ensure operations get inlined
4+
//@ compile-flags: -Copt-level=1
5+
6+
#![crate_type = "lib"]
7+
#![feature(f16)]
8+
#![feature(f128)]
9+
#![feature(float_algebraic)]
10+
11+
// CHECK-LABEL: @f16_algebraic_add
12+
#[no_mangle]
13+
pub fn f16_algebraic_add(a: f16, b: f16) -> f16 {
14+
// CHECK: fadd reassoc nsz arcp contract half %{{.+}}, %{{.+}}
15+
a.algebraic_add(b)
16+
}
17+
18+
// CHECK-LABEL: @f16_algebraic_sub
19+
#[no_mangle]
20+
pub fn f16_algebraic_sub(a: f16, b: f16) -> f16 {
21+
// CHECK: fsub reassoc nsz arcp contract half %{{.+}}, %{{.+}}
22+
a.algebraic_sub(b)
23+
}
24+
25+
// CHECK-LABEL: @f16_algebraic_mul
26+
#[no_mangle]
27+
pub fn f16_algebraic_mul(a: f16, b: f16) -> f16 {
28+
// CHECK: fmul reassoc nsz arcp contract half %{{.+}}, %{{.+}}
29+
a.algebraic_mul(b)
30+
}
31+
32+
// CHECK-LABEL: @f16_algebraic_div
33+
#[no_mangle]
34+
pub fn f16_algebraic_div(a: f16, b: f16) -> f16 {
35+
// CHECK: fdiv reassoc nsz arcp contract half %{{.+}}, %{{.+}}
36+
a.algebraic_div(b)
37+
}
38+
39+
// CHECK-LABEL: @f16_algebraic_rem
40+
#[no_mangle]
41+
pub fn f16_algebraic_rem(a: f16, b: f16) -> f16 {
42+
// CHECK: frem reassoc nsz arcp contract half %{{.+}}, %{{.+}}
43+
a.algebraic_rem(b)
44+
}
45+
46+
// CHECK-LABEL: @f32_algebraic_add
47+
#[no_mangle]
48+
pub fn f32_algebraic_add(a: f32, b: f32) -> f32 {
49+
// CHECK: fadd reassoc nsz arcp contract float %{{.+}}, %{{.+}}
50+
a.algebraic_add(b)
51+
}
52+
53+
// CHECK-LABEL: @f32_algebraic_sub
54+
#[no_mangle]
55+
pub fn f32_algebraic_sub(a: f32, b: f32) -> f32 {
56+
// CHECK: fsub reassoc nsz arcp contract float %{{.+}}, %{{.+}}
57+
a.algebraic_sub(b)
58+
}
59+
60+
// CHECK-LABEL: @f32_algebraic_mul
61+
#[no_mangle]
62+
pub fn f32_algebraic_mul(a: f32, b: f32) -> f32 {
63+
// CHECK: fmul reassoc nsz arcp contract float %{{.+}}, %{{.+}}
64+
a.algebraic_mul(b)
65+
}
66+
67+
// CHECK-LABEL: @f32_algebraic_div
68+
#[no_mangle]
69+
pub fn f32_algebraic_div(a: f32, b: f32) -> f32 {
70+
// CHECK: fdiv reassoc nsz arcp contract float %{{.+}}, %{{.+}}
71+
a.algebraic_div(b)
72+
}
73+
74+
// CHECK-LABEL: @f32_algebraic_rem
75+
#[no_mangle]
76+
pub fn f32_algebraic_rem(a: f32, b: f32) -> f32 {
77+
// CHECK: frem reassoc nsz arcp contract float %{{.+}}, %{{.+}}
78+
a.algebraic_rem(b)
79+
}
80+
81+
// CHECK-LABEL: @f64_algebraic_add
82+
#[no_mangle]
83+
pub fn f64_algebraic_add(a: f64, b: f64) -> f64 {
84+
// CHECK: fadd reassoc nsz arcp contract double %{{.+}}, %{{.+}}
85+
a.algebraic_add(b)
86+
}
87+
88+
// CHECK-LABEL: @f64_algebraic_sub
89+
#[no_mangle]
90+
pub fn f64_algebraic_sub(a: f64, b: f64) -> f64 {
91+
// CHECK: fsub reassoc nsz arcp contract double %{{.+}}, %{{.+}}
92+
a.algebraic_sub(b)
93+
}
94+
95+
// CHECK-LABEL: @f64_algebraic_mul
96+
#[no_mangle]
97+
pub fn f64_algebraic_mul(a: f64, b: f64) -> f64 {
98+
// CHECK: fmul reassoc nsz arcp contract double %{{.+}}, %{{.+}}
99+
a.algebraic_mul(b)
100+
}
101+
102+
// CHECK-LABEL: @f64_algebraic_div
103+
#[no_mangle]
104+
pub fn f64_algebraic_div(a: f64, b: f64) -> f64 {
105+
// CHECK: fdiv reassoc nsz arcp contract double %{{.+}}, %{{.+}}
106+
a.algebraic_div(b)
107+
}
108+
109+
// CHECK-LABEL: @f64_algebraic_rem
110+
#[no_mangle]
111+
pub fn f64_algebraic_rem(a: f64, b: f64) -> f64 {
112+
// CHECK: frem reassoc nsz arcp contract double %{{.+}}, %{{.+}}
113+
a.algebraic_rem(b)
114+
}
115+
116+
// CHECK-LABEL: @f128_algebraic_add
117+
#[no_mangle]
118+
pub fn f128_algebraic_add(a: f128, b: f128) -> f128 {
119+
// CHECK: fadd reassoc nsz arcp contract fp128 %{{.+}}, %{{.+}}
120+
a.algebraic_add(b)
121+
}
122+
123+
// CHECK-LABEL: @f128_algebraic_sub
124+
#[no_mangle]
125+
pub fn f128_algebraic_sub(a: f128, b: f128) -> f128 {
126+
// CHECK: fsub reassoc nsz arcp contract fp128 %{{.+}}, %{{.+}}
127+
a.algebraic_sub(b)
128+
}
129+
130+
// CHECK-LABEL: @f128_algebraic_mul
131+
#[no_mangle]
132+
pub fn f128_algebraic_mul(a: f128, b: f128) -> f128 {
133+
// CHECK: fmul reassoc nsz arcp contract fp128 %{{.+}}, %{{.+}}
134+
a.algebraic_mul(b)
135+
}
136+
137+
// CHECK-LABEL: @f128_algebraic_div
138+
#[no_mangle]
139+
pub fn f128_algebraic_div(a: f128, b: f128) -> f128 {
140+
// CHECK: fdiv reassoc nsz arcp contract fp128 %{{.+}}, %{{.+}}
141+
a.algebraic_div(b)
142+
}
143+
144+
// CHECK-LABEL: @f128_algebraic_rem
145+
#[no_mangle]
146+
pub fn f128_algebraic_rem(a: f128, b: f128) -> f128 {
147+
// CHECK: frem reassoc nsz arcp contract fp128 %{{.+}}, %{{.+}}
148+
a.algebraic_rem(b)
149+
}

‎tests/codegen/float_math.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
#![crate_type = "lib"]
44
#![feature(core_intrinsics)]
55

6-
use std::intrinsics::{fadd_fast, fdiv_fast, fmul_fast, frem_fast, fsub_fast};
6+
use std::intrinsics::{
7+
fadd_algebraic, fadd_fast, fdiv_algebraic, fdiv_fast, fmul_algebraic, fmul_fast,
8+
frem_algebraic, frem_fast, fsub_algebraic, fsub_fast,
9+
};
710

811
// CHECK-LABEL: @add
912
#[no_mangle]
@@ -13,30 +16,72 @@ pub fn add(x: f32, y: f32) -> f32 {
1316
x + y
1417
}
1518

16-
// CHECK-LABEL: @addition
19+
// CHECK-LABEL: @test_fadd_algebraic
1720
#[no_mangle]
18-
pub fn addition(x: f32, y: f32) -> f32 {
19-
// CHECK: fadd fast float
21+
pub fn test_fadd_algebraic(x: f32, y: f32) -> f32 {
22+
// CHECK: fadd reassoc nsz arcp contract float %x, %y
23+
fadd_algebraic(x, y)
24+
}
25+
26+
// CHECK-LABEL: @test_fsub_algebraic
27+
#[no_mangle]
28+
pub fn test_fsub_algebraic(x: f32, y: f32) -> f32 {
29+
// CHECK: fsub reassoc nsz arcp contract float %x, %y
30+
fsub_algebraic(x, y)
31+
}
32+
33+
// CHECK-LABEL: @test_fmul_algebraic
34+
#[no_mangle]
35+
pub fn test_fmul_algebraic(x: f32, y: f32) -> f32 {
36+
// CHECK: fmul reassoc nsz arcp contract float %x, %y
37+
fmul_algebraic(x, y)
38+
}
39+
40+
// CHECK-LABEL: @test_fdiv_algebraic
41+
#[no_mangle]
42+
pub fn test_fdiv_algebraic(x: f32, y: f32) -> f32 {
43+
// CHECK: fdiv reassoc nsz arcp contract float %x, %y
44+
fdiv_algebraic(x, y)
45+
}
46+
47+
// CHECK-LABEL: @test_frem_algebraic
48+
#[no_mangle]
49+
pub fn test_frem_algebraic(x: f32, y: f32) -> f32 {
50+
// CHECK: frem reassoc nsz arcp contract float %x, %y
51+
frem_algebraic(x, y)
52+
}
53+
54+
// CHECK-LABEL: @test_fadd_fast
55+
#[no_mangle]
56+
pub fn test_fadd_fast(x: f32, y: f32) -> f32 {
57+
// CHECK: fadd fast float %x, %y
2058
unsafe { fadd_fast(x, y) }
2159
}
2260

23-
// CHECK-LABEL: @subtraction
61+
// CHECK-LABEL: @test_fsub_fast
2462
#[no_mangle]
25-
pub fn subtraction(x: f32, y: f32) -> f32 {
26-
// CHECK: fsub fast float
63+
pub fn test_fsub_fast(x: f32, y: f32) -> f32 {
64+
// CHECK: fsub fast float %x, %y
2765
unsafe { fsub_fast(x, y) }
2866
}
2967

30-
// CHECK-LABEL: @multiplication
68+
// CHECK-LABEL: @test_fmul_fast
3169
#[no_mangle]
32-
pub fn multiplication(x: f32, y: f32) -> f32 {
33-
// CHECK: fmul fast float
70+
pub fn test_fmul_fast(x: f32, y: f32) -> f32 {
71+
// CHECK: fmul fast float %x, %y
3472
unsafe { fmul_fast(x, y) }
3573
}
3674

37-
// CHECK-LABEL: @division
75+
// CHECK-LABEL: @test_fdiv_fast
3876
#[no_mangle]
39-
pub fn division(x: f32, y: f32) -> f32 {
40-
// CHECK: fdiv fast float
77+
pub fn test_fdiv_fast(x: f32, y: f32) -> f32 {
78+
// CHECK: fdiv fast float %x, %y
4179
unsafe { fdiv_fast(x, y) }
4280
}
81+
82+
// CHECK-LABEL: @test_frem_fast
83+
#[no_mangle]
84+
pub fn test_frem_fast(x: f32, y: f32) -> f32 {
85+
// CHECK: frem fast float %x, %y
86+
unsafe { frem_fast(x, y) }
87+
}

0 commit comments

Comments
 (0)
Please sign in to comment.