From bcd883a3f5eaee10f4cdefd6cb84ea1b2c4cd898 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 14 Jul 2026 23:43:30 +0200 Subject: [PATCH 01/13] add Complex abi run-make test --- tests/run-make/complex-abi/main.rs | 499 ++++++++++++++++++++++++++++ tests/run-make/complex-abi/rmake.rs | 13 + tests/run-make/complex-abi/test.c | 113 +++++++ 3 files changed, 625 insertions(+) create mode 100644 tests/run-make/complex-abi/main.rs create mode 100644 tests/run-make/complex-abi/rmake.rs create mode 100644 tests/run-make/complex-abi/test.c diff --git a/tests/run-make/complex-abi/main.rs b/tests/run-make/complex-abi/main.rs new file mode 100644 index 0000000000000..5c9a4367b8c62 --- /dev/null +++ b/tests/run-make/complex-abi/main.rs @@ -0,0 +1,499 @@ +// ignore-tidy-file-linelength +#![feature(complex_numbers, f128)] +#![allow(improper_ctypes)] +#![allow(unused_features)] +#![deny(dead_code)] + +use std::ffi::*; +use std::num::Complex; + +fn main() { + sqrt(); + #[cfg(not(target_family = "wasm"))] + mul(); + #[cfg(not(target_family = "wasm"))] + div(); + pass_simple(); + aligned_int(); + aligned_float(); + spill_gpr(); + spill_fpr(); + partial_gpr(); +} + +// This is just a placeholder until we actually add c_longdouble. +#[allow(non_camel_case_types)] +#[allow(unused)] +type c_longdouble = cfg_select! { + target_arch = "aarch64" => f128, + _ => (), +}; + +fn sqrt() { + unsafe extern "C" { + safe fn csqrtf(_: Complex) -> Complex; + safe fn csqrt(_: Complex) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn csqrtl(_: Complex) -> Complex; + } + + let c = Complex::new(-1.0, 0.0); + assert_eq!(csqrtf(c), Complex::new(0.0, 1.0)); + + let c = Complex::new(-1.0, 0.0); + assert_eq!(csqrt(c), Complex::new(0.0, 1.0)); + + #[cfg(target_arch = "aarch64")] + { + let c = Complex::new(-1.0, 0.0); + assert_eq!(csqrtl(c), Complex::new(0.0, 1.0)); + } +} + +#[cfg(not(target_family = "wasm"))] +fn mul() { + unsafe extern "C" { + safe fn __mulsc3(a: c_float, b: c_float, c: c_float, d: c_float) -> Complex; + safe fn __muldc3(a: c_double, b: c_double, c: c_double, d: c_double) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn __multc3( + a: c_longdouble, + b: c_longdouble, + c: c_longdouble, + d: c_longdouble, + ) -> Complex; + } + + assert_eq!(__mulsc3(1.0, 2.0, 3.0, 4.0), Complex::new(-5.0, 10.0)); + assert_eq!(__muldc3(1.0, 2.0, 3.0, 4.0), Complex::new(-5.0, 10.0)); + + #[cfg(target_arch = "aarch64")] + assert_eq!(__multc3(1.0, 2.0, 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. + assert_eq!( + __mulsc3(1.0, 0.0, c_float::INFINITY, c_float::INFINITY), + Complex::new(c_float::INFINITY, c_float::INFINITY) + ); + assert_eq!( + __muldc3(1.0, 0.0, c_double::INFINITY, c_double::INFINITY), + Complex::new(c_double::INFINITY, c_double::INFINITY) + ); + + #[cfg(target_arch = "aarch64")] + assert_eq!( + __multc3(1.0, 0.0, c_longdouble::INFINITY, c_longdouble::INFINITY), + Complex::new(c_longdouble::INFINITY, c_longdouble::INFINITY) + ); +} + +#[cfg(not(target_family = "wasm"))] +fn div() { + unsafe extern "C" { + safe fn __divsc3(a: c_float, b: c_float, c: c_float, d: c_float) -> Complex; + safe fn __divdc3(a: c_double, b: c_double, c: c_double, d: c_double) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn __divtc3( + a: c_longdouble, + b: c_longdouble, + c: c_longdouble, + d: c_longdouble, + ) -> Complex; + } + + assert_eq!(__divsc3(2.0, 11.0, 2.0, 1.0), Complex::new(3.0, 4.0)); + assert_eq!(__divdc3(2.0, 11.0, 2.0, 1.0), Complex::new(3.0, 4.0)); + + #[cfg(target_arch = "aarch64")] + assert_eq!(__divtc3(2.0, 11.0, 2.0, 1.0), Complex::new(3.0, 4.0)); + + assert_eq!( + __divsc3(c_float::INFINITY, 0.0, 1.0, 1.0), + Complex::new(c_float::INFINITY, c_float::NEG_INFINITY) + ); + assert_eq!( + __divdc3(c_double::INFINITY, 0.0, 1.0, 1.0), + Complex::new(c_double::INFINITY, c_double::NEG_INFINITY) + ); + + #[cfg(target_arch = "aarch64")] + assert_eq!( + __divtc3(c_longdouble::INFINITY, 0.0, 1.0, 1.0), + Complex::new(c_longdouble::INFINITY, c_longdouble::NEG_INFINITY) + ); +} + +fn pass_simple() { + #[rustfmt::skip] + #[link(name = "test", kind = "static")] + unsafe extern "C" { + safe fn pass_simple_complex_float(x: Complex) -> Complex; + safe fn pass_simple_complex_double(x: Complex) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn pass_simple_complex_long_double(x: Complex) -> Complex; + + safe fn pass_simple_complex_char(x: Complex) -> Complex; + safe fn pass_simple_complex_short(x: Complex) -> Complex; + safe fn pass_simple_complex_int(x: Complex) -> Complex; + safe fn pass_simple_complex_long(x: Complex) -> Complex; + safe fn pass_simple_complex_long_long(x: Complex) -> Complex; + } + + assert_eq!(Complex::new(3.5, 4.5), pass_simple_complex_float(Complex::new(3.5, 4.5))); + assert_eq!(Complex::new(3.5, 4.5), pass_simple_complex_double(Complex::new(3.5, 4.5))); + + assert_eq!( + Complex::new(c_float::INFINITY, c_float::NEG_INFINITY), + pass_simple_complex_float(Complex::new(c_float::INFINITY, c_float::NEG_INFINITY)) + ); + assert_eq!( + Complex::new(c_double::INFINITY, c_double::NEG_INFINITY), + pass_simple_complex_double(Complex::new(c_double::INFINITY, c_double::NEG_INFINITY)) + ); + #[cfg(target_arch = "aarch64")] + assert_eq!( + Complex::new(c_longdouble::INFINITY, c_longdouble::NEG_INFINITY), + pass_simple_complex_long_double(Complex::new( + c_longdouble::INFINITY, + c_longdouble::NEG_INFINITY + )) + ); + + assert_eq!(Complex::new(3, 4), pass_simple_complex_char(Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), pass_simple_complex_short(Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), pass_simple_complex_int(Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), pass_simple_complex_long(Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), pass_simple_complex_long_long(Complex::new(3, 4))); + + assert_eq!( + Complex::new(c_char::MIN, c_char::MAX), + pass_simple_complex_char(Complex::new(c_char::MIN, c_char::MAX)) + ); + assert_eq!( + Complex::new(c_short::MIN, c_short::MAX), + pass_simple_complex_short(Complex::new(c_short::MIN, c_short::MAX)) + ); + assert_eq!( + Complex::new(c_int::MIN, c_int::MAX), + pass_simple_complex_int(Complex::new(c_int::MIN, c_int::MAX)) + ); + assert_eq!( + Complex::new(c_long::MIN, c_long::MAX), + pass_simple_complex_long(Complex::new(c_long::MIN, c_long::MAX)) + ); + assert_eq!( + Complex::new(c_longlong::MIN, c_longlong::MAX), + pass_simple_complex_long_long(Complex::new(c_longlong::MIN, c_longlong::MAX)) + ); +} + +fn aligned_int() { + #[rustfmt::skip] + #[link(name = "test", kind = "static")] + unsafe extern "C" { + safe fn complex_float_align_int(_: c_int, x: Complex) -> Complex; + safe fn complex_double_align_int(_: c_int, x: Complex) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn complex_long_double_align_int(_: c_int, x: Complex) -> Complex; + + safe fn complex_char_align_int(_: c_int, x: Complex) -> Complex; + safe fn complex_short_align_int(_: c_int, x: Complex) -> Complex; + safe fn complex_int_align_int(_: c_int, x: Complex) -> Complex; + safe fn complex_long_align_int(_: c_int, x: Complex) -> Complex; + safe fn complex_long_long_align_int(_: c_int, x: Complex) -> Complex; + } + + let a = 0xAAAA_AAAAu32 as c_int; + + assert_eq!(Complex::new(3.5, 4.5), complex_float_align_int(a, Complex::new(3.5, 4.5))); + assert_eq!(Complex::new(3.5, 4.5), complex_double_align_int(a, Complex::new(3.5, 4.5))); + + assert_eq!( + Complex::new(c_float::INFINITY, c_float::NEG_INFINITY), + complex_float_align_int(a, Complex::new(c_float::INFINITY, c_float::NEG_INFINITY)) + ); + assert_eq!( + Complex::new(c_double::INFINITY, c_double::NEG_INFINITY), + complex_double_align_int(a, Complex::new(c_double::INFINITY, c_double::NEG_INFINITY)) + ); + #[cfg(target_arch = "aarch64")] + assert_eq!( + Complex::new(c_longdouble::INFINITY, c_longdouble::NEG_INFINITY), + complex_long_double_align_int( + a, + Complex::new(c_longdouble::INFINITY, c_longdouble::NEG_INFINITY) + ) + ); + + assert_eq!(Complex::new(3, 4), complex_char_align_int(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_short_align_int(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_int_align_int(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_long_align_int(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_long_long_align_int(a, Complex::new(3, 4))); + + assert_eq!( + Complex::new(c_char::MIN, c_char::MAX), + complex_char_align_int(a, Complex::new(c_char::MIN, c_char::MAX)) + ); + assert_eq!( + Complex::new(c_short::MIN, c_short::MAX), + complex_short_align_int(a, Complex::new(c_short::MIN, c_short::MAX)) + ); + assert_eq!( + Complex::new(c_int::MIN, c_int::MAX), + complex_int_align_int(a, Complex::new(c_int::MIN, c_int::MAX)) + ); + assert_eq!( + Complex::new(c_long::MIN, c_long::MAX), + complex_long_align_int(a, Complex::new(c_long::MIN, c_long::MAX)) + ); + assert_eq!( + Complex::new(c_longlong::MIN, c_longlong::MAX), + complex_long_long_align_int(a, Complex::new(c_longlong::MIN, c_longlong::MAX)) + ); +} + +fn aligned_float() { + #[rustfmt::skip] + #[link(name = "test", kind = "static")] + unsafe extern "C" { + safe fn complex_float_align_float(_: c_float, x: Complex) -> Complex; + safe fn complex_double_align_float(_: c_float, x: Complex) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn complex_long_double_align_float(_: c_float, x: Complex) -> Complex; + + safe fn complex_char_align_float(_: c_float, x: Complex) -> Complex; + safe fn complex_short_align_float(_: c_float, x: Complex) -> Complex; + safe fn complex_int_align_float(_: c_float, x: Complex) -> Complex; + safe fn complex_long_align_float(_: c_float, x: Complex) -> Complex; + safe fn complex_long_long_align_float(_: c_float, x: Complex) -> Complex; + } + + let a = 3.14 as c_float; + + assert_eq!(Complex::new(3.5, 4.5), complex_float_align_float(a, Complex::new(3.5, 4.5))); + assert_eq!(Complex::new(3.5, 4.5), complex_double_align_float(a, Complex::new(3.5, 4.5))); + + assert_eq!( + Complex::new(c_float::INFINITY, c_float::NEG_INFINITY), + complex_float_align_float(a, Complex::new(c_float::INFINITY, c_float::NEG_INFINITY)) + ); + assert_eq!( + Complex::new(c_double::INFINITY, c_double::NEG_INFINITY), + complex_double_align_float(a, Complex::new(c_double::INFINITY, c_double::NEG_INFINITY)) + ); + #[cfg(target_arch = "aarch64")] + assert_eq!( + Complex::new(c_longdouble::INFINITY, c_longdouble::NEG_INFINITY), + complex_long_double_align_float( + a, + Complex::new(c_longdouble::INFINITY, c_longdouble::NEG_INFINITY) + ) + ); + + assert_eq!(Complex::new(3, 4), complex_char_align_float(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_short_align_float(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_int_align_float(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_long_align_float(a, Complex::new(3, 4))); + assert_eq!(Complex::new(3, 4), complex_long_long_align_float(a, Complex::new(3, 4))); + + assert_eq!( + Complex::new(c_char::MIN, c_char::MAX), + complex_char_align_float(a, Complex::new(c_char::MIN, c_char::MAX)) + ); + assert_eq!( + Complex::new(c_short::MIN, c_short::MAX), + complex_short_align_float(a, Complex::new(c_short::MIN, c_short::MAX)) + ); + assert_eq!( + Complex::new(c_int::MIN, c_int::MAX), + complex_int_align_float(a, Complex::new(c_int::MIN, c_int::MAX)) + ); + assert_eq!( + Complex::new(c_long::MIN, c_long::MAX), + complex_long_align_float(a, Complex::new(c_long::MIN, c_long::MAX)) + ); + assert_eq!( + Complex::new(c_longlong::MIN, c_longlong::MAX), + complex_long_long_align_float(a, Complex::new(c_longlong::MIN, c_longlong::MAX)) + ); +} + +#[rustfmt::skip] +fn spill_gpr() { + #[link(name = "test", kind = "static")] + unsafe extern "C" { + fn spill_trailing_complex_float_1(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex, h: c_int) -> c_int; + + fn spill_trailing_complex_double_1(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, x: Complex, g: c_int) -> c_int; + fn spill_trailing_complex_double_2(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, x: Complex, f: c_int, g: c_int) -> c_int; + fn spill_trailing_complex_double_3(a: c_int, b: c_int, c: c_int, d: c_int, x: Complex, e: c_int, f: c_int, g: c_int) -> c_int; + + #[cfg(target_arch = "aarch64")] + fn spill_trailing_complex_long_double_1(a: c_int, x: Complex, b: c_int) -> c_int; + } + + unsafe { + assert_eq!( + spill_trailing_complex_float_1(0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, 0x77777777, Complex::new(1.0, 2.0), 0x88888888_u32 as c_int), + 0x88888888_u32 as c_int, + ); + + assert_eq!( + spill_trailing_complex_double_1(0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, 0x66666666, Complex::new(1.0, 2.0), 0x77777777), + 0x77777777, + ); + + assert_eq!( + spill_trailing_complex_double_2(0x11111111, 0x22222222, 0x33333333, 0x44444444, 0x55555555, Complex::new(1.0, 2.0), 0x66666666, 0x77777777), + 0x77777777, + ); + + assert_eq!( + spill_trailing_complex_double_3(0x11111111, 0x22222222, 0x33333333, 0x44444444, Complex::new(1.0, 2.0), 0x55555555, 0x66666666, 0x77777777), + 0x77777777, + ); + + #[cfg(target_arch = "aarch64")] + assert_eq!( + spill_trailing_complex_long_double_1(0x11111111, Complex::new(1.0, 2.0), 0x22222222,), + 0x22222222, + ); + } +} + +#[rustfmt::skip] +fn spill_fpr() { + #[link(name = "test", kind = "static")] + unsafe extern "C" { + safe fn spill_trailing_complex_float_1_float(a0: f32, a1: f32, a2: f32, a3: f32, a4: f32, a5: f32, a6: f32, value: Complex, x: f32) -> f32; + safe fn spill_trailing_complex_float_2_float(a0: f32, a1: f32, a2: f32, a3: f32, a4: f32, a5: f32, a6: f32, a7: f32, value: Complex, x: f32) -> f32; + + safe fn spill_trailing_complex_double_1_float(a0: f32, a1: f32, a2: f32, a3: f32, a4: f32, a5: f32, value: Complex, x: f32) -> f32; + safe fn spill_trailing_complex_double_2_float(a0: f32, a1: f32, a2: f32, a3: f32, a4: f32, a5: f32, a6: f32, value: Complex, x: f32, y: f32) -> f32; + safe fn spill_trailing_complex_double_3_float(a0: f32, a1: f32, a2: f32, a3: f32, a4: f32, a5: f32, a6: f32, a7: f32, value: Complex, x: f32, y: f32) -> f32; + + #[cfg(target_arch = "aarch64")] + safe fn spill_trailing_complex_long_double_1_float(a0: f32, a1: f32, a2: f32, a3: f32, a4: f32, a5: f32, value: Complex, x: f32) -> f32; + } + + assert_eq!( + spill_trailing_complex_float_1_float(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, Complex::new(1.0, 2.0), 0.8), + 0.8, + ); + assert_eq!( + spill_trailing_complex_float_2_float(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, Complex::new(1.0, 2.0), 0.8), + 0.8, + ); + + assert_eq!( + spill_trailing_complex_double_1_float(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, Complex::new(1.0, 2.0), 0.6), + 0.6, + ); + assert_eq!( + spill_trailing_complex_double_2_float(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, Complex::new(1.0, 2.0), 0.7, 0.8), + 0.8, + ); + assert_eq!( + spill_trailing_complex_double_3_float(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, Complex::new(1.0, 2.0), 0.8, 0.9), + 0.9, + ); + + #[cfg(target_arch = "aarch64")] + assert_eq!( + spill_trailing_complex_long_double_1_float(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, Complex::new(1.0, 2.0), 0.6), + 0.6, + ); +} + +#[rustfmt::skip] +fn partial_gpr() { + #[link(name = "test", kind = "static")] + unsafe extern "C" { + safe fn partial_complex_float(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + safe fn partial_complex_double(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + #[cfg(target_arch = "aarch64")] + safe fn partial_complex_long_double(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + + safe fn partial_complex_char(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + safe fn partial_complex_short(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + safe fn partial_complex_int(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + safe fn partial_complex_long(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + safe fn partial_complex_long_long(a: c_int, b: c_int, c: c_int, d: c_int, e: c_int, f: c_int, g: c_int, x: Complex) -> Complex; + } + + let complex = Complex { re: 3.14, im: 6.28 }; + assert_eq!( + partial_complex_float( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + + let complex = Complex { re: 3.14, im: 6.28 }; + assert_eq!( + partial_complex_double( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + + #[cfg(target_arch = "aarch64")] + { + let complex = Complex { re: 3.14 as c_longdouble, im: 6.28 as c_longdouble }; + assert_eq!( + partial_complex_long_double( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + } + + let complex = Complex { re: 3 as c_schar, im: 6 as c_schar }; + assert_eq!( + partial_complex_char( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + + let complex = Complex { re: 3 as c_short, im: 6 as c_short }; + assert_eq!( + partial_complex_short( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + + let complex = Complex { re: 3 as c_int, im: 6 as c_int }; + assert_eq!( + partial_complex_int( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + + let complex = Complex { re: 3 as c_long, im: 6 as c_long }; + assert_eq!( + partial_complex_long( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); + + let complex = Complex { re: 3 as c_longlong, im: 6 as c_longlong }; + assert_eq!( + partial_complex_long_long( + 0x11111111, 0x22222222, 0x33333333, 0x44444444, + 0x55555555, 0x66666666, 0x77777777, complex, + ), + complex, + ); +} diff --git a/tests/run-make/complex-abi/rmake.rs b/tests/run-make/complex-abi/rmake.rs new file mode 100644 index 0000000000000..afb4dc9621d6b --- /dev/null +++ b/tests/run-make/complex-abi/rmake.rs @@ -0,0 +1,13 @@ +//@ needs-target-std +//@ ignore-android: FIXME(#142855) +//@ ignore-sgx: (x86 machine code cannot be directly executed) +//@ ignore-pauthtest: (it requires non-trivial compilation of c sources, and only supports dynamic +// linking, ignore the test). + +use run_make_support::{bin_name, build_native_static_lib, env_var, run, rustc}; + +fn main() { + build_native_static_lib("test"); + rustc().linker(&env_var("CC")).input("main.rs").run(); + run(&bin_name("main")); +} diff --git a/tests/run-make/complex-abi/test.c b/tests/run-make/complex-abi/test.c new file mode 100644 index 0000000000000..45155ca6f113d --- /dev/null +++ b/tests/run-make/complex-abi/test.c @@ -0,0 +1,113 @@ +// ignore-tidy-file-linelength + +_Complex float pass_simple_complex_float(_Complex float x) { return x; } +_Complex double pass_simple_complex_double(_Complex double x) { return x; } +_Complex long double pass_simple_complex_long_double(_Complex long double x) { return x; } + +_Complex char pass_simple_complex_char(_Complex char x) { return x; } +_Complex short pass_simple_complex_short(_Complex short x) { return x; } +_Complex int pass_simple_complex_int(_Complex int x) { return x; } +_Complex long pass_simple_complex_long(_Complex long x) { return x; } +_Complex long long pass_simple_complex_long_long(_Complex long long x) { return x; } + + + +_Complex float complex_float_align_int(int a0, _Complex float value) { return value; } +_Complex double complex_double_align_int(int a0, _Complex double value) { return value; } +_Complex long double complex_long_double_align_int(int a0, _Complex long double value) { return value; } + +_Complex char complex_char_align_int(int a0, _Complex char value) { return value; } +_Complex short complex_short_align_int(int a0, _Complex short value) { return value; } +_Complex int complex_int_align_int(int a0, _Complex int value) { return value; } +_Complex long complex_long_align_int(int a0, _Complex long value) { return value; } +_Complex long long complex_long_long_align_int(int a0, _Complex long long value) { return value; } + + + +_Complex float complex_float_align_float(float a0, _Complex float value) { return value; } +_Complex double complex_double_align_float(float a0, _Complex double value) { return value; } +_Complex long double complex_long_double_align_float(float a0, _Complex long double value) { return value; } + +_Complex char complex_char_align_float(float a0, _Complex char value) { return value; } +_Complex short complex_short_align_float(float a0, _Complex short value) { return value; } +_Complex int complex_int_align_float(float a0, _Complex int value) { return value; } +_Complex long complex_long_align_float(float a0, _Complex long value) { return value; } +_Complex long long complex_long_long_align_float(float a0, _Complex long long value) { return value; } + + + +int spill_trailing_complex_float_1(int a0, int a1, int a2, int a3, int a4, int a5, int a6, _Complex float value, int x) { return x; } + +int spill_trailing_complex_double_1(int a0, int a1, int a2, int a3, int a4, int a5, _Complex double value, int x) { return x; } +int spill_trailing_complex_double_2(int a0, int a1, int a2, int a3, int a4, _Complex double value, int x, int y) { return y; } +int spill_trailing_complex_double_3(int a0, int a1, int a2, int a3, _Complex double value, int x, int y, int z) { return z; } + +int spill_trailing_complex_long_double_1(int a0, _Complex long double value, int x) { return x; } + + + +float spill_trailing_complex_float_1_float(float a0, float a1, float a2, float a3, float a4, float a5, float a6, _Complex float value, float x) { return x; } +float spill_trailing_complex_float_2_float(float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, _Complex float value, float x) { return x; } + +float spill_trailing_complex_double_1_float(float a0, float a1, float a2, float a3, float a4, float a5, _Complex double value, float x) { return x; } +float spill_trailing_complex_double_2_float(float a0, float a1, float a2, float a3, float a4, float a5, float a6, _Complex double value, float x, float y) { return y; } +float spill_trailing_complex_double_3_float(float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, _Complex double value, float x, float y) { return y; } + +float spill_trailing_complex_long_double_1_float(float a0, float a1, float a2, float a3, float a4, float a5, _Complex long double value, float x) { return x; } + + +_Complex float partial_complex_float( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex float value) { + return value; +} + +_Complex double partial_complex_double( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex double value) { + return value; +} + +_Complex long double partial_complex_long_double( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex long double value) { + return value; +} + +_Complex char partial_complex_char( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex char value) { + return value; +} + +_Complex short partial_complex_short( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex short value) { + return value; +} + +_Complex int partial_complex_int( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex int value) { + return value; +} + +_Complex long partial_complex_long( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex long value) { + return value; +} + +_Complex long long partial_complex_long_long( + int a0, int a1, int a2, int a3, + int a4, int a5, int a6, + _Complex long long value) { + return value; +} From d8377b1026fe540e0f13a4a2db7f8a6ae5babb80 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 2 Aug 2026 20:45:12 +0200 Subject: [PATCH 02/13] sparc: pass ZST arguments --- compiler/rustc_target/src/callconv/sparc.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/rustc_target/src/callconv/sparc.rs b/compiler/rustc_target/src/callconv/sparc.rs index 71af508915e59..004cd5c8bc5ee 100644 --- a/compiler/rustc_target/src/callconv/sparc.rs +++ b/compiler/rustc_target/src/callconv/sparc.rs @@ -55,6 +55,10 @@ where for arg in fn_abi.args.iter_mut() { if arg.is_ignore() { + if arg.layout.is_zst() { + arg.make_indirect_from_ignore(); + offset += cx.data_layout().pointer_size(); + } continue; } classify_arg(cx, arg, &mut offset); From 28172f4e5c950db58bacde6e8370216037068ecf Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 2 Aug 2026 21:11:17 +0200 Subject: [PATCH 03/13] sparc: pass and return `f128` indirectly long double (i.e. f128) is special-case in the sparc abi --- compiler/rustc_target/src/callconv/sparc.rs | 17 +++++-- tests/codegen-llvm/f128-sparc-callconv.rs | 53 +++++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 tests/codegen-llvm/f128-sparc-callconv.rs diff --git a/compiler/rustc_target/src/callconv/sparc.rs b/compiler/rustc_target/src/callconv/sparc.rs index 004cd5c8bc5ee..a5a1dc36c9d4c 100644 --- a/compiler/rustc_target/src/callconv/sparc.rs +++ b/compiler/rustc_target/src/callconv/sparc.rs @@ -1,16 +1,20 @@ -use rustc_abi::{HasDataLayout, Size, TyAbiInterface}; +use rustc_abi::{BackendRepr, Float, HasDataLayout, Primitive, Size, TyAbiInterface}; use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; +fn is_long_double(repr: BackendRepr) -> bool { + matches!(repr, BackendRepr::Scalar(scalar) if scalar.primitive() == Primitive::Float(Float::F128)) +} + fn classify_ret(cx: &C, ret: &mut ArgAbi<'_, Ty>, offset: &mut Size) where C: HasDataLayout, { - if !ret.layout.is_aggregate() { - ret.extend_integer_width_to(32); - } else { + if is_long_double(ret.layout.backend_repr) || ret.layout.is_aggregate() { ret.make_indirect(); *offset += cx.data_layout().pointer_size(); + } else { + ret.extend_integer_width_to(32); } } @@ -30,6 +34,11 @@ where *offset += dl.pointer_size(); return; } + if is_long_double(arg.layout.backend_repr) { + arg.pass_by_stack_offset(None); + *offset += dl.pointer_size(); + return; + } let size = arg.layout.size; let align = arg.layout.align.abi.max(dl.i32_align).min(dl.i64_align); diff --git a/tests/codegen-llvm/f128-sparc-callconv.rs b/tests/codegen-llvm/f128-sparc-callconv.rs new file mode 100644 index 0000000000000..b5e7b95c3fc6b --- /dev/null +++ b/tests/codegen-llvm/f128-sparc-callconv.rs @@ -0,0 +1,53 @@ +//! Verify that Rust implements the expected calling convention for `f128` + +//@ add-minicore +//@ revisions: sparc-none sparc-linux +//@ [sparc-none] compile-flags: --target sparc-unknown-none-elf +//@ [sparc-linux] compile-flags: --target sparc-unknown-linux-gnu +//@ compile-flags: -Copt-level=3 +//@ needs-llvm-components: sparc + +#![crate_type = "lib"] +#![no_std] +#![no_core] +#![feature(no_core, lang_items, f128)] + +extern crate minicore; + +extern "C" { + fn extern_call(arg0: f128); + fn extern_ret() -> f128; +} + +#[no_mangle] +pub extern "C" fn pass(_arg0: u32, arg1: f128) { + // CHECK-LABEL: @pass( + // an f128 is passed via the stack + // CHECK-SAME: ptr {{.*}}byval([16 x i8] + // CHECK: call void @extern_call + unsafe { extern_call(arg1) }; +} + +// Check that we produce the correct return ABI +#[no_mangle] +pub extern "C" fn ret(_arg0: u32, arg1: f128) -> f128 { + // CHECK-LABEL: @ret( + // and an f128 is returned via the stack + // CHECK-SAME: sret([16 x i8]) + // CHECK: %0 = load fp128, ptr %arg1 + // CHECK-NEXT: store fp128 %0, ptr %_0 + // CHECK-NEXT: ret void + arg1 +} + +// Check that we consume the correct return ABI +#[no_mangle] +pub extern "C" fn forward(dst: *mut f128) { + // CHECK-LABEL: @forward + // CHECK-SAME: ptr{{.*}} %dst) + // without optimizatons, an intermediate alloca is used + // CHECK: call void @extern_ret + // CHECK: store fp128 + // CHECK: ret void + unsafe { *dst = extern_ret() }; +} From 3afa3eab7d489e82b524b5680441907b0835d6ef Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 2 Aug 2026 21:29:43 +0200 Subject: [PATCH 04/13] sparc: pass aggregate arguments by reference this matches GCC and Clang. --- compiler/rustc_target/src/callconv/sparc.rs | 35 ++---- src/llvm-project | 2 +- tests/codegen-llvm/f128-sparc-callconv.rs | 2 +- .../repr/transparent-imm-array.rs | 4 +- tests/codegen-llvm/repr/transparent-sparc.rs | 116 ++++++++++++++++++ 5 files changed, 127 insertions(+), 32 deletions(-) create mode 100644 tests/codegen-llvm/repr/transparent-sparc.rs diff --git a/compiler/rustc_target/src/callconv/sparc.rs b/compiler/rustc_target/src/callconv/sparc.rs index a5a1dc36c9d4c..d342c3cff6e88 100644 --- a/compiler/rustc_target/src/callconv/sparc.rs +++ b/compiler/rustc_target/src/callconv/sparc.rs @@ -1,55 +1,38 @@ -use rustc_abi::{BackendRepr, Float, HasDataLayout, Primitive, Size, TyAbiInterface}; +use rustc_abi::{BackendRepr, Float, HasDataLayout, Primitive, TyAbiInterface}; -use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; +use crate::callconv::{ArgAbi, FnAbi}; fn is_long_double(repr: BackendRepr) -> bool { matches!(repr, BackendRepr::Scalar(scalar) if scalar.primitive() == Primitive::Float(Float::F128)) } -fn classify_ret(cx: &C, ret: &mut ArgAbi<'_, Ty>, offset: &mut Size) -where - C: HasDataLayout, -{ +fn classify_ret<'a, Ty>(ret: &mut ArgAbi<'a, Ty>) { if is_long_double(ret.layout.backend_repr) || ret.layout.is_aggregate() { ret.make_indirect(); - *offset += cx.data_layout().pointer_size(); } else { ret.extend_integer_width_to(32); } } -fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>, offset: &mut Size) +fn classify_arg<'a, Ty, C>(cx: &C, arg: &mut ArgAbi<'a, Ty>) where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { if !arg.layout.is_sized() { - // FIXME: Update offset? // Not touching this... return; } - let dl = cx.data_layout(); if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { arg.make_indirect(); - *offset += dl.pointer_size(); return; } - if is_long_double(arg.layout.backend_repr) { - arg.pass_by_stack_offset(None); - *offset += dl.pointer_size(); - return; - } - let size = arg.layout.size; - let align = arg.layout.align.abi.max(dl.i32_align).min(dl.i64_align); - if arg.layout.is_aggregate() { - let pad_i32 = u8::from(!offset.is_aligned(align)); - arg.cast_to_and_pad_i32(Uniform::new(Reg::i32(), size), pad_i32); + if arg.layout.is_aggregate() || is_long_double(arg.layout.backend_repr) { + arg.make_indirect(); } else { arg.extend_integer_width_to(32); } - - *offset = offset.align_to(align) + size.align_to(align); } pub(crate) fn compute_abi_info<'a, Ty, C>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) @@ -57,19 +40,17 @@ where Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - let mut offset = Size::ZERO; if !fn_abi.ret.is_ignore() { - classify_ret(cx, &mut fn_abi.ret, &mut offset); + classify_ret(&mut fn_abi.ret); } for arg in fn_abi.args.iter_mut() { if arg.is_ignore() { if arg.layout.is_zst() { arg.make_indirect_from_ignore(); - offset += cx.data_layout().pointer_size(); } continue; } - classify_arg(cx, arg, &mut offset); + classify_arg(cx, arg); } } diff --git a/src/llvm-project b/src/llvm-project index 16696adcd119e..21cf284327989 160000 --- a/src/llvm-project +++ b/src/llvm-project @@ -1 +1 @@ -Subproject commit 16696adcd119e6ba9cc175207d984d7021211acb +Subproject commit 21cf28432798952d942bacc6bcee3a328faa3638 diff --git a/tests/codegen-llvm/f128-sparc-callconv.rs b/tests/codegen-llvm/f128-sparc-callconv.rs index b5e7b95c3fc6b..d7fd1a0ff4953 100644 --- a/tests/codegen-llvm/f128-sparc-callconv.rs +++ b/tests/codegen-llvm/f128-sparc-callconv.rs @@ -23,7 +23,7 @@ extern "C" { pub extern "C" fn pass(_arg0: u32, arg1: f128) { // CHECK-LABEL: @pass( // an f128 is passed via the stack - // CHECK-SAME: ptr {{.*}}byval([16 x i8] + // CHECK-SAME: ptr {{.*}} // CHECK: call void @extern_call unsafe { extern_call(arg1) }; } diff --git a/tests/codegen-llvm/repr/transparent-imm-array.rs b/tests/codegen-llvm/repr/transparent-imm-array.rs index c72151741400a..04c9727b815d4 100644 --- a/tests/codegen-llvm/repr/transparent-imm-array.rs +++ b/tests/codegen-llvm/repr/transparent-imm-array.rs @@ -1,5 +1,5 @@ //@ add-minicore -//@ revisions: arm-linux arm-android armv7-linux armv7-android mips thumb sparc +//@ revisions: arm-linux arm-android armv7-linux armv7-android mips thumb //@ compile-flags: -Copt-level=3 -C no-prepopulate-passes //@[arm-linux] compile-flags: --target arm-unknown-linux-gnueabi @@ -14,8 +14,6 @@ //@[mips] needs-llvm-components: mips //@[thumb] compile-flags: --target thumbv7neon-linux-androideabi //@[thumb] needs-llvm-components: arm -//@[sparc] compile-flags: --target sparc-unknown-linux-gnu -//@[sparc] needs-llvm-components: sparc // See ./transparent.rs // Some platforms pass large aggregates using immediate arrays in LLVMIR diff --git a/tests/codegen-llvm/repr/transparent-sparc.rs b/tests/codegen-llvm/repr/transparent-sparc.rs new file mode 100644 index 0000000000000..a9532a966e170 --- /dev/null +++ b/tests/codegen-llvm/repr/transparent-sparc.rs @@ -0,0 +1,116 @@ +//@ add-minicore +//@ revisions: sparc-none sparc-linux +//@ [sparc-none] compile-flags: --target sparc-unknown-none-elf +//@ [sparc-linux] compile-flags: --target sparc-unknown-linux-gnu +//@ compile-flags: -Copt-level=3 -C no-prepopulate-passes +//@ needs-llvm-components: sparc + +// See ./transparent.rs + +#![feature(no_core, lang_items, transparent_unions)] +#![crate_type = "lib"] +#![no_std] +#![no_core] + +extern crate minicore; +use minicore::*; +impl Copy for BigS {} +impl Copy for BigU {} + +#[repr(C)] +pub struct BigS([u32; 16]); + +#[repr(transparent)] +pub struct TsBigS(BigS); + +#[repr(transparent)] +pub union TuBigS { + field: BigS, +} + +#[repr(transparent)] +pub enum TeBigS { + Variant(BigS), +} + +// CHECK: define{{.*}}void @test_BigS(ptr [[BIGS_RET_ATTRS1:.*]] sret([64 x i8]) [[BIGS_RET_ATTRS2:.*]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_BigS(_: BigS) -> BigS { + loop {} +} + +// CHECK: define{{.*}}void @test_TsBigS(ptr [[BIGS_RET_ATTRS1]] sret([64 x i8]) [[BIGS_RET_ATTRS2]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_TsBigS(_: TsBigS) -> TsBigS { + loop {} +} + +// CHECK: define{{.*}}void @test_TuBigS(ptr [[BIGS_RET_ATTRS1]] sret([64 x i8]) [[BIGS_RET_ATTRS2]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_TuBigS(_: TuBigS) -> TuBigS { + loop {} +} + +// CHECK: define{{.*}}void @test_TeBigS(ptr [[BIGS_RET_ATTRS1]] sret([64 x i8]) [[BIGS_RET_ATTRS2]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_TeBigS(_: TeBigS) -> TeBigS { + loop {} +} + +#[repr(C)] +pub union BigU { + foo: [u32; 16], +} + +#[repr(transparent)] +pub struct TsBigU(BigU); + +#[repr(transparent)] +pub union TuBigU { + field: BigU, +} + +#[repr(transparent)] +pub enum TeBigU { + Variant(BigU), +} + +// CHECK: define{{.*}}void @test_BigU(ptr [[BIGU_RET_ATTRS1:.*]] sret([64 x i8]) [[BIGU_RET_ATTRS2:.*]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_BigU(_: BigU) -> BigU { + loop {} +} + +// CHECK: define{{.*}}void @test_TsBigU(ptr [[BIGU_RET_ATTRS1:.*]] sret([64 x i8]) [[BIGU_RET_ATTRS2:.*]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_TsBigU(_: TsBigU) -> TsBigU { + loop {} +} + +// CHECK: define{{.*}}void @test_TuBigU(ptr [[BIGU_RET_ATTRS1]] sret([64 x i8]) [[BIGU_RET_ATTRS2:.*]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_TuBigU(_: TuBigU) -> TuBigU { + loop {} +} + +// CHECK: define{{.*}}void @test_TeBigU(ptr [[BIGU_RET_ATTRS1]] sret([64 x i8]) [[BIGU_RET_ATTRS2:.*]], ptr +// CHECK-NOT: byval +// CHECK-SAME: %{{[0-9a-z_]+}}) +#[no_mangle] +pub extern "C" fn test_TeBigU(_: TeBigU) -> TeBigU { + loop {} +} From 72ff98e76d05024f41b3f0942bbf4b2bd642a6e9 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 9 Aug 2026 16:21:42 +0200 Subject: [PATCH 05/13] add sparc c-zst revision --- tests/ui/abi/c-zst.aarch64-darwin.stderr | 2 +- tests/ui/abi/c-zst.powerpc-linux.stderr | 2 +- tests/ui/abi/c-zst.rs | 20 ++--- tests/ui/abi/c-zst.s390x-linux.stderr | 2 +- tests/ui/abi/c-zst.sparc-linux.stderr | 80 +++++++++++++++++++ tests/ui/abi/c-zst.sparc-none.stderr | 80 +++++++++++++++++++ tests/ui/abi/c-zst.sparc64-linux.stderr | 2 +- tests/ui/abi/c-zst.x86_64-linux.stderr | 2 +- .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 2 +- 9 files changed, 177 insertions(+), 15 deletions(-) create mode 100644 tests/ui/abi/c-zst.sparc-linux.stderr create mode 100644 tests/ui/abi/c-zst.sparc-none.stderr diff --git a/tests/ui/abi/c-zst.aarch64-darwin.stderr b/tests/ui/abi/c-zst.aarch64-darwin.stderr index 6d2ac90c0c975..2ed9ffdf791f6 100644 --- a/tests/ui/abi/c-zst.aarch64-darwin.stderr +++ b/tests/ui/abi/c-zst.aarch64-darwin.stderr @@ -60,7 +60,7 @@ error: fn_abi_of(pass_zst) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/c-zst.rs:65:1 + --> $DIR/c-zst.rs:67:1 | LL | extern "C" fn pass_zst(_: ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/abi/c-zst.powerpc-linux.stderr b/tests/ui/abi/c-zst.powerpc-linux.stderr index edea2d5772280..302ffe1efc8b8 100644 --- a/tests/ui/abi/c-zst.powerpc-linux.stderr +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -71,7 +71,7 @@ error: fn_abi_of(pass_zst) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/c-zst.rs:65:1 + --> $DIR/c-zst.rs:67:1 | LL | extern "C" fn pass_zst(_: ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/abi/c-zst.rs b/tests/ui/abi/c-zst.rs index 22cb3f98f28dc..973f5f0002f25 100644 --- a/tests/ui/abi/c-zst.rs +++ b/tests/ui/abi/c-zst.rs @@ -15,9 +15,9 @@ extern "C" fn(i32, (), i32); ``` */ -/* - * ZST IN "C" IS ZERO-SIZED - */ +// +// ZST IN "C" IS ZERO-SIZED +// //@ revisions: aarch64-darwin //@[aarch64-darwin] compile-flags: --target aarch64-apple-darwin @@ -27,10 +27,9 @@ extern "C" fn(i32, (), i32); //@[x86_64-linux] compile-flags: --target x86_64-unknown-linux-gnu //@[x86_64-linux] needs-llvm-components: x86 - -/* - * ZST IN "C" IS PASS-BY-POINTER - */ +// +// ZST IN "C" IS PASS-BY-POINTER +// // according to the SRV4 ABI, an aggregate is always passed in registers, // and it so happens the GCC extension for ZSTs considers them as structs. @@ -42,7 +41,11 @@ extern "C" fn(i32, (), i32); //@[s390x-linux] compile-flags: --target s390x-unknown-linux-gnu //@[s390x-linux] needs-llvm-components: systemz -//@ revisions: sparc64-linux +//@ revisions: sparc-none sparc-linux sparc64-linux +//@[sparc-none] compile-flags: --target sparc-unknown-none-elf +//@[sparc-none] needs-llvm-components: sparc +//@[sparc-linux] compile-flags: --target sparc-unknown-linux-gnu +//@[sparc-linux] needs-llvm-components: sparc //@[sparc64-linux] compile-flags: --target sparc64-unknown-linux-gnu //@[sparc64-linux] needs-llvm-components: sparc @@ -53,7 +56,6 @@ extern "C" fn(i32, (), i32); //@[x86_64-pc-windows-gnu] needs-llvm-components: x86 //@ ignore-backends: gcc - #![feature(no_core, rustc_attrs)] #![no_core] #![crate_type = "lib"] diff --git a/tests/ui/abi/c-zst.s390x-linux.stderr b/tests/ui/abi/c-zst.s390x-linux.stderr index edea2d5772280..302ffe1efc8b8 100644 --- a/tests/ui/abi/c-zst.s390x-linux.stderr +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -71,7 +71,7 @@ error: fn_abi_of(pass_zst) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/c-zst.rs:65:1 + --> $DIR/c-zst.rs:67:1 | LL | extern "C" fn pass_zst(_: ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/abi/c-zst.sparc-linux.stderr b/tests/ui/abi/c-zst.sparc-linux.stderr new file mode 100644 index 0000000000000..302ffe1efc8b8 --- /dev/null +++ b/tests/ui/abi/c-zst.sparc-linux.stderr @@ -0,0 +1,80 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: $SOME_ALIGN, + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + in_memory_order: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + randomization_seed: 0, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: $SOME_ALIGN, + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + in_memory_order: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + randomization_seed: 0, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:67:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.sparc-none.stderr b/tests/ui/abi/c-zst.sparc-none.stderr new file mode 100644 index 0000000000000..302ffe1efc8b8 --- /dev/null +++ b/tests/ui/abi/c-zst.sparc-none.stderr @@ -0,0 +1,80 @@ +error: fn_abi_of(pass_zst) = FnAbi { + args: [ + ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: $SOME_ALIGN, + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + in_memory_order: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + randomization_seed: 0, + }, + }, + mode: Indirect { + attrs: ArgAttributes { + regular: CapturesAddress | NoAlias | NonNull | NoUndef | NoFree, + arg_ext: None, + pointee_size: Size(0 bytes), + pointee_align: Some( + Align(1 bytes), + ), + }, + meta_attrs: None, + on_stack: false, + }, + }, + ], + ret: ArgAbi { + layout: TyAndLayout { + ty: (), + layout: Layout { + size: Size(0 bytes), + align: AbiAlign { + abi: $SOME_ALIGN, + }, + backend_repr: Memory { + sized: true, + }, + fields: Arbitrary { + offsets: [], + in_memory_order: [], + }, + largest_niche: None, + uninhabited: false, + variants: Single { + index: 0, + }, + max_repr_align: None, + unadjusted_abi_align: $SOME_ALIGN, + randomization_seed: 0, + }, + }, + mode: Ignore, + }, + c_variadic: false, + fixed_count: 1, + conv: C, + can_unwind: false, + } + --> $DIR/c-zst.rs:67:1 + | +LL | extern "C" fn pass_zst(_: ()) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/abi/c-zst.sparc64-linux.stderr b/tests/ui/abi/c-zst.sparc64-linux.stderr index edea2d5772280..302ffe1efc8b8 100644 --- a/tests/ui/abi/c-zst.sparc64-linux.stderr +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -71,7 +71,7 @@ error: fn_abi_of(pass_zst) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/c-zst.rs:65:1 + --> $DIR/c-zst.rs:67:1 | LL | extern "C" fn pass_zst(_: ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/abi/c-zst.x86_64-linux.stderr b/tests/ui/abi/c-zst.x86_64-linux.stderr index 6d2ac90c0c975..2ed9ffdf791f6 100644 --- a/tests/ui/abi/c-zst.x86_64-linux.stderr +++ b/tests/ui/abi/c-zst.x86_64-linux.stderr @@ -60,7 +60,7 @@ error: fn_abi_of(pass_zst) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/c-zst.rs:65:1 + --> $DIR/c-zst.rs:67:1 | LL | extern "C" fn pass_zst(_: ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr index edea2d5772280..302ffe1efc8b8 100644 --- a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr +++ b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr @@ -71,7 +71,7 @@ error: fn_abi_of(pass_zst) = FnAbi { conv: C, can_unwind: false, } - --> $DIR/c-zst.rs:65:1 + --> $DIR/c-zst.rs:67:1 | LL | extern "C" fn pass_zst(_: ()) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From dfdb235776b297b68b788c4f39efa9ed9c8229fd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 26 Jul 2026 20:54:37 +0200 Subject: [PATCH 06/13] add placeholder for amdgpu and a note on llvm 23 changes for bpf --- tests/codegen-llvm/complex-abi.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 81d93b704ee1c..6762e67ebb505 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -83,6 +83,12 @@ // [NVPTX] compile-flags: --target nvptx64-nvidia-cuda // [NVPTX] needs-llvm-components: nvptx +// revisions: AMDGPU +// [AMDGPU] compile-flags: --target amdgcn-amd-amdhsa -Ctarget-cpu=gfx900 +// [AMDGPU] needs-llvm-components: amdgpu + +// NOTE: BPF cannot codegen sret, which the larger Complex<..> types use. +// LLVM 23 adds support, see https://github.com/llvm/llvm-project/pull/206876. // revisions: BPF // [BPF] compile-flags: --target bpfel-unknown-none // [BPF] needs-llvm-components: bpf From 07d0de82bee7661863549d88f703b45493bb10fd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Thu, 13 Aug 2026 14:22:00 +0200 Subject: [PATCH 07/13] add `TyAndLayout::complex_number` --- compiler/rustc_abi/src/layout/ty.rs | 13 ++++++++++++- compiler/rustc_abi/src/lib.rs | 25 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_abi/src/layout/ty.rs b/compiler/rustc_abi/src/layout/ty.rs index 7c878cc619472..10921f94dc74b 100644 --- a/compiler/rustc_abi/src/layout/ty.rs +++ b/compiler/rustc_abi/src/layout/ty.rs @@ -7,7 +7,7 @@ use rustc_macros::StableHash; use crate::layout::{FieldIdx, VariantIdx}; use crate::{ - AbiAlign, Align, BackendRepr, FieldsShape, Float, HasDataLayout, LayoutData, Niche, + AbiAlign, Align, BackendRepr, FieldsShape, Float, HasDataLayout, LayoutData, Niche, Numeric, PointeeInfo, Primitive, Size, Variants, }; @@ -345,6 +345,17 @@ impl<'a, Ty> TyAndLayout<'a, Ty> { } } + pub fn complex_number(&self, cx: &C) -> Option + where + Ty: TyAbiInterface<'a, C> + Copy, + { + match self.complex_number_primitive(cx)? { + Primitive::Int(i, sign) => Some(Numeric::Int(i, sign)), + Primitive::Float(f) => Some(Numeric::Float(f)), + Primitive::Pointer(_) => None, + } + } + /// Whether this type/layout has any padding that is dependent on a variant, i.e. has bytes that /// are padding for some, but not all, valid values of this type. pub fn has_variant_dependent_padding(&self, cx: &C) -> bool diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs index ba5119ee228ad..7eab2fa88cde0 100644 --- a/compiler/rustc_abi/src/lib.rs +++ b/compiler/rustc_abi/src/lib.rs @@ -1443,6 +1443,31 @@ impl Float { } } +/// Numeric primitives. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "nightly", derive(StableHash))] +pub enum Numeric { + /// The `bool` is the signedness of the `Integer` type. + Int(Integer, bool), + Float(Float), +} + +impl Numeric { + pub fn size(self) -> Size { + match self { + Numeric::Int(integer, _) => integer.size(), + Numeric::Float(float) => float.size(), + } + } + + pub fn reg_kind(self) -> RegKind { + match self { + Numeric::Int(_, _) => RegKind::Integer, + Numeric::Float(_) => RegKind::Float, + } + } +} + /// Fundamental unit of memory access and layout. #[derive(Copy, Clone, PartialEq, Eq, Hash)] #[cfg_attr(feature = "nightly", derive(StableHash))] From fa72c11f35e451397996e91540c3f35cbc415c1f Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Tue, 14 Jul 2026 23:43:42 +0200 Subject: [PATCH 08/13] make SPARC64 `Complex` ABI GCC-compatible --- compiler/rustc_target/src/callconv/sparc64.rs | 11 ++++++++-- tests/codegen-llvm/complex-abi.rs | 20 +++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/compiler/rustc_target/src/callconv/sparc64.rs b/compiler/rustc_target/src/callconv/sparc64.rs index 7e441d7100c39..2ea4cfae1038c 100644 --- a/compiler/rustc_target/src/callconv/sparc64.rs +++ b/compiler/rustc_target/src/callconv/sparc64.rs @@ -1,7 +1,7 @@ use arrayvec::ArrayVec; use rustc_abi::{ - Align, BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, - TyAndLayout, Variants, + Align, BackendRepr, FieldsShape, Float, HasDataLayout, Integer, Numeric, Primitive, Reg, + RegKind, Size, TyAbiInterface, TyAndLayout, Variants, }; use crate::callconv::{ArgAbi, ArgAttribute, CastTarget, FnAbi, Uniform}; @@ -143,6 +143,13 @@ fn classify_arg<'a, Ty, C>( *total_double_word_count = start_double_word_count + double_word_count; + // Clang treats `_Complex` like a struct, GCC like a big scalar. That changes how the bits get + // packed. We follow GCC here. See also https://github.com/llvm/llvm-project/pull/212340. + if let Some(Numeric::Int(Integer::I8 | Integer::I16, _)) = arg.layout.complex_number(cx) { + arg.cast_to(Reg { kind: RegKind::Integer, size: total }); + return; + } + const ARGUMENT_REGISTERS: usize = 8; let mut double_words = [DoubleWord::Words([Word::Integer; 2]); ARGUMENT_REGISTERS / 2]; diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 6762e67ebb505..fa8e2f72b8fa3 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -61,8 +61,16 @@ //@ [CSKY] compile-flags: --target csky-unknown-linux-gnuabiv2 //@ [CSKY] needs-llvm-components: csky +//@ revisions: SPARC64 +//@ [SPARC64] compile-flags: --target sparc64-unknown-linux-gnu +//@ [SPARC64] needs-llvm-components: sparc + // FIXME: the below revisions are deliberately disabled for now. +// revisions: SPARC +// [SPARC] compile-flags: --target sparc-unknown-linux-gnu +// [SPARC] needs-llvm-components: sparc + // revisions: POWERPC POWERPC64LE POWERPC64 AIX // [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu // [POWERPC] needs-llvm-components: powerpc @@ -145,7 +153,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // RISCV32: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // RISCV64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // S390X: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} {{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) + // SPARC64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // SPARC: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) // WASM32: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) @@ -179,7 +187,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // RISCV32: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // RISCV64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // S390X: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) + // SPARC64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // SPARC: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) // WASM32: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) @@ -227,7 +235,7 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // RISCV32: define{{.*}} i32 @cplx_i8(i32{{.*}}) // RISCV64: define{{.*}} i64 @cplx_i8(i64{{.*}}) // S390X: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} {{.*}} i64 @cplx_i8(i64{{.*}}) + // SPARC64: define{{.*}} i16 @cplx_i8(i16{{.*}}) // SPARC: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) // WASM32: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) @@ -261,7 +269,7 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // RISCV32: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // RISCV64: define{{.*}} i64 @cplx_i16(i64{{.*}}) // S390X: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} {{.*}} i64 @cplx_i16(i64{{.*}}) + // SPARC64: define{{.*}} i32 @cplx_i16(i32{{.*}}) // SPARC: define{{.*}} { i16, i16 } @cplx_i16(ptr {{.*}} byval({ i16, i16 }) {{.*}}) // WASM32: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) @@ -329,7 +337,7 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // RISCV32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) + // SPARC64: define { i64, i64 } @cplx_i64({ i64, i64 } {{.*}}) // SPARC: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // WASM32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) @@ -368,7 +376,7 @@ pub extern "C" fn wrapper_cplx_i64( // RISCV32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) - // SPARC64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) + // SPARC64: define { i64, i64 } @wrapper_cplx_i64({ i64, i64 } {{.*}}) // SPARC: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // WASM32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) From cc809f2179deaf869dfea4c23528a007df89ef37 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sat, 25 Jul 2026 22:42:32 +0200 Subject: [PATCH 09/13] make SPARC `Complex` ABI GCC-compatible --- compiler/rustc_target/src/callconv/sparc.rs | 51 ++++++++++++++++++--- tests/codegen-llvm/complex-abi.rs | 23 +++++----- 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_target/src/callconv/sparc.rs b/compiler/rustc_target/src/callconv/sparc.rs index d342c3cff6e88..d58a17383c2b0 100644 --- a/compiler/rustc_target/src/callconv/sparc.rs +++ b/compiler/rustc_target/src/callconv/sparc.rs @@ -1,13 +1,43 @@ -use rustc_abi::{BackendRepr, Float, HasDataLayout, Primitive, TyAbiInterface}; +use rustc_abi::{ + BackendRepr, Float, HasDataLayout, Integer, Numeric, Primitive, RegKind, TyAbiInterface, +}; -use crate::callconv::{ArgAbi, FnAbi}; +use crate::callconv::{ArgAbi, ArgAttribute, CastTarget, FnAbi, Reg}; +/// C `long double` is IEEE binary128 on 32-bit SPARC, i.e. Rust's `f128`. It is passed and +/// returned indirectly. fn is_long_double(repr: BackendRepr) -> bool { matches!(repr, BackendRepr::Scalar(scalar) if scalar.primitive() == Primitive::Float(Float::F128)) } -fn classify_ret<'a, Ty>(ret: &mut ArgAbi<'a, Ty>) { - if is_long_double(ret.layout.backend_repr) || ret.layout.is_aggregate() { +fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, + C: HasDataLayout, +{ + if let Some(component) = ret.layout.complex_number(cx) { + let reg = Reg { kind: component.reg_kind(), size: component.size() }; + let mut cast = CastTarget::pair(reg, reg); + + match component { + Numeric::Float(Float::F128) => { + // long double _Complex is special in that it should be marked as inreg. + // See Clang `SparcV8ABIInfo::classifyReturnType`. + cast.attrs.set(ArgAttribute::InReg); + } + Numeric::Float(Float::F16) + | Numeric::Int(Integer::I8 | Integer::I16 | Integer::I32, _) => { + let size = ret.layout.size; + cast = CastTarget::from(Reg { kind: RegKind::Integer, size }); + } + _ => { /* default behavior */ } + } + + ret.cast_to(cast); + } else if is_long_double(ret.layout.backend_repr) || ret.layout.is_aggregate() { + // See Clang `SparcV8ABIInfo::classifyReturnType`, which returns `long double` through an + // `sret` pointer. The `sret` attribute is also what makes LLVM emit the `unimp` marker + // after the call and return to `%o7+12` in the callee. ret.make_indirect(); } else { ret.extend_integer_width_to(32); @@ -28,7 +58,16 @@ where return; } - if arg.layout.is_aggregate() || is_long_double(arg.layout.backend_repr) { + if let Some(component) = arg.layout.complex_number(cx) { + if let Numeric::Int(Integer::I8 | Integer::I16 | Integer::I32, _) = component { + arg.cast_to(Reg { kind: RegKind::Integer, size: 2 * component.size() }); + } else { + arg.pass_by_stack_offset(None); + } + } else if is_long_double(arg.layout.backend_repr) || arg.layout.is_aggregate() { + // `long double` and aggregates are passed by reference: the caller makes a copy and passes + // its address. See Clang `SparcV8ABIInfo::classifyArgumentType` and the + // `DefaultABIInfo::classifyArgumentType` it falls back to. arg.make_indirect(); } else { arg.extend_integer_width_to(32); @@ -41,7 +80,7 @@ where C: HasDataLayout, { if !fn_abi.ret.is_ignore() { - classify_ret(&mut fn_abi.ret); + classify_ret(cx, &mut fn_abi.ret); } for arg in fn_abi.args.iter_mut() { diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index fa8e2f72b8fa3..1a8030f112a19 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -61,16 +61,14 @@ //@ [CSKY] compile-flags: --target csky-unknown-linux-gnuabiv2 //@ [CSKY] needs-llvm-components: csky -//@ revisions: SPARC64 +//@ revisions: SPARC64 SPARC //@ [SPARC64] compile-flags: --target sparc64-unknown-linux-gnu //@ [SPARC64] needs-llvm-components: sparc +//@ [SPARC] compile-flags: --target sparc-unknown-linux-gnu +//@ [SPARC] needs-llvm-components: sparc // FIXME: the below revisions are deliberately disabled for now. -// revisions: SPARC -// [SPARC] compile-flags: --target sparc-unknown-linux-gnu -// [SPARC] needs-llvm-components: sparc - // revisions: POWERPC POWERPC64LE POWERPC64 AIX // [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu // [POWERPC] needs-llvm-components: powerpc @@ -154,7 +152,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // RISCV64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // S390X: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) - // SPARC: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) + // SPARC: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval([8 x i8]) {{.*}}) // WASM32: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} i64 @cplx_f32(ptr {{.*}} byval([8 x i8]) {{.*}}) @@ -188,7 +186,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // RISCV64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // S390X: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) - // SPARC: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) + // SPARC: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval([16 x i8]) {{.*}}) // WASM32: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}} byval([16 x i8]) {{.*}}) @@ -203,6 +201,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { pub extern "C" fn cplx_f128(x: Complex) -> Complex { // AARCH64: define{{.*}} [2 x fp128] {{.*}}) // I686: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}} byval([32 x i8]) {{.*}}) + // SPARC: define{{.*}} inreg { fp128, fp128 } @cplx_f128(ptr {{.*}} byval([32 x i8]) {{.*}}) // WASM32: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}} byval([32 x i8]) {{.*}}) @@ -236,7 +235,7 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // RISCV64: define{{.*}} i64 @cplx_i8(i64{{.*}}) // S390X: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define{{.*}} i16 @cplx_i8(i16{{.*}}) - // SPARC: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) + // SPARC: define{{.*}} i16 @cplx_i8(i16{{.*}}) // WASM32: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} i16 @cplx_i8(ptr {{.*}} byval([2 x i8]) {{.*}}) @@ -270,7 +269,7 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // RISCV64: define{{.*}} i64 @cplx_i16(i64{{.*}}) // S390X: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define{{.*}} i32 @cplx_i16(i32{{.*}}) - // SPARC: define{{.*}} { i16, i16 } @cplx_i16(ptr {{.*}} byval({ i16, i16 }) {{.*}}) + // SPARC: define{{.*}} i32 @cplx_i16(i32{{.*}}) // WASM32: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} i32 @cplx_i16(ptr {{.*}} byval([4 x i8]) {{.*}}) @@ -304,7 +303,7 @@ pub extern "C" fn cplx_i32(x: Complex) -> Complex { // RISCV64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // S390X: define{{.*}} void @cplx_i32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) - // SPARC: define{{.*}} { i32, i32 } @cplx_i32(ptr {{.*}} byval({ i32, i32 }) {{.*}}) + // SPARC: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // WASM32: define{{.*}} void @cplx_i32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} i64 @cplx_i32(ptr {{.*}} byval([8 x i8]) {{.*}}) @@ -338,7 +337,7 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // RISCV64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define { i64, i64 } @cplx_i64({ i64, i64 } {{.*}}) - // SPARC: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) + // SPARC: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval([16 x i8]) {{.*}}) // WASM32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}} byval([16 x i8]) {{.*}}) @@ -377,7 +376,7 @@ pub extern "C" fn wrapper_cplx_i64( // RISCV64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // SPARC64: define { i64, i64 } @wrapper_cplx_i64({ i64, i64 } {{.*}}) - // SPARC: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) + // SPARC: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval([16 x i8]) {{.*}}) // WASM32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // WIN32_GNU: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}} byval([16 x i8]) {{.*}}) From dfc055335e22db88fdadcec5d1ae0bb0ac4d4443 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 26 Jul 2026 14:02:45 +0200 Subject: [PATCH 10/13] make mips64 `Complex` ABI GCC-compatible --- compiler/rustc_target/src/callconv/mips64.rs | 46 +++++++++++++++++++- tests/codegen-llvm/complex-abi.rs | 33 ++++++++++---- 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index 8002f98507ba8..ac059547d45b5 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -1,10 +1,13 @@ use arrayvec::ArrayVec; use rustc_abi::{ - BackendRepr, FieldsShape, Float, HasDataLayout, Primitive, Reg, Size, TyAbiInterface, + BackendRepr, FieldsShape, Float, HasDataLayout, Numeric, Primitive, Reg, RegKind, Size, + TyAbiInterface, }; use crate::callconv::{ArgAbi, ArgAttribute, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; +const NUM_ARG_SLOTS: u64 = 8; + fn extend_integer_width_mips(arg: &mut ArgAbi<'_, Ty>, bits: u64) { // Always sign extend u32 values on 64-bit mips if let BackendRepr::Scalar(scalar) = arg.layout.backend_repr @@ -55,6 +58,19 @@ where let size = ret.layout.size; let bits = size.bits(); if bits <= 128 { + if let Some(component) = ret.layout.complex_number(cx) { + if matches!(component, Numeric::Int(..)) && size <= cx.data_layout().pointer_size() { + // Return a Complex<{integer}> packed into a single register when that fits. + // We match GCC, not Clang, see https://github.com/llvm/llvm-project/issues/212109. + ret.cast_to(Reg { kind: RegKind::Integer, size }); + } else { + // Otherwise pass in 2 registers. + let reg = Reg { kind: component.reg_kind(), size: component.size() }; + ret.cast_to(CastTarget::pair(reg, reg)); + } + return; + } + // Unlike other architectures which return aggregates in registers, MIPS n64 limits the // use of float registers to structures (not unions) containing exactly one or two // float fields. @@ -102,6 +118,34 @@ where extend_integer_width_mips(arg, 64); } else if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { arg.make_indirect(); + } else if arg.layout.complex_float(cx).is_some() && size > dl.pointer_size() * 2 { + // Complex is passed in 4 GPRs, but aligned to 16 so may need padding. + let reg = Reg { kind: RegKind::Float, size: arg.layout.field(cx, 0).size }; + arg.cast_to_and_pad_i32(CastTarget::pair(reg, reg), pad_i32); + } else if arg.layout.is_complex_number(cx) && size <= dl.pointer_size() * 2 { + let slot = dl.pointer_size(); + let curr_offset = offset.align_to(align); + + if arg.layout.complex_float(cx).is_some() { + // Only pass a Complex/Complex in FPRs when two argument slots are free, + // otherwise pack it into GPRs (or the stack) like an integer of the same size. + if curr_offset.bytes() / slot.bytes() + 2 <= NUM_ARG_SLOTS { + // The default `PassMode::Pair` already passes one component per register. Both + // components claim a slot, even for a Complex which could fit into one slot. + *offset = curr_offset + slot * 2; + return; + } + + arg.cast_to_and_pad_i32(Uniform::new(Reg::i64(), size), pad_i32); + } else if size > slot { + // Complex is passed as 2 separate arguments, which is what the default + // `PassMode::Pair` already does. + } else { + // Cast Complex into i16, Complex to i32, etc. The inreg attribute is required + // to make the bits land in the right (upper) bits on big endian targets. + let cast_target = CastTarget::from(Reg { kind: RegKind::Integer, size }); + arg.cast_to(cast_target.with_attrs(ArgAttribute::InReg.into())); + } } else { match arg.layout.fields { FieldsShape::Primitive => unreachable!(), diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 1a8030f112a19..9f1461af6990e 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -67,8 +67,20 @@ //@ [SPARC] compile-flags: --target sparc-unknown-linux-gnu //@ [SPARC] needs-llvm-components: sparc +// NOTE: for Mips we follow the GCC ABI, not the Clang ABI. +// See https://github.com/llvm/llvm-project/issues/212109. +//@ revisions: MIPS64 MIPS64EL +//@ [MIPS64] compile-flags: --target mips64-unknown-linux-gnuabi64 +//@ [MIPS64] needs-llvm-components: mips +//@ [MIPS64EL] compile-flags: --target mips64el-unknown-linux-gnuabi64 +//@ [MIPS64EL] needs-llvm-components: mips + // FIXME: the below revisions are deliberately disabled for now. +// revisions: MIPS +// [MIPS] compile-flags: --target mips-unknown-linux-gnu +// [MIPS] needs-llvm-components: mips + // revisions: POWERPC POWERPC64LE POWERPC64 AIX // [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu // [POWERPC] needs-llvm-components: powerpc @@ -79,12 +91,6 @@ // [AIX] compile-flags: --target powerpc64-ibm-aix // [AIX] needs-llvm-components: powerpc -// revisions: MIPS64EL MIPS -// [MIPS64EL] compile-flags: --target mips64el-unknown-linux-gnuabi64 -// [MIPS64EL] needs-llvm-components: mips -// [MIPS] compile-flags: --target mips-unknown-linux-gnu -// [MIPS] needs-llvm-components: mips - // revisions: NVPTX // [NVPTX] compile-flags: --target nvptx64-nvidia-cuda // [NVPTX] needs-llvm-components: nvptx @@ -142,6 +148,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // I686: define{{.*}} i64 @cplx_f32(ptr {{.*}} byval([8 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // LOONGARCH64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) + // MIPS64: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // MIPS64EL: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // MIPS: define{{.*}} { float, float } @cplx_f32([2 x i32] {{.*}}) // NVPTX: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) @@ -176,6 +183,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // I686: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}} byval([16 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // LOONGARCH64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) + // MIPS64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) // MIPS64EL: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) // MIPS: define{{.*}} { double, double } @cplx_f64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) @@ -201,6 +209,8 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { pub extern "C" fn cplx_f128(x: Complex) -> Complex { // AARCH64: define{{.*}} [2 x fp128] {{.*}}) // I686: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}} byval([32 x i8]) {{.*}}) + // MIPS64: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, i32 %0, { fp128, fp128 } %1) + // MIPS64EL: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, i32 %0, { fp128, fp128 } %1) // SPARC: define{{.*}} inreg { fp128, fp128 } @cplx_f128(ptr {{.*}} byval([32 x i8]) {{.*}}) // WASM32: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}}) // WASM64: define{{.*}} void @cplx_f128(ptr {{.*}} sret([32 x i8]) {{.*}}, ptr {{.*}}) @@ -225,7 +235,8 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // I686: define{{.*}} i16 @cplx_i8(ptr {{.*}} byval([2 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} i32 @cplx_i8(i32{{.*}}) // LOONGARCH64: define{{.*}} i64 @cplx_i8(i64{{.*}}) - // MIPS64EL: define{{.*}} { i8, i8 } @cplx_i8(i16 {{.*}}) + // MIPS64: define{{.*}} i16 @cplx_i8(i16 inreg {{.*}}) + // MIPS64EL: define{{.*}} i16 @cplx_i8(i16 inreg {{.*}}) // MIPS: define{{.*}} { i8, i8 } @cplx_i8(i16 {{.*}}) // NVPTX: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) // POWERPC64: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) @@ -259,7 +270,8 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // I686: define{{.*}} i32 @cplx_i16(ptr {{.*}} byval([4 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // LOONGARCH64: define{{.*}} i64 @cplx_i16(i64{{.*}}) - // MIPS64EL: define{{.*}} { i16, i16 } @cplx_i16(i32 {{.*}}) + // MIPS64: define{{.*}} i32 @cplx_i16(i32 inreg {{.*}}) + // MIPS64EL: define{{.*}} i32 @cplx_i16(i32 inreg {{.*}}) // MIPS: define{{.*}} { i16, i16 } @cplx_i16(i32 {{.*}}) // NVPTX: define{{.*}} { i16, i16 } @cplx_i16(ptr {{.*}} byval({ i16, i16 }) {{.*}}) // POWERPC64: define{{.*}} { i16, i16 } @cplx_i16(i16 {{.*}}, i16 {{.*}}) @@ -293,7 +305,8 @@ pub extern "C" fn cplx_i32(x: Complex) -> Complex { // I686: define{{.*}} i64 @cplx_i32(ptr {{.*}} byval([8 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} [2 x i32] @cplx_i32([2 x i32] {{.*}}) // LOONGARCH64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) - // MIPS64EL: define{{.*}} { i32, i32 } @cplx_i32(i64 {{.*}}) + // MIPS64: define{{.*}} i64 @cplx_i32(i64 inreg {{.*}}) + // MIPS64EL: define{{.*}} i64 @cplx_i32(i64 inreg {{.*}}) // MIPS: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { i32, i32 } @cplx_i32(ptr {{.*}} byval({ i32, i32 }) {{.*}}) // POWERPC64: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) @@ -327,6 +340,7 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // I686: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}} byval([16 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // LOONGARCH64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) + // MIPS64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS64EL: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS: define{{.*}} { i64, i64 } @cplx_i64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) @@ -366,6 +380,7 @@ pub extern "C" fn wrapper_cplx_i64( // I686: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}} byval([16 x i8]) {{.*}}) // LOONGARCH32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // LOONGARCH64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) + // MIPS64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS64EL: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) // NVPTX: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) From 2c539a7196548a1e348050de756ce3865b8e8bdd Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 26 Jul 2026 17:12:50 +0200 Subject: [PATCH 11/13] _Complex for mips --- compiler/rustc_target/src/callconv/mips.rs | 34 +++++++++++++++++----- tests/codegen-llvm/complex-abi.rs | 29 +++++++++++------- 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/compiler/rustc_target/src/callconv/mips.rs b/compiler/rustc_target/src/callconv/mips.rs index d3a39d05b4964..a68c2e71c923d 100644 --- a/compiler/rustc_target/src/callconv/mips.rs +++ b/compiler/rustc_target/src/callconv/mips.rs @@ -1,16 +1,36 @@ -use rustc_abi::{HasDataLayout, Size, TyAbiInterface}; +use rustc_abi::{Float, HasDataLayout, Integer, Numeric, Reg, RegKind, Size, TyAbiInterface}; -use crate::callconv::{ArgAbi, FnAbi, Reg, Uniform}; +use crate::callconv::{ArgAbi, CastTarget, FnAbi, Uniform}; -fn classify_ret(cx: &C, ret: &mut ArgAbi<'_, Ty>, offset: &mut Size) +fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>, offset: &mut Size) where + Ty: TyAbiInterface<'a, C> + Copy, C: HasDataLayout, { - if !ret.layout.is_aggregate() { - ret.extend_integer_width_to(32); - } else { + let dl = cx.data_layout(); + let size = ret.layout.size; + + if let Some(component) = ret.layout.complex_number(cx) { + match component { + Numeric::Float(Float::F128) => { + // Same as an aggregate. + ret.make_indirect(); + *offset += dl.pointer_size(); + } + Numeric::Int(Integer::I8 | Integer::I16, _) => { + // Pack Complex<{integer}> into a single register if that fits. + ret.cast_to(Reg { kind: RegKind::Integer, size }); + } + _ => { + let reg = Reg { kind: component.reg_kind(), size: component.size() }; + ret.cast_to(CastTarget::pair(reg, reg)); + } + } + } else if ret.layout.is_aggregate() { ret.make_indirect(); - *offset += cx.data_layout().pointer_size(); + *offset += dl.pointer_size(); + } else { + ret.extend_integer_width_to(32); } } diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 9f1461af6990e..2d1b953353cea 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -75,11 +75,13 @@ //@ [MIPS64EL] compile-flags: --target mips64el-unknown-linux-gnuabi64 //@ [MIPS64EL] needs-llvm-components: mips -// FIXME: the below revisions are deliberately disabled for now. +//@ revisions: MIPS MIPSEL +//@ [MIPS] compile-flags: --target mips-unknown-linux-gnu +//@ [MIPS] needs-llvm-components: mips +//@ [MIPSEL] compile-flags: --target mipsel-unknown-linux-gnu +//@ [MIPSEL] needs-llvm-components: mips -// revisions: MIPS -// [MIPS] compile-flags: --target mips-unknown-linux-gnu -// [MIPS] needs-llvm-components: mips +// FIXME: the below revisions are deliberately disabled for now. // revisions: POWERPC POWERPC64LE POWERPC64 AIX // [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu @@ -151,6 +153,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // MIPS64: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // MIPS64EL: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // MIPS: define{{.*}} { float, float } @cplx_f32([2 x i32] {{.*}}) + // MIPSEL: define{{.*}} { float, float } @cplx_f32([2 x i32] {{.*}}) // NVPTX: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) // POWERPC64: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // POWERPC64LE: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) @@ -185,7 +188,8 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // LOONGARCH64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // MIPS64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) // MIPS64EL: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) - // MIPS: define{{.*}} { double, double } @cplx_f64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) + // MIPS: define{{.*}} { double, double } @cplx_f64([4 x i32] {{.*}}) + // MIPSEL: define{{.*}} { double, double } @cplx_f64([4 x i32] {{.*}}) // NVPTX: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) // POWERPC64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) // POWERPC64LE: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) @@ -237,7 +241,8 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // LOONGARCH64: define{{.*}} i64 @cplx_i8(i64{{.*}}) // MIPS64: define{{.*}} i16 @cplx_i8(i16 inreg {{.*}}) // MIPS64EL: define{{.*}} i16 @cplx_i8(i16 inreg {{.*}}) - // MIPS: define{{.*}} { i8, i8 } @cplx_i8(i16 {{.*}}) + // MIPS: define{{.*}} i16 @cplx_i8(i32 {{.*}}) + // MIPSEL: define{{.*}} i16 @cplx_i8(i32 {{.*}}) // NVPTX: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) // POWERPC64: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) // POWERPC64LE: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) @@ -272,7 +277,8 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // LOONGARCH64: define{{.*}} i64 @cplx_i16(i64{{.*}}) // MIPS64: define{{.*}} i32 @cplx_i16(i32 inreg {{.*}}) // MIPS64EL: define{{.*}} i32 @cplx_i16(i32 inreg {{.*}}) - // MIPS: define{{.*}} { i16, i16 } @cplx_i16(i32 {{.*}}) + // MIPS: define{{.*}} i32 @cplx_i16(i32 {{.*}}) + // MIPSEL: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // NVPTX: define{{.*}} { i16, i16 } @cplx_i16(ptr {{.*}} byval({ i16, i16 }) {{.*}}) // POWERPC64: define{{.*}} { i16, i16 } @cplx_i16(i16 {{.*}}, i16 {{.*}}) // POWERPC64LE: define{{.*}} { i16, i16 } @cplx_i16(i16 {{.*}}, i16 {{.*}}) @@ -307,7 +313,8 @@ pub extern "C" fn cplx_i32(x: Complex) -> Complex { // LOONGARCH64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // MIPS64: define{{.*}} i64 @cplx_i32(i64 inreg {{.*}}) // MIPS64EL: define{{.*}} i64 @cplx_i32(i64 inreg {{.*}}) - // MIPS: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) + // MIPS: define{{.*}} { i32, i32 } @cplx_i32([2 x i32] {{.*}}) + // MIPSEL: define{{.*}} { i32, i32 } @cplx_i32([2 x i32] {{.*}}) // NVPTX: define{{.*}} { i32, i32 } @cplx_i32(ptr {{.*}} byval({ i32, i32 }) {{.*}}) // POWERPC64: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) // POWERPC64LE: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) @@ -342,7 +349,8 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // LOONGARCH64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // MIPS64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS64EL: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) - // MIPS: define{{.*}} { i64, i64 } @cplx_i64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) + // MIPS: define{{.*}} { i64, i64 } @cplx_i64([4 x i32] {{.*}}) + // MIPSEL: define{{.*}} { i64, i64 } @cplx_i64([4 x i32] {{.*}}) // NVPTX: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // POWERPC64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) // POWERPC64LE: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) @@ -382,7 +390,8 @@ pub extern "C" fn wrapper_cplx_i64( // LOONGARCH64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // MIPS64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) // MIPS64EL: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) - // MIPS: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i32 {{.*}}, i32 {{.*}}, i32 {{.*}}, i32 {{.*}}) + // MIPS: define{{.*}} { i64, i64 } @wrapper_cplx_i64([4 x i32] {{.*}}) + // MIPSEL: define{{.*}} { i64, i64 } @wrapper_cplx_i64([4 x i32] {{.*}}) // NVPTX: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // POWERPC64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) // POWERPC64LE: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) From c948df95767b38726e876df0abe6936446ae7837 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Sun, 26 Jul 2026 20:55:58 +0200 Subject: [PATCH 12/13] make powerpc `Complex` ABI GCC-compatible --- compiler/rustc_target/src/callconv/powerpc.rs | 108 ++++++++++++++++-- .../rustc_target/src/callconv/powerpc64.rs | 9 +- tests/codegen-llvm/complex-abi.rs | 34 +++--- 3 files changed, 123 insertions(+), 28 deletions(-) diff --git a/compiler/rustc_target/src/callconv/powerpc.rs b/compiler/rustc_target/src/callconv/powerpc.rs index 2b6a104e1221d..5c32e68aefe36 100644 --- a/compiler/rustc_target/src/callconv/powerpc.rs +++ b/compiler/rustc_target/src/callconv/powerpc.rs @@ -1,17 +1,45 @@ -use rustc_abi::TyAbiInterface; +use rustc_abi::{BackendRepr, Primitive, Reg, RegKind, TyAbiInterface}; -use crate::callconv::{ArgAbi, FnAbi}; +use crate::callconv::{ArgAbi, CastTarget, FnAbi, Uniform}; use crate::spec::{Env, HasTargetSpec, Os}; -fn classify_ret(ret: &mut ArgAbi<'_, Ty>) { - if ret.layout.is_aggregate() { +const NUM_ARG_GPRS: u32 = 8; // r3..=r10 + +/// How to cast `Complex` so that we match the GCC ABI. +fn complex_cast_target(arg: &ArgAbi<'_, Ty>) -> CastTarget { + let size = arg.layout.size; + + if size.bytes() <= 4 { + // Coerce to an integer for `Complex` and `Complex`. + CastTarget::from(Reg { kind: RegKind::Integer, size }) + } else if size.bytes() == 8 { + // Coerce to a single `i64` for `Complex` and `Complex`, which has the correct + // register alignment of 8 bytes. + // + // NOTE: clang uses a vector (e.g. <2 x f32>) here, but if we try that we run into + // ABI issues because vectors require the altivec target feature. + CastTarget::from(Reg::i64()) + } else { + // Coerce to an array `[N x i32]` for everything wider. An array of i32 gives the correct + // 4-byte register alignment. + CastTarget::from(Uniform::new(Reg::i32(), size)) + } +} + +fn classify_ret<'a, Ty, C>(cx: &C, ret: &mut ArgAbi<'a, Ty>) +where + Ty: TyAbiInterface<'a, C> + Copy, +{ + if ret.layout.is_complex_number(cx) { + ret.cast_to(complex_cast_target(ret)); + } else if ret.layout.is_aggregate() { ret.make_indirect(); } else { ret.extend_integer_width_to(32); } } -fn classify_arg<'a, Ty, C: HasTargetSpec>(cx: &C, arg: &mut ArgAbi<'a, Ty>) +fn classify_arg<'a, Ty, C: HasTargetSpec>(cx: &C, arg: &mut ArgAbi<'a, Ty>, arg_gprs_left: &mut u32) where Ty: TyAbiInterface<'a, C> + Copy, { @@ -25,11 +53,70 @@ where } return; } - if arg.layout.pass_indirectly_in_non_rustic_abis(cx) || arg.layout.is_aggregate() { - arg.make_indirect(); + + let default = |arg: &mut ArgAbi<'a, Ty>| { + if arg.layout.pass_indirectly_in_non_rustic_abis(cx) || arg.layout.is_aggregate() { + arg.make_indirect(); + } else { + arg.extend_integer_width_to(32); + } + }; + + let is_complex = arg.layout.is_complex_number(cx); + let is_float = match arg.layout.backend_repr { + BackendRepr::Scalar(scalar) => matches!(scalar.primitive(), Primitive::Float(_)), + _ => false, + }; + + // Arguments that are not relevant for the GPR budget: floats go in the FPRs, and once the GPRs + // are exhausted everything lands on the stack anyway. Complex always needs custom handling. + if (*arg_gprs_left == 0 || is_float) && !is_complex { + return default(arg); + } + + let size = arg.layout.size; + let regs_needed = size.bytes().div_ceil(4) as u32; // 32-bit registers + + if arg.layout.is_aggregate() && !is_complex { + // Non-complex aggregates are passed indirectly, and consume one GPR. + *arg_gprs_left -= 1; } else { - arg.extend_integer_width_to(32); + let mut padding = 0; + + // The powerpc ABI in GCC hardcodes a special rule for values of size 8. It remarks + // + // > V.4 wants long longs and doubles to be double word aligned. Just + // > testing the mode size is a boneheaded way to do this as it means + // > that other types such as complex int are also double word aligned. + // > However, we're stuck with this because changing the ABI might break + // > existing library interfaces. + // + // An eight-byte value must start in an even-numbered GPR. The `i64` it is coerced to + // already makes LLVM skip an odd register, so only account for it in the budget. + if size.bytes() == 8 && !arg_gprs_left.is_multiple_of(2) { + *arg_gprs_left -= 1; + } + + if regs_needed <= *arg_gprs_left { + // Everything fits, great! + *arg_gprs_left -= regs_needed; + } else if is_complex { + // Never split a Complex across the GPRs and the stack. + // + // The full complex value is passed via the stack, and the remaining GPRs are consumed, + // so all subsequent arguments will also be passed via the stack. Use the padding value + // to fill up the remaining GPRs. + padding += *arg_gprs_left; + *arg_gprs_left = 0; + } + + if is_complex { + arg.cast_to_and_pad_i32(complex_cast_target(arg), padding as u8); + return; + } } + + default(arg) } pub(crate) fn compute_abi_info<'a, Ty, C: HasTargetSpec>(cx: &C, fn_abi: &mut FnAbi<'a, Ty>) @@ -37,10 +124,11 @@ where Ty: TyAbiInterface<'a, C> + Copy, { if !fn_abi.ret.is_ignore() { - classify_ret(&mut fn_abi.ret); + classify_ret(cx, &mut fn_abi.ret); } + let mut arg_gprs_left = NUM_ARG_GPRS; for arg in fn_abi.args.iter_mut() { - classify_arg(cx, arg); + classify_arg(cx, arg, &mut arg_gprs_left); } } diff --git a/compiler/rustc_target/src/callconv/powerpc64.rs b/compiler/rustc_target/src/callconv/powerpc64.rs index 3eb40abe90f33..7d017d5a19174 100644 --- a/compiler/rustc_target/src/callconv/powerpc64.rs +++ b/compiler/rustc_target/src/callconv/powerpc64.rs @@ -4,7 +4,7 @@ use rustc_abi::{HasDataLayout, TyAbiInterface}; -use crate::callconv::{Align, ArgAbi, FnAbi, Reg, RegKind, Uniform}; +use crate::callconv::{Align, ArgAbi, CastTarget, FnAbi, Reg, RegKind, Uniform}; use crate::spec::{HasTargetSpec, LlvmAbi, Os}; #[derive(Debug, Clone, Copy, PartialEq)] @@ -60,6 +60,13 @@ where arg.extend_integer_width_to(64); return; } + if let Some(component) = arg.layout.complex_number(cx) { + if is_ret { + let reg = Reg { kind: component.reg_kind(), size: component.size() }; + arg.cast_to(CastTarget::pair(reg, reg)); + } + return; + } // The AIX ABI expect byval for aggregates // See https://github.com/llvm/llvm-project/blob/main/clang/lib/CodeGen/Targets/PPC.cpp. diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index 2d1b953353cea..3b404fad2af32 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -81,17 +81,17 @@ //@ [MIPSEL] compile-flags: --target mipsel-unknown-linux-gnu //@ [MIPSEL] needs-llvm-components: mips -// FIXME: the below revisions are deliberately disabled for now. +//@ revisions: POWERPC POWERPC64LE POWERPC64 AIX +//@ [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu +//@ [POWERPC] needs-llvm-components: powerpc +//@ [POWERPC64LE] compile-flags: --target powerpc64le-unknown-linux-gnu +//@ [POWERPC64LE] needs-llvm-components: powerpc +//@ [POWERPC64] compile-flags: --target powerpc64-unknown-linux-gnu +//@ [POWERPC64] needs-llvm-components: powerpc +//@ [AIX] compile-flags: --target powerpc64-ibm-aix +//@ [AIX] needs-llvm-components: powerpc -// revisions: POWERPC POWERPC64LE POWERPC64 AIX -// [POWERPC] compile-flags: --target powerpc-unknown-linux-gnu -// [POWERPC] needs-llvm-components: powerpc -// [POWERPC64LE] compile-flags: --target powerpc64le-unknown-linux-gnu -// [POWERPC64LE] needs-llvm-components: powerpc -// [POWERPC64] compile-flags: --target powerpc64-unknown-linux-gnu -// [POWERPC64] needs-llvm-components: powerpc -// [AIX] compile-flags: --target powerpc64-ibm-aix -// [AIX] needs-llvm-components: powerpc +// FIXME: the below revisions are deliberately disabled for now. // revisions: NVPTX // [NVPTX] compile-flags: --target nvptx64-nvidia-cuda @@ -157,7 +157,7 @@ pub extern "C" fn cplx_f32(x: Complex) -> Complex { // NVPTX: define{{.*}} { float, float } @cplx_f32(ptr {{.*}} byval({ float, float }) {{.*}}) // POWERPC64: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) // POWERPC64LE: define{{.*}} { float, float } @cplx_f32(float {{.*}}, float {{.*}}) - // POWERPC: define{{.*}} void @cplx_f32(ptr {{.*}} sret({ float, float }) {{.*}}, ptr {{.*}} byval({ float, float }) {{.*}}) + // POWERPC: define{{.*}} i64 @cplx_f32(i64 {{.*}}) // RISCV32: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // RISCV64: define{{.*}} { float, float } @cplx_f32({ float, float } {{.*}}) // S390X: define{{.*}} void @cplx_f32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) @@ -193,7 +193,7 @@ pub extern "C" fn cplx_f64(x: Complex) -> Complex { // NVPTX: define{{.*}} { double, double } @cplx_f64(ptr {{.*}} byval({ double, double }) {{.*}}) // POWERPC64: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) // POWERPC64LE: define{{.*}} { double, double } @cplx_f64(double {{.*}}, double {{.*}}) - // POWERPC: define{{.*}} void @cplx_f64(ptr {{.*}} sret({ double, double }) {{.*}}, ptr {{.*}} byval({ double, double }) {{.*}}) + // POWERPC: define{{.*}} [4 x i32] @cplx_f64([4 x i32] {{.*}}) // RISCV32: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // RISCV64: define{{.*}} { double, double } @cplx_f64({ double, double } {{.*}}) // S390X: define{{.*}} void @cplx_f64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) @@ -246,7 +246,7 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // NVPTX: define{{.*}} { i8, i8 } @cplx_i8(ptr {{.*}} byval({ i8, i8 }) {{.*}}) // POWERPC64: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) // POWERPC64LE: define{{.*}} { i8, i8 } @cplx_i8(i8 {{.*}}, i8 {{.*}}) - // POWERPC: define{{.*}} void @cplx_i8(ptr {{.*}} sret({ i8, i8 }) {{.*}}, ptr {{.*}} byval({ i8, i8 }) {{.*}}) + // POWERPC: define{{.*}} i16 @cplx_i8(i16 {{.*}}) // RISCV32: define{{.*}} i32 @cplx_i8(i32{{.*}}) // RISCV64: define{{.*}} i64 @cplx_i8(i64{{.*}}) // S390X: define{{.*}} void @cplx_i8(ptr {{.*}} sret([2 x i8]) {{.*}}, ptr {{.*}}) @@ -282,7 +282,7 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // NVPTX: define{{.*}} { i16, i16 } @cplx_i16(ptr {{.*}} byval({ i16, i16 }) {{.*}}) // POWERPC64: define{{.*}} { i16, i16 } @cplx_i16(i16 {{.*}}, i16 {{.*}}) // POWERPC64LE: define{{.*}} { i16, i16 } @cplx_i16(i16 {{.*}}, i16 {{.*}}) - // POWERPC: define{{.*}} void @cplx_i16(ptr {{.*}} sret({ i16, i16 }) {{.*}}, ptr {{.*}} byval({ i16, i16 }) {{.*}}) + // POWERPC: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // RISCV32: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // RISCV64: define{{.*}} i64 @cplx_i16(i64{{.*}}) // S390X: define{{.*}} void @cplx_i16(ptr {{.*}} sret([4 x i8]) {{.*}}, ptr {{.*}}) @@ -318,7 +318,7 @@ pub extern "C" fn cplx_i32(x: Complex) -> Complex { // NVPTX: define{{.*}} { i32, i32 } @cplx_i32(ptr {{.*}} byval({ i32, i32 }) {{.*}}) // POWERPC64: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) // POWERPC64LE: define{{.*}} { i32, i32 } @cplx_i32(i32 {{.*}}, i32 {{.*}}) - // POWERPC: define{{.*}} void @cplx_i32(ptr {{.*}} sret({ i32, i32 }) {{.*}}, ptr {{.*}} byval({ i32, i32 }) {{.*}}) + // POWERPC: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // RISCV32: define{{.*}} [2 x i32] @cplx_i32([2 x i32] {{.*}}) // RISCV64: define{{.*}} i64 @cplx_i32(i64 {{.*}}) // S390X: define{{.*}} void @cplx_i32(ptr {{.*}} sret([8 x i8]) {{.*}}, ptr {{.*}}) @@ -354,7 +354,7 @@ pub extern "C" fn cplx_i64(x: Complex) -> Complex { // NVPTX: define{{.*}} { i64, i64 } @cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // POWERPC64: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) // POWERPC64LE: define{{.*}} { i64, i64 } @cplx_i64(i64 {{.*}}, i64 {{.*}}) - // POWERPC: define{{.*}} void @cplx_i64(ptr {{.*}} sret({ i64, i64 }) {{.*}}, ptr {{.*}} byval({ i64, i64 }) {{.*}}) + // POWERPC: define{{.*}} [4 x i32] @cplx_i64([4 x i32] {{.*}}) // RISCV32: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) @@ -395,7 +395,7 @@ pub extern "C" fn wrapper_cplx_i64( // NVPTX: define{{.*}} { i64, i64 } @wrapper_cplx_i64(ptr {{.*}} byval({ i64, i64 }) {{.*}}) // POWERPC64: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) // POWERPC64LE: define{{.*}} { i64, i64 } @wrapper_cplx_i64(i64 {{.*}}, i64 {{.*}}) - // POWERPC: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret({ i64, i64 }) {{.*}}, ptr {{.*}} byval({ i64, i64 }) {{.*}}) + // POWERPC: define{{.*}} [4 x i32] @wrapper_cplx_i64([4 x i32] {{.*}}) // RISCV32: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) // RISCV64: define{{.*}} [2 x i64] @wrapper_cplx_i64([2 x i64] {{.*}}) // S390X: define{{.*}} void @wrapper_cplx_i64(ptr {{.*}} sret([16 x i8]) {{.*}}, ptr {{.*}}) From 34fe97c6cb30da04794b4bfa2602c27dabb8dbc5 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Mon, 24 Aug 2026 14:18:52 +0200 Subject: [PATCH 13/13] sparc64 cleanup --- compiler/rustc_target/src/callconv/mips64.rs | 72 +++++++++++--------- 1 file changed, 40 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_target/src/callconv/mips64.rs b/compiler/rustc_target/src/callconv/mips64.rs index ac059547d45b5..d28edb46bed85 100644 --- a/compiler/rustc_target/src/callconv/mips64.rs +++ b/compiler/rustc_target/src/callconv/mips64.rs @@ -1,7 +1,7 @@ use arrayvec::ArrayVec; use rustc_abi::{ - BackendRepr, FieldsShape, Float, HasDataLayout, Numeric, Primitive, Reg, RegKind, Size, - TyAbiInterface, + BackendRepr, FieldsShape, Float, HasDataLayout, Integer, Numeric, Primitive, Reg, RegKind, + Size, TyAbiInterface, }; use crate::callconv::{ArgAbi, ArgAttribute, ArgExtension, CastTarget, FnAbi, PassMode, Uniform}; @@ -59,14 +59,17 @@ where let bits = size.bits(); if bits <= 128 { if let Some(component) = ret.layout.complex_number(cx) { - if matches!(component, Numeric::Int(..)) && size <= cx.data_layout().pointer_size() { - // Return a Complex<{integer}> packed into a single register when that fits. - // We match GCC, not Clang, see https://github.com/llvm/llvm-project/issues/212109. - ret.cast_to(Reg { kind: RegKind::Integer, size }); - } else { - // Otherwise pass in 2 registers. - let reg = Reg { kind: component.reg_kind(), size: component.size() }; - ret.cast_to(CastTarget::pair(reg, reg)); + match component { + Numeric::Int(Integer::I8 | Integer::I16 | Integer::I32, _) => { + // Return a Complex<{integer}> packed into a single register when that fits. + // We match GCC, not Clang, see https://github.com/llvm/llvm-project/issues/212109. + ret.cast_to(Reg { kind: RegKind::Integer, size }); + } + _ => { + // Otherwise pass in 2 registers. + let reg = Reg { kind: component.reg_kind(), size: component.size() }; + ret.cast_to(CastTarget::pair(reg, reg)); + } } return; } @@ -118,33 +121,38 @@ where extend_integer_width_mips(arg, 64); } else if arg.layout.pass_indirectly_in_non_rustic_abis(cx) { arg.make_indirect(); - } else if arg.layout.complex_float(cx).is_some() && size > dl.pointer_size() * 2 { - // Complex is passed in 4 GPRs, but aligned to 16 so may need padding. - let reg = Reg { kind: RegKind::Float, size: arg.layout.field(cx, 0).size }; - arg.cast_to_and_pad_i32(CastTarget::pair(reg, reg), pad_i32); - } else if arg.layout.is_complex_number(cx) && size <= dl.pointer_size() * 2 { + } else if let Some(component) = arg.layout.complex_number(cx) { let slot = dl.pointer_size(); let curr_offset = offset.align_to(align); - if arg.layout.complex_float(cx).is_some() { - // Only pass a Complex/Complex in FPRs when two argument slots are free, - // otherwise pack it into GPRs (or the stack) like an integer of the same size. - if curr_offset.bytes() / slot.bytes() + 2 <= NUM_ARG_SLOTS { - // The default `PassMode::Pair` already passes one component per register. Both - // components claim a slot, even for a Complex which could fit into one slot. - *offset = curr_offset + slot * 2; - return; + match component { + Numeric::Float(Float::F128) => { + // Complex is passed in 4 GPRs, but aligned to 16 so may need padding. + let reg = Reg { kind: RegKind::Float, size: arg.layout.field(cx, 0).size }; + arg.cast_to_and_pad_i32(CastTarget::pair(reg, reg), pad_i32); } + Numeric::Float(_) => { + // Only pass a Complex/Complex in FPRs when two argument slots are free, + if curr_offset.bytes() / slot.bytes() + 2 <= NUM_ARG_SLOTS { + // The default `PassMode::Pair` already passes one component per register. Both + // components claim a slot, even a Complex which could fit into one slot. + *offset = curr_offset + slot * 2; + return; + } - arg.cast_to_and_pad_i32(Uniform::new(Reg::i64(), size), pad_i32); - } else if size > slot { - // Complex is passed as 2 separate arguments, which is what the default - // `PassMode::Pair` already does. - } else { - // Cast Complex into i16, Complex to i32, etc. The inreg attribute is required - // to make the bits land in the right (upper) bits on big endian targets. - let cast_target = CastTarget::from(Reg { kind: RegKind::Integer, size }); - arg.cast_to(cast_target.with_attrs(ArgAttribute::InReg.into())); + // Otherwise pack it into GPRs (or the stack) like an integer of the same size. + arg.cast_to_and_pad_i32(Uniform::new(Reg::i64(), size), pad_i32); + } + Numeric::Int(Integer::I8 | Integer::I16 | Integer::I32, _) => { + // Cast Complex into i16, Complex to i32, etc. + let cast_target = CastTarget::from(Reg { kind: RegKind::Integer, size }); + // The inreg attribute makes the bits land in the right (upper) bits on BE targets. + arg.cast_to(cast_target.with_attrs(ArgAttribute::InReg.into())); + } + Numeric::Int(Integer::I64 | Integer::I128, _) => { + // Complex is passed as 2 separate arguments, which is what the default + // `PassMode::Pair` already does. + } } } else { match arg.layout.fields {