rust-lang/rust · #161987

fix `is_single_fp_element` for `s390x` and `x86`

folkertdev · merged Sep 18, 20268 files · 417 + / 25
compiler/rustc_abi/src/callconv/reg.rs1 + / 0
@@ -38,6 +38,7 @@ impl Reg {     reg_ctor!(i64, Integer, 64);     reg_ctor!(i128, Integer, 128); +    reg_ctor!(f16, Float, 16);     reg_ctor!(f32, Float, 32);     reg_ctor!(f64, Float, 64);     reg_ctor!(f128, Float, 128);
compiler/rustc_abi/src/layout/ty.rs23 + / 20
@@ -155,26 +155,6 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {         Ty::ty_and_layout_pointee_info_at(self, cx, offset)     } -    pub fn is_single_fp_element<C>(self, cx: &C) -> bool-    where-        Ty: TyAbiInterface<'a, C>,-        C: HasDataLayout,-    {-        match self.backend_repr {-            BackendRepr::Scalar(scalar) => {-                matches!(scalar.primitive(), Primitive::Float(Float::F32 | Float::F64))-            }-            BackendRepr::Memory { .. } => {-                if self.fields.count() == 1 && self.fields.offset(0).bytes() == 0 {-                    self.field(cx, 0).is_single_fp_element(cx)-                } else {-                    false-                }-            }-            _ => false,-        }-    }-     pub fn is_single_vector_element<C>(self, cx: &C, expected_size: Size) -> bool     where         Ty: TyAbiInterface<'a, C>,@@ -301,6 +281,29 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {         found     } +    /// Finds the one field that is not a ZST.+    /// Returns `None` if there are multiple non-ZST fields or only ZST-fields.+    ///+    /// Note that this function checks for ZSTs, not just 1-ZSTs.+    pub fn non_zst_field_ignore_alignment<C>(&self, cx: &C) -> Option<(FieldIdx, Self)>+    where+        Ty: TyAbiInterface<'a, C> + Copy,+    {+        let mut found = None;+        for field_idx in 0..self.fields.count() {+            let field = self.field(cx, field_idx);+            if field.is_zst() {+                continue;+            }+            if found.is_some() {+                // More than one non-ZST field.+                return None;+            }+            found = Some((FieldIdx::from_usize(field_idx), field));+        }+        found+    }+     /// If this type should match the ABI of the C `_Complex` type, returns the primitive that is     /// used for its components.     ///
compiler/rustc_codegen_llvm/src/va_arg.rs13 + / 1
@@ -474,7 +474,19 @@ fn emit_s390x_va_arg<'ll, 'tcx>(     let padded_size = 8;     let padding = padded_size - unpadded_size; -    let gpr_type = indirect || !layout.is_single_fp_element(bx.cx);+    // NOTE: if we ever allow aggregate types, this should handle structs with a single fp element.+    let is_single_fp_element = |layout: TyAndLayout<'_>| -> bool {+        match layout.layout.backend_repr() {+            BackendRepr::Scalar(scalar) => match scalar.primitive() {+                Primitive::Float(Float::F16 | Float::F32 | Float::F64) => true,+                Primitive::Float(Float::F128) => false,+                Primitive::Int(_, _) | Primitive::Pointer(_) => false,+            },+            _ => false,+        }+    };++    let gpr_type = indirect || !is_single_fp_element(layout);     let (max_regs, reg_count, reg_save_index, reg_padding) =         if gpr_type { (5, gpr, 2, padding) } else { (4, fpr, 16, 0) }; 
compiler/rustc_target/src/callconv/s390x.rs46 + / 2
@@ -1,11 +1,44 @@ // Reference: ELF Application Binary Interface s390x Supplement // https://github.com/IBM/s390x-abi -use rustc_abi::{BackendRepr, HasDataLayout, TyAbiInterface};+use rustc_abi::{BackendRepr, FieldsShape, HasDataLayout, Primitive, TyAbiInterface, TyAndLayout};  use crate::callconv::{ArgAbi, FnAbi, Reg}; use crate::spec::{Env, HasTargetSpec, Os}; +/// Is this a struct with a single float field?+fn is_single_fp_element<'a, Ty, C>(mut layout: TyAndLayout<'a, Ty>, cx: &C) -> bool+where+    Ty: TyAbiInterface<'a, C> + Copy,+    C: HasDataLayout,+{+    // Contrary to X86, trailing padding is allowed on s390x.++    loop {+        layout = layout.peel_transparent_wrappers(cx);++        return match layout.backend_repr {+            BackendRepr::Scalar(scalar) => match scalar.primitive() {+                Primitive::Float(_) => true,+                Primitive::Int(_, _) | Primitive::Pointer(_) => false,+            },+            BackendRepr::Memory { .. } => {+                // A single-element array or union does not qualify.+                if let FieldsShape::Arbitrary { .. } = layout.fields+                    && layout.fields.count() == 1+                    && layout.fields.offset(0).bytes() == 0+                {+                    layout = layout.field(cx, 0);+                    continue;+                } else {+                    false+                }+            }+            _ => false,+        };+    }+}+ fn classify_ret<Ty>(ret: &mut ArgAbi<'_, Ty>) {     let size = ret.layout.size;     if size.bits() <= 128 && matches!(ret.layout.backend_repr, BackendRepr::SimdVector { .. }) {@@ -65,8 +98,19 @@ where         return;     } -    if arg.layout.is_single_fp_element(cx) {+    if is_single_fp_element(arg.layout, cx) {+        // Match GCC and Clang by explicitly passing padding, even though their behavior violates+        // (our reading of) the specification, which says that:+        //+        // > Structures equivalent to a floating point type are passed in floating point registers.+        // > A structure is equivalent to a floating point type if and only if it has exactly one+        // > member, which is either of floating point type of itself a structure equivalent to a+        // > floating point type.+        //+        // When the alignment is higher than 8, we pass the argument indirectly, which violates+        // the specification but is consistent with GCC and Clang.         match size.bytes() {+            2 => arg.cast_to(Reg::f16()),             4 => arg.cast_to(Reg::f32()),             8 => arg.cast_to(Reg::f64()),             _ => arg.make_indirect(),
compiler/rustc_target/src/callconv/x86.rs34 + / 1
@@ -5,6 +5,38 @@ use rustc_abi::{ use crate::callconv::{ArgAttribute, FnAbi, PassMode, TyAbiInterface}; use crate::spec::{HasTargetSpec, RustcAbi}; +/// Is this a struct with a single float field?+fn is_single_fp_element<'a, Ty, C>(mut layout: TyAndLayout<'a, Ty>, cx: &C) -> bool+where+    Ty: TyAbiInterface<'a, C> + Copy,+    C: HasDataLayout,+{+    // On X86 over-aligned structs are disqualified.+    let outer_size = layout.layout.size();++    loop {+        layout = layout.peel_transparent_wrappers(cx);++        return match layout.backend_repr {+            BackendRepr::Scalar(scalar) => match scalar.primitive() {+                Primitive::Float(float) => float.size() == outer_size,+                Primitive::Int(_, _) | Primitive::Pointer(_) => false,+            },+            BackendRepr::Memory { .. } => {+                // Structs, unions and arrays all qualify.+                if let Some((_idx, field)) = layout.non_zst_field_ignore_alignment(cx) {+                    // NOTE: alignment is not relevant here, checking for 1-ZST is incorrect.+                    layout = field;+                    continue;+                } else {+                    false+                }+            }+            _ => false,+        };+    }+}+ #[derive(PartialEq)] pub(crate) enum Flavor {     General,@@ -42,8 +74,9 @@ where             {                 // According to Clang, everyone but MSVC returns single-element                 // float aggregates directly in a floating-point register.-                if fn_abi.ret.layout.is_single_fp_element(cx) {+                if is_single_fp_element(fn_abi.ret.layout, cx) {                     match fn_abi.ret.layout.size.bytes() {+                        2 => fn_abi.ret.cast_to(Reg::f16()),                         4 => fn_abi.ret.cast_to(Reg::f32()),                         8 => fn_abi.ret.cast_to(Reg::f64()),                         _ => fn_abi.ret.make_indirect(),
tests/codegen-llvm/s390x-abi/single-fp-element.rsadded129 + / 0
@@ -0,0 +1,129 @@+//@ add-minicore+//@ needs-llvm-components: systemz+//@ compile-flags: --target=s390x-unknown-linux-gnu -Copt-level=3 -Zmerge-functions=disabled+#![crate_type = "lib"]+#![feature(no_core, f16, f128)]+#![no_core]++extern crate minicore;+use minicore::hint::black_box;+use minicore::*;++#[repr(C)]+struct Wrapper<T>(T);++// CHECK-LABEL: define void @plain_f16(half noundef %x)+#[unsafe(no_mangle)]+extern "C" fn plain_f16(x: f16) {+    black_box(x);+}++// CHECK-LABEL: define void @wrapped_f16(half %0)+#[unsafe(no_mangle)]+extern "C" fn wrapped_f16(x: Wrapper<f16>) {+    black_box(x);+}++// CHECK-LABEL: define void @plain_f32(float noundef %x)+#[unsafe(no_mangle)]+extern "C" fn plain_f32(x: f32) {+    black_box(x);+}++// CHECK-LABEL: define void @wrapped_f32(float %0)+#[unsafe(no_mangle)]+extern "C" fn wrapped_f32(x: Wrapper<f32>) {+    black_box(x);+}++// CHECK-LABEL: define void @plain_f64(double noundef %x)+#[unsafe(no_mangle)]+extern "C" fn plain_f64(x: f64) {+    black_box(x);+}++// CHECK-LABEL: define void @wrapped_f64(double %0)+#[unsafe(no_mangle)]+extern "C" fn wrapped_f64(x: Wrapper<f64>) {+    black_box(x);+}++// CHECK-LABEL: define void @plain_f128(ptr {{.*}}dereferenceable(16) %x)+#[unsafe(no_mangle)]+extern "C" fn plain_f128(x: f128) {+    black_box(x);+}++// CHECK-LABEL: define void @wrapped_f128(ptr {{.*}}dereferenceable(16) %x)+#[unsafe(no_mangle)]+extern "C" fn wrapped_f128(x: Wrapper<f128>) {+    black_box(x);+}++#[repr(transparent)]+struct Transparent<T>(T);++// CHECK-LABEL: define void @transparent_wrapped_f32(float %0)+#[unsafe(no_mangle)]+extern "C" fn transparent_wrapped_f32(x: Transparent<Wrapper<f32>>) {+    black_box(x);+}++// CHECK-LABEL: define void @transparent_transparent_wrapped_f32(float %0)+#[unsafe(no_mangle)]+extern "C" fn transparent_transparent_wrapped_f32(x: Transparent<Transparent<Wrapper<f32>>>) {+    black_box(x);+}++#[repr(C, align(8))]+struct Aligned8Wrapper<T>(T);++// CHECK-LABEL: define void @aligned_8_wrapped_f16(double %0)+#[unsafe(no_mangle)]+extern "C" fn aligned_8_wrapped_f16(x: Aligned8Wrapper<f16>) {+    black_box(x);+}++// CHECK-LABEL: define void @aligned_8_wrapped_f32(double %0)+#[unsafe(no_mangle)]+extern "C" fn aligned_8_wrapped_f32(x: Aligned8Wrapper<f32>) {+    black_box(x);+}++#[repr(C, align(16))]+struct Aligned16Wrapper<T>(T);++// CHECK-LABEL: define void @aligned_16_wrapped_f32(ptr {{.*}}dereferenceable(16)+#[unsafe(no_mangle)]+extern "C" fn aligned_16_wrapped_f32(x: Aligned16Wrapper<f32>) {+    black_box(x);+}++#[repr(C)]+union UnionWrapper<T: Copy> {+    a: T,+}++// A repr(C) union does not count.+//+// CHECK-LABEL: define void @union_wrapped_f32(i32 %0)+#[unsafe(no_mangle)]+extern "C" fn union_wrapped_f32(x: UnionWrapper<f32>) {+    black_box(x);+}++// But a repr(transparent) union does.+//+// CHECK-LABEL: define void @maybe_uninit_f32(float %x)+#[unsafe(no_mangle)]+extern "C" fn maybe_uninit_f32(x: MaybeUninit<f32>) {+    black_box(x);+}++// A single-element array also does not count.+//+// CHECK-LABEL: define void @array_f32(i32 %0)+#[unsafe(no_mangle)]+extern "C" fn array_f32(x: [f32; 1]) {+    black_box(x);+}
tests/codegen-llvm/x86-abi/single-fp-element.rsadded146 + / 0
@@ -0,0 +1,146 @@+//@ add-minicore+//@ needs-llvm-components: x86+//@ revisions: win linux+//@[win] compile-flags: --target i686-pc-windows-gnu+//@[linux] compile-flags: --target i686-unknown-linux-gnu -Zreg-struct-return=true+//@ compile-flags: -Copt-level=3 -Zmerge-functions=disabled+#![crate_type = "lib"]+#![feature(no_core, f16, f128)]+#![no_core]++extern crate minicore;+use minicore::hint::black_box;+use minicore::*;++#[repr(C)]+struct Wrapper<T>(T);++// CHECK-LABEL: define noundef half @plain_f16(+#[unsafe(no_mangle)]+extern "C" fn plain_f16(x: f16) -> f16 {+    x+}++// CHECK-LABEL: define half @wrapped_f16(+#[unsafe(no_mangle)]+extern "C" fn wrapped_f16(x: Wrapper<f16>) -> Wrapper<f16> {+    x+}++// CHECK-LABEL: define noundef float @plain_f32(+#[unsafe(no_mangle)]+extern "C" fn plain_f32(x: f32) -> f32 {+    x+}++// CHECK-LABEL: define float @wrapped_f32(+#[unsafe(no_mangle)]+extern "C" fn wrapped_f32(x: Wrapper<f32>) -> Wrapper<f32> {+    x+}++// CHECK-LABEL: define noundef double @plain_f64(+#[unsafe(no_mangle)]+extern "C" fn plain_f64(x: f64) -> f64 {+    x+}++// CHECK-LABEL: define double @wrapped_f64(+#[unsafe(no_mangle)]+extern "C" fn wrapped_f64(x: Wrapper<f64>) -> Wrapper<f64> {+    x+}++// CHECK-LABEL: define noundef fp128 @plain_f128(+#[unsafe(no_mangle)]+extern "C" fn plain_f128(x: f128) -> f128 {+    x+}++// CHECK-LABEL: define void @wrapped_f128(ptr {{.*}}sret([16 x i8])+#[unsafe(no_mangle)]+extern "C" fn wrapped_f128(x: Wrapper<f128>) -> Wrapper<f128> {+    x+}++#[repr(transparent)]+struct Transparent<T>(T);++// CHECK-LABEL: define float @transparent_wrapped_f32(+#[unsafe(no_mangle)]+extern "C" fn transparent_wrapped_f32(x: Transparent<Wrapper<f32>>) -> Transparent<Wrapper<f32>> {+    x+}++// CHECK-LABEL: define float @transparent_transparent_wrapped_f32(+#[unsafe(no_mangle)]+extern "C" fn transparent_transparent_wrapped_f32(+    x: Transparent<Transparent<Wrapper<f32>>>,+) -> Transparent<Transparent<Wrapper<f32>>> {+    x+}++#[repr(align(4))]+struct Empty {}++#[repr(C)]+struct Struct {+    f: f32,+    a: [i32; 0],+    b: Empty,+}++// One or more aligned ZSTs are fine and do not disqualify the type.+// CHECK-LABEL: define float @aligned_zst_f32(+#[unsafe(no_mangle)]+extern "C" fn aligned_zst_f32(x: Struct) -> Struct {+    x+}++#[repr(C, align(8))]+struct AlignedWrapper<T>(T);++// Over-aligning disqualifies the type.+//+// CHECK-LABEL: define i64 @aligned_wrapped_f16(+#[unsafe(no_mangle)]+extern "C" fn aligned_wrapped_f16(x: AlignedWrapper<f16>) -> AlignedWrapper<f16> {+    x+}++// Over-aligning disqualifies the type.+//+// CHECK-LABEL: define i64 @aligned_wrapped_f32(+#[unsafe(no_mangle)]+extern "C" fn aligned_wrapped_f32(x: AlignedWrapper<f32>) -> AlignedWrapper<f32> {+    x+}++#[repr(C)]+union UnionWrapper<T: Copy> {+    a: T,+}++// A repr(C) union does count.+//+// CHECK-LABEL: define float @union_wrapped_f32(+#[unsafe(no_mangle)]+extern "C" fn union_wrapped_f32(x: UnionWrapper<f32>) -> UnionWrapper<f32> {+    x+}++// A repr(transparent) union does too.+//+// CHECK-LABEL: define float @maybe_uninit_f32(+#[unsafe(no_mangle)]+extern "C" fn maybe_uninit_f32(x: MaybeUninit<f32>) -> MaybeUninit<f32> {+    x+}++// A single-element array also does count.+//+// CHECK-LABEL: define float @array_f32(+#[unsafe(no_mangle)]+extern "C" fn array_f32(x: [f32; 1]) -> [f32; 1] {+    x+}
tests/ui/abi/compatibility.rs25 + / 1
@@ -4,12 +4,24 @@ //@ revisions: i686 //@[i686] compile-flags: --target i686-unknown-linux-gnu //@[i686] needs-llvm-components: x86+//@ revisions: i686-reg-struct-return+//@[i686-reg-struct-return] compile-flags: --target i686-unknown-linux-gnu -Zreg-struct-return=true+//@[i686-reg-struct-return] needs-llvm-components: x86+//@ revisions: i686-win+//@[i686-win] compile-flags: --target i686-pc-windows-msvc+//@[i686-win] needs-llvm-components: x86+//@ revisions: i686-win-gnu+//@[i686-win-gnu] compile-flags: --target i686-pc-windows-gnu+//@[i686-win-gnu] needs-llvm-components: x86 //@ revisions: x86-64 //@[x86-64] compile-flags: --target x86_64-unknown-linux-gnu //@[x86-64] needs-llvm-components: x86 //@ revisions: x86-64-win //@[x86-64-win] compile-flags: --target x86_64-pc-windows-msvc //@[x86-64-win] needs-llvm-components: x86+//@ revisions: x86-64-win-gnu+//@[x86-64-win-gnu] compile-flags: --target x86_64-pc-windows-gnu+//@[x86-64-win-gnu] needs-llvm-components: x86 //@ revisions: arm //@[arm] compile-flags: --target arm-unknown-linux-gnueabi //@[arm] needs-llvm-components: arm@@ -19,6 +31,9 @@ //@ revisions: aarch64 //@[aarch64] compile-flags: --target aarch64-unknown-linux-gnu //@[aarch64] needs-llvm-components: aarch64+//@ revisions: aarch64-win+//@[aarch64-win] compile-flags: --target aarch64-pc-windows-msvc+//@[aarch64-win] needs-llvm-components: aarch64 //@ revisions: s390x //@[s390x] compile-flags: --target s390x-unknown-linux-gnu //@[s390x] needs-llvm-components: systemz@@ -171,6 +186,11 @@ enum Either2<T, U> {     Right(U, ()), } +#[repr(C)]+struct ReprC<T>(T);+#[repr(C)]+struct ReprC2<T, U>(T, U);+ #[repr(C)] enum ReprCEnum<T> {     Variant1,@@ -240,16 +260,20 @@ macro_rules! test_transparent { }  test_transparent!(simple, i32);+test_transparent!(float, f32); test_transparent!(reference, &'static i32); test_transparent!(zst, Zst); test_transparent!(unit, ()); test_transparent!(enum_, Option<i32>); test_transparent!(enum_niched, Option<&'static i32>); #[cfg(not(any(target_arch = "mips64")))]-mod tuples {+mod structs_and_tuples {     use super::*;+    test_transparent!(float_struct, ReprC<f32>);     // mixing in some floats since they often get special treatment     test_transparent!(pair, (i32, f32));+    // a homogeneous repr(C) struct+    test_transparent!(c_pair, ReprC2<f32, f32>);     // chosen to fit into 64bit     test_transparent!(triple, (i8, i16, f32));     // Pure-float types that are not ScalarPair seem to be tricky.