From b1a3475aece76762fb5f6d4cc6649b8993d19bf0 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 25 Sep 2026 18:01:58 +0200 Subject: [PATCH] `unit` and `[1 x unit]` layouts are incompatible on `aarch64_be` so don't unwrap singleton arrays for it --- compiler/rustc_codegen_llvm/src/abi.rs | 15 +-- .../rustc_target/src/callconv/powerpc64.rs | 2 +- tests/assembly-llvm/singleton-hfa.rs | 99 +++++++++++++++++++ .../aarch64-abi/homogeneous-aggregate.rs | 23 ++++- .../arm-abi/homogeneous-aggregate.rs | 66 +++++++++---- .../codegen-llvm/arm-abi/struct-alignment.rs | 8 +- tests/codegen-llvm/complex-abi.rs | 4 +- .../powerpc64-abi/homogeneous-aggregate.rs | 25 +++-- 8 files changed, 201 insertions(+), 41 deletions(-) create mode 100644 tests/assembly-llvm/singleton-hfa.rs diff --git a/compiler/rustc_codegen_llvm/src/abi.rs b/compiler/rustc_codegen_llvm/src/abi.rs index 19e6a3314f20a..2c3d805e4c63b 100644 --- a/compiler/rustc_codegen_llvm/src/abi.rs +++ b/compiler/rustc_codegen_llvm/src/abi.rs @@ -195,13 +195,16 @@ impl LlvmType for CastTarget { self.rest.total.bytes().div_ceil(self.rest.unit.size.bytes()) }; - // Simplify to a single unit or an array if there's no prefix. - // This produces the same layout, but using a simpler type. + // Simplify to an array if there is no prefix. if self.prefix.is_empty() { - // We can't do this if is_consecutive is set and the unit would get - // split on the target. Currently, this is only relevant for i128 - // registers. - if rest_count == 1 && (!self.rest.is_consecutive || self.rest.unit != Reg::i128()) { + // Pass `[1 x unit]` as just `unit`. + // + // On little-endian targets this produces a compatible layout, but using a simpler type. + // But on big-endian targets (e.g. aarch64_be or powerpc64) the value may be passed in + // different halves of the register-sized slot, so unit and [1 x unit] are incompatible. + // + // Setting `is_consecutive` forces use of an array. + if rest_count == 1 && !self.rest.is_consecutive { return rest_ll_unit; } diff --git a/compiler/rustc_target/src/callconv/powerpc64.rs b/compiler/rustc_target/src/callconv/powerpc64.rs index ca79a52d2874f..daaaa00572e61 100644 --- a/compiler/rustc_target/src/callconv/powerpc64.rs +++ b/compiler/rustc_target/src/callconv/powerpc64.rs @@ -75,7 +75,7 @@ where RegKind::Vector { .. } => unit.size.bits() == 128, }; - valid_unit.then_some(Uniform::consecutive(unit, arg.layout.size)) + valid_unit.then_some(Uniform::new(unit, arg.layout.size)) }) } diff --git a/tests/assembly-llvm/singleton-hfa.rs b/tests/assembly-llvm/singleton-hfa.rs new file mode 100644 index 0000000000000..218e9e284d728 --- /dev/null +++ b/tests/assembly-llvm/singleton-hfa.rs @@ -0,0 +1,99 @@ +//@ add-minicore +//@ revisions: powerpc64 aarch64_be +//@ assembly-output: emit-asm +//@ compile-flags: -Copt-level=3 -Z merge-functions=disabled +// +//@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu +//@[powerpc64] needs-llvm-components: powerpc +// +//@[aarch64_be] compile-flags: --target aarch64_be-unknown-linux-gnu +//@[aarch64_be] needs-llvm-components: aarch64 +#![feature(no_core)] +#![no_core] +#![crate_type = "lib"] + +// On big-endian targets, the singleton HFA and the unit are not ABI-compatible, so it matters +// which is used. Test that: +// +// - on powerpc64 SingletonStruct is passed like f32 +// - on aarch64_be SingletonStruct is passed like [f32; 1] +// +// The test start with 13 float arguments, to exhaust float registers and force the last argument +// onto the stack (technically we only need 8 float arguments on aarch64_be but a couple more +// does not matter). + +extern crate minicore; +use minicore::*; + +#[repr(C)] +struct SingletonStruct(T); + +#[no_mangle] +extern "C" fn singleton_struct_float( + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + x: SingletonStruct, +) -> f32 { + // powerpc64: lfs 1, 156(1) + // aarch64_be: ldr s0, [sp, #40] + x.0 +} + +// CHECK-LABEL: singleton_array_float: +#[no_mangle] +extern "C" fn singleton_array_float( + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + x: [f32; 1], +) -> f32 { + // powerpc64: lfs 1, 156(1) + // aarch64_be: ldr s0, [sp, #40] + match x { + [a] => a, + } +} + +// CHECK-LABEL: float: +#[no_mangle] +extern "C" fn float( + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + _: f64, + x: f32, +) -> f32 { + // powerpc64: lfs 1, 156(1) + // aarch64_be: ldr s0, [sp, #44] + x +} diff --git a/tests/codegen-llvm/aarch64-abi/homogeneous-aggregate.rs b/tests/codegen-llvm/aarch64-abi/homogeneous-aggregate.rs index 3d5ec10f2b2f7..ecb318ddbb794 100644 --- a/tests/codegen-llvm/aarch64-abi/homogeneous-aggregate.rs +++ b/tests/codegen-llvm/aarch64-abi/homogeneous-aggregate.rs @@ -1,8 +1,9 @@ //@ add-minicore //@ compile-flags: -Cno-prepopulate-passes -Copt-level=0 // -//@ revisions: linux win +//@ revisions: linux linux-be win //@[linux] compile-flags: --target aarch64-unknown-linux-gnu +//@[linux-be] compile-flags: --target aarch64_be-unknown-linux-gnu //@[win] compile-flags: --target aarch64-pc-windows-msvc // //@ needs-llvm-components: aarch64 @@ -17,17 +18,29 @@ extern crate minicore; use minicore::simd::*; use minicore::*; +// A singleton homogeneous float aggregate. +#[repr(C)] +pub struct Hfa1 { + pub a: f32, +} + +// NOTE: on aarch64_be `[1 x float]` is not abi-compatible with a bare `float`. +// CHECK: define void @test_hfa1([1 x float] %0) +#[unsafe(no_mangle)] +pub extern "C" fn test_hfa1(a: Hfa1) { + hint::black_box(a); +} + // A homogeneous float aggregate. #[repr(C)] -pub struct Hfa { +pub struct Hfa2 { pub a: f32, pub b: f32, } -impl Copy for Hfa {} -// CHECK: define void @test_hfa([2 x float] %0) +// CHECK: define void @test_hfa2([2 x float] %0) #[unsafe(no_mangle)] -pub extern "C" fn test_hfa(a: Hfa) { +pub extern "C" fn test_hfa2(a: Hfa2) { hint::black_box(a); } diff --git a/tests/codegen-llvm/arm-abi/homogeneous-aggregate.rs b/tests/codegen-llvm/arm-abi/homogeneous-aggregate.rs index 272eb419494b0..8c058a95b322a 100644 --- a/tests/codegen-llvm/arm-abi/homogeneous-aggregate.rs +++ b/tests/codegen-llvm/arm-abi/homogeneous-aggregate.rs @@ -19,37 +19,69 @@ extern crate minicore; use minicore::simd::*; use minicore::*; +// A singleton homogeneous float aggregate. +#[repr(C)] +pub struct Hfa1 { + pub a: f32, +} + +// linux: define void @test_hfa1([1 x float] %0) +// eabi: define dso_local void @test_hfa1([1 x i32] %0) +// watchos: define void @test_hfa1([1 x float] %0) +#[unsafe(no_mangle)] +pub extern "C" fn test_hfa1(a: Hfa1) { + hint::black_box(a); +} + +// linux: define [1 x float] @ret_hfa1([1 x float] %0) +// eabi: define dso_local i32 @ret_hfa1([1 x i32] %0) +// watchos: define [1 x float] @ret_hfa1([1 x float] %0) +#[unsafe(no_mangle)] +pub extern "C" fn ret_hfa1(a: Hfa1) -> Hfa1 { + a +} + +// When we ask for `extern "aapcs"` specifically, GPRs are used, except on watchOS. +// +// linux: define arm_aapcscc void @test_hfa1_aapcs([1 x i32] %0) +// eabi: define dso_local arm_aapcscc void @test_hfa1_aapcs([1 x i32] %0) +// watchos: define arm_aapcscc void @test_hfa1_aapcs([1 x float] %0) +#[unsafe(no_mangle)] +pub extern "aapcs" fn test_hfa1_aapcs(a: Hfa1) { + hint::black_box(a); +} + // A homogeneous float aggregate, which a hard-float ABI passes in VFP registers. #[repr(C)] -pub struct Hfa { +pub struct Hfa2 { pub a: f32, pub b: f32, } -impl Copy for Hfa {} +impl Copy for Hfa2 {} -// linux: define void @test_hfa([2 x float] %0) -// eabi: define dso_local void @test_hfa([2 x i32] %0) -// watchos: define void @test_hfa([2 x float] %0) +// linux: define void @test_hfa2([2 x float] %0) +// eabi: define dso_local void @test_hfa2([2 x i32] %0) +// watchos: define void @test_hfa2([2 x float] %0) #[unsafe(no_mangle)] -pub extern "C" fn test_hfa(a: Hfa) { +pub extern "C" fn test_hfa2(a: Hfa2) { hint::black_box(a); } -// linux: define [2 x float] @ret_hfa([2 x float] %0) -// eabi: define dso_local void @ret_hfa(ptr sret([8 x i8]) align 4 %_0, [2 x i32] %0) -// watchos: define [2 x float] @ret_hfa([2 x float] %0) +// linux: define [2 x float] @ret_hfa2([2 x float] %0) +// eabi: define dso_local void @ret_hfa2(ptr sret([8 x i8]) align 4 %_0, [2 x i32] %0) +// watchos: define [2 x float] @ret_hfa2([2 x float] %0) #[unsafe(no_mangle)] -pub extern "C" fn ret_hfa(a: Hfa) -> Hfa { +pub extern "C" fn ret_hfa2(a: Hfa2) -> Hfa2 { a } // When we ask for `extern "aapcs"` specifically, GPRs are used, except on watchOS. // -// linux: define arm_aapcscc void @test_hfa_aapcs([2 x i32] %0) -// eabi: define dso_local arm_aapcscc void @test_hfa_aapcs([2 x i32] %0) -// watchos: define arm_aapcscc void @test_hfa_aapcs([2 x float] %0) +// linux: define arm_aapcscc void @test_hfa2_aapcs([2 x i32] %0) +// eabi: define dso_local arm_aapcscc void @test_hfa2_aapcs([2 x i32] %0) +// watchos: define arm_aapcscc void @test_hfa2_aapcs([2 x float] %0) #[unsafe(no_mangle)] -pub extern "aapcs" fn test_hfa_aapcs(a: Hfa) { +pub extern "aapcs" fn test_hfa2_aapcs(a: Hfa2) { hint::black_box(a); } @@ -216,7 +248,7 @@ extern "C" { // linux: declare void @test_hfa_variadic_c([2 x i32], ...) // eabi: declare dso_local void @test_hfa_variadic_c([2 x i32], ...) // watchos: declare void @test_hfa_variadic_c([2 x float], ...) - fn test_hfa_variadic_c(_: Hfa, ...); + fn test_hfa_variadic_c(_: Hfa2, ...); // linux: declare void @test_ints_6_variadic_c([6 x i32], ...) // eabi: declare dso_local void @test_ints_6_variadic_c([6 x i32], ...) @@ -228,7 +260,7 @@ extern "aapcs" { // linux: declare arm_aapcscc void @test_hfa_variadic_aapcs([2 x i32], ...) // eabi: declare dso_local arm_aapcscc void @test_hfa_variadic_aapcs([2 x i32], ...) // watchos: declare arm_aapcscc void @test_hfa_variadic_aapcs([2 x float], ...) - fn test_hfa_variadic_aapcs(_: Hfa, ...); + fn test_hfa_variadic_aapcs(_: Hfa2, ...); // linux: declare arm_aapcscc void @test_ints_6_variadic_aapcs([6 x i32], ...) // eabi: declare dso_local arm_aapcscc void @test_ints_6_variadic_aapcs([6 x i32], ...) @@ -236,7 +268,7 @@ extern "aapcs" { fn test_ints_6_variadic_aapcs(_: Ints6, ...); } -pub unsafe fn call_variadics(a: Hfa, b: Ints6) { +pub unsafe fn call_variadics(a: Hfa2, b: Ints6) { test_hfa_variadic_c(a); test_ints_6_variadic_c(b); diff --git a/tests/codegen-llvm/arm-abi/struct-alignment.rs b/tests/codegen-llvm/arm-abi/struct-alignment.rs index 1921b8a9ddda9..42f8bb318e85a 100644 --- a/tests/codegen-llvm/arm-abi/struct-alignment.rs +++ b/tests/codegen-llvm/arm-abi/struct-alignment.rs @@ -36,9 +36,9 @@ pub struct HasU64 { pub a: u64, } -// linux: define void @test_has_u64(i64 %0) -// eabi: define dso_local void @test_has_u64(i64 %0) -// watchos: define void @test_has_u64(i64 %0) +// linux: define void @test_has_u64([1 x i64] %0) +// eabi: define dso_local void @test_has_u64([1 x i64] %0) +// watchos: define void @test_has_u64([1 x i64] %0) #[unsafe(no_mangle)] pub extern "C" fn test_has_u64(a: HasU64) { hint::black_box(a); @@ -62,7 +62,7 @@ pub struct Transparent8 { // // linux: define void @test_8([2 x i32] %0, [2 x i32] %1) // eabi: define dso_local void @test_8([2 x i32] %0, [2 x i32] %1) -// watchos: define void @test_8(i64 %0, i64 %1) +// watchos: define void @test_8([1 x i64] %0, [1 x i64] %1) #[unsafe(no_mangle)] pub extern "C" fn test_8(a: Align8, b: Transparent8) { hint::black_box(a); diff --git a/tests/codegen-llvm/complex-abi.rs b/tests/codegen-llvm/complex-abi.rs index c9e59f6f7eaa7..45224bd9316fe 100644 --- a/tests/codegen-llvm/complex-abi.rs +++ b/tests/codegen-llvm/complex-abi.rs @@ -230,7 +230,7 @@ pub extern "C" fn cplx_i8(x: Complex) -> Complex { // AARCH64_MSVC: define{{.*}} i64 @cplx_i8(i64{{.*}}) // AIX: define{{.*}} { i8, i8 } @cplx_i8({ i8, i8 } %0) // ARM64EC: define{{.*}} i64 @cplx_i8(i64{{.*}}) - // ARM: define{{.*}} i32 @cplx_i8(i32{{.*}}) + // ARM: define{{.*}} i32 @cplx_i8([1 x i32]{{.*}}) // BPF: define{{.*}} void @cplx_i8(ptr {{.*}} sret({ i8, i8 }) {{.*}}, i16 {{.*}}) // CSKY: define{{.*}} i32 @cplx_i8(i32{{.*}}) // I686: define{{.*}} i16 @cplx_i8(ptr {{.*}} byval([2 x i8]) {{.*}}) @@ -265,7 +265,7 @@ pub extern "C" fn cplx_i16(x: Complex) -> Complex { // AARCH64_MSVC: define{{.*}} i64 @cplx_i16(i64{{.*}}) // AIX: define{{.*}} { i16, i16 } @cplx_i16(i16 {{.*}}, i16 {{.*}}) // ARM64EC: define{{.*}} i64 @cplx_i16(i64{{.*}}) - // ARM: define{{.*}} i32 @cplx_i16(i32{{.*}}) + // ARM: define{{.*}} i32 @cplx_i16([1 x i32]{{.*}}) // BPF: define{{.*}} void @cplx_i16(ptr {{.*}} sret({ i16, i16 }) {{.*}}, i32 {{.*}}) // CSKY: define{{.*}} i32 @cplx_i16(i32 {{.*}}) // I686: define{{.*}} i32 @cplx_i16(ptr {{.*}} byval([4 x i8]) {{.*}}) diff --git a/tests/codegen-llvm/powerpc64-abi/homogeneous-aggregate.rs b/tests/codegen-llvm/powerpc64-abi/homogeneous-aggregate.rs index 58dd9c837603f..a85901cf55bfd 100644 --- a/tests/codegen-llvm/powerpc64-abi/homogeneous-aggregate.rs +++ b/tests/codegen-llvm/powerpc64-abi/homogeneous-aggregate.rs @@ -18,19 +18,32 @@ extern crate minicore; use minicore::simd::*; use minicore::*; +// A singleton homogeneous float aggregate. +#[repr(C)] +pub struct Hfa1 { + pub a: f32, +} + +// NOTE: on powerpc64 `[1 x float]` is not abi-compatible with a bare `float`. +// CHECK: define void @test_hfa1(float %0) +#[unsafe(no_mangle)] +pub extern "C" fn test_hfa1(a: Hfa1) { + hint::black_box(a); +} + // A homogeneous float aggregate. #[repr(C)] -pub struct Hfa { +pub struct Hfa2 { pub a: f32, pub b: f32, } -impl Copy for Hfa {} +impl Copy for Hfa2 {} -// ppc64: define void @test_hfa(i64 %0) -// ppc64_vsx: define void @test_hfa(i64 %0) -// ppc64le: define void @test_hfa([2 x float] %0) +// ppc64: define void @test_hfa2(i64 %0) +// ppc64_vsx: define void @test_hfa2(i64 %0) +// ppc64le: define void @test_hfa2([2 x float] %0) #[unsafe(no_mangle)] -pub extern "C" fn test_hfa(a: Hfa) { +pub extern "C" fn test_hfa2(a: Hfa2) { hint::black_box(a); }