Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
From 20d296e9a8b837e586fcc8fc648d4ab9687a2a47 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert@folkertdev.nl>
Date: Mon, 28 Sep 2026 19:14:22 +0200
Subject: [PATCH] sysroot_tests: Disable complex type tests

---
coretests/tests/num/complex.rs | 2 ++
1 file changed, 2 insertions(+)

diff --git a/coretests/tests/num/complex.rs b/coretests/tests/num/complex.rs
index d73d04b1afc..289c0baec17 100644
--- a/coretests/tests/num/complex.rs
+++ b/coretests/tests/num/complex.rs
@@ -75,6 +75,7 @@ fn complex_negation() {
}

#[test]
+#[cfg_attr(target_os = "windows", ignore = "hits an ABI issue with cranelift on windows")]
fn complex_multiplication() {
#[cfg(target_has_reliable_f16)]
assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0));
@@ -105,6 +106,7 @@ fn complex_multiplication() {
}

#[test]
+#[cfg_attr(target_os = "windows", ignore = "hits an ABI issue with cranelift on windows")]
fn complex_div() {
#[cfg(target_has_reliable_f16)]
assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0));
--
2.43.0

54 changes: 53 additions & 1 deletion library/core/src/num/complex.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::ops::{Add, Neg, Sub};
use crate::num::imp::libm::complex::*;
use crate::ops::{Add, Div, Mul, Neg, Sub};

/// A complex number.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -91,3 +92,54 @@ impl<T: Sub<Output = T>> Sub<T> for Complex<T> {
Complex::new(self.re - rhs, self.im)
}
}

macro_rules! impl_complex_mul_div {
($ty:ty, $mul:ident, $div:ident) => {
#[unstable(feature = "complex_numbers", issue = "154023")]
impl Mul for Complex<$ty> {
type Output = Self;

#[inline]
fn mul(self, rhs: Self) -> Self::Output {
let Complex { re: a, im: b } = self;
let Complex { re: c, im: d } = rhs;

let ac = a * c;
let bd = b * d;
let ad = a * d;
let bc = b * c;

let z = Complex::new(ac - bd, ad + bc);

@linkmauve linkmauve Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to use algebraic operators here in case $ty is {float}? This would make it possible to use FMA and FMS instructions on architectures which have those, such as ARM.

View changes since the review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should go with algebraic operators here - it is good to limit the maximum possible error, and algebraic operators don't have any accuracy guarantees at all.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mul_add_relaxed could technically be used once that PR lands. But I think it's good to have a consistent result across platforms until we figure out exactly what we can say about allowed imprecision.


// Only call the libcall when both components are NaN.
//
// The naive algorithm would return NaN + NaNi for an input like
// (1 + 0i) * (inf + infi). The libcall instead returns inf + infi.
//
// We duplicate the fast path here so that it can be inlined. We use a libcall
// for the NaN correction to reduce the size of `core`.
if z.re.is_nan() && z.im.is_nan() {
crate::hint::cold_path();
$mul(a, b, c, d)
} else {
z
}
}
Comment on lines +102 to +127

@tgross35 tgross35 Sep 17, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says pretty much the same thing as the code, it would be helpful to explain why. Also mild preference for cold_path over unlikely since it's stable.

I think the optimization may pay off but it's unfortunate it means repeated work. Not too bad for hard floats but this means e.g. 12 additional function calls if you hit a NaN with f16. I wonder if there's a case to be made that these (and possibly other) libcalls should be preserve_most or preserve_all since stashing the registers is likely the most expensive part of a call.

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote something, and now usecold_path. I'm not sure how much it helps given that the branch is small, but it also serves as documentation that this is an unlikely branch.

And, this ABI etc was designed way before f16 entered the picture, I don't think this will matter much in practice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did mean more of a comment about the optimization going on here, i.e. why are we doing some things inline rather than going directly to the libcall

}

#[unstable(feature = "complex_numbers", issue = "154023")]
impl Div for Complex<$ty> {
type Output = Self;

#[inline]
fn div(self, rhs: Self) -> Self::Output {
$div(self.re, self.im, rhs.re, rhs.im)
}
}
};
}

impl_complex_mul_div!(f16, __rust_mulhc3, __rust_divhc3);
impl_complex_mul_div!(f32, __mulsc3, __divsc3);
impl_complex_mul_div!(f64, __muldc3, __divdc3);
impl_complex_mul_div!(f128, __rust_multc3, __rust_divtc3);
21 changes: 21 additions & 0 deletions library/core/src/num/imp/libm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,27 @@ unsafe extern "C" {
pub(crate) safe fn truncf16(x: f16) -> f16;
}

/// These symbols are always provided by compiler-builtins.
pub(crate) mod complex {
use crate::num::Complex;

unsafe extern "C" {
pub(crate) safe fn __mulsc3(a: f32, b: f32, c: f32, d: f32) -> Complex<f32>;
pub(crate) safe fn __muldc3(a: f64, b: f64, c: f64, d: f64) -> Complex<f64>;

pub(crate) safe fn __divsc3(a: f32, b: f32, c: f32, d: f32) -> Complex<f32>;
pub(crate) safe fn __divdc3(a: f64, b: f64, c: f64, d: f64) -> Complex<f64>;
}

unsafe extern "Rust" {
pub(crate) safe fn __rust_mulhc3(a: f16, b: f16, c: f16, d: f16) -> Complex<f16>;
pub(crate) safe fn __rust_multc3(a: f128, b: f128, c: f128, d: f128) -> Complex<f128>;

pub(crate) safe fn __rust_divhc3(a: f16, b: f16, c: f16, d: f16) -> Complex<f16>;
pub(crate) safe fn __rust_divtc3(a: f128, b: f128, c: f128, d: f128) -> Complex<f128>;
}
}

/// These symbols will be available when `std` is available, and on many no-std platforms. However,
/// since this isn't a guarantee, we cannot rely on them for stable implementations.
pub(crate) mod likely_available {
Expand Down
60 changes: 60 additions & 0 deletions library/coretests/tests/num/complex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,63 @@ fn complex_negation() {
assert_eq!(-Complex::new(1.0, -2.0), Complex::new(-1.0, 2.0));
assert_eq!(-Complex::new(1.0, f32::INFINITY), Complex::new(-1.0, f32::NEG_INFINITY),);
}

#[test]
fn complex_multiplication() {
#[cfg(target_has_reliable_f16)]
assert_eq!(Complex::new(1.0f16, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0));
assert_eq!(Complex::new(1.0f32, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0));
assert_eq!(Complex::new(1.0f64, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0));
#[cfg(target_has_reliable_f128)]
assert_eq!(Complex::new(1.0f128, 2.0) * Complex::new(3.0, 4.0), Complex::new(-5.0, 10.0));

// The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it.
#[cfg(target_has_reliable_f16)]
assert_eq!(
Complex::new(1.0, 0.0) * Complex::new(f16::INFINITY, f16::INFINITY),
Complex::new(f16::INFINITY, f16::INFINITY)
);
assert_eq!(
Complex::new(1.0, 0.0) * Complex::new(f32::INFINITY, f32::INFINITY),
Complex::new(f32::INFINITY, f32::INFINITY)
);
assert_eq!(
Complex::new(1.0, 0.0) * Complex::new(f64::INFINITY, f64::INFINITY),
Complex::new(f64::INFINITY, f64::INFINITY)
);
#[cfg(target_has_reliable_f128)]
assert_eq!(
Complex::new(1.0, 0.0) * Complex::new(f128::INFINITY, f128::INFINITY),
Complex::new(f128::INFINITY, f128::INFINITY)
);
}

#[test]
fn complex_div() {
#[cfg(target_has_reliable_f16)]
assert_eq!(Complex::new(2.0f16, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0));
assert_eq!(Complex::new(2.0f32, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0));
assert_eq!(Complex::new(2.0f64, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0));
#[cfg(target_has_reliable_f128)]
assert_eq!(Complex::new(2.0f128, 11.0) / Complex::new(2.0, 1.0), Complex::new(3.0, 4.0));

// The naive algorithm would return NaN + NaNi for these inputs, but the libcall handles it.
#[cfg(target_has_reliable_f16)]
assert_eq!(
Complex::new(f16::INFINITY, 0.0) / Complex::new(1.0, 1.0),
Complex::new(f16::INFINITY, f16::NEG_INFINITY)
);
assert_eq!(
Complex::new(f32::INFINITY, 0.0) / Complex::new(1.0, 1.0),
Complex::new(f32::INFINITY, f32::NEG_INFINITY)
);
assert_eq!(
Complex::new(f64::INFINITY, 0.0) / Complex::new(1.0, 1.0),
Complex::new(f64::INFINITY, f64::NEG_INFINITY)
);
#[cfg(target_has_reliable_f128)]
assert_eq!(
Complex::new(f128::INFINITY, 0.0) / Complex::new(1.0, 1.0),
Complex::new(f128::INFINITY, f128::NEG_INFINITY)
);
}
Loading