[clang] f137bc0 - [clang][X86] bring `regparm` in line with GCC (#227130)

via cfe-commits cfe-commits at lists.llvm.org
Sun Oct 4 05:30:32 PDT 2026


Author: Folkert de Vries
Date: 2026-10-04T12:30:25Z
New Revision: f137bc0f5cad3bc498bbee0469223a815031846a

URL: https://github.com/llvm/llvm-project/commit/f137bc0f5cad3bc498bbee0469223a815031846a
DIFF: https://github.com/llvm/llvm-project/commit/f137bc0f5cad3bc498bbee0469223a815031846a.diff

LOG: [clang][X86] bring `regparm` in line with GCC (#227130)

Removes some divergences between GCC and Clang using `regparm`. In
particular

- All floats count as floats (previously `f16`, `f16b`, `long double`
and `f128` did not)
- Complex numbers are always passed via the stack
- Unions are always passed like integers
- Some changes to how wrapping structs (with a single non-ZST field) are
handled

I've tested this empirically (with abi-cafe)

---------

Co-authored-by: Reid Kleckner <rkleckner at nvidia.com>

Added: 
    

Modified: 
    clang/docs/ReleaseNotes.md
    clang/include/clang/Basic/ABIVersions.def
    clang/lib/CodeGen/Targets/X86.cpp
    clang/test/CodeGen/regparm-struct.c

Removed: 
    


################################################################################
diff  --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index bf194e87f76a6..7ec126a065ae5 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -105,6 +105,13 @@ features cannot lower the translation-unit ABI level;
   This also fixes a crash when such a struct was passed or returned.
   `-fclang-abi-compat=23` restores the previous behavior. (#GH202205)
 
+- On 32-bit x86, Clang now matches GCC when classifying arguments for the
+  `regparm` calling convention. Floating-point types other than `float` and
+  `double` no longer consume general-purpose register slots, complex values
+  are passed on the stack without consuming register slots, and unions are no
+  longer classified according to a single member. `-fclang-abi-compat=23`
+  restores the previous behavior. (#GH227130)
+
 - Clang now considers matrix types in its isHomogeneousAggregate() handling,
   which can lead to 
diff erences in how structures containing matrix types are
   classified for ABI purposes. The previous exclusion of matrix types appears

diff  --git a/clang/include/clang/Basic/ABIVersions.def b/clang/include/clang/Basic/ABIVersions.def
index 3c434da91bfab..399be760fc7a3 100644
--- a/clang/include/clang/Basic/ABIVersions.def
+++ b/clang/include/clang/Basic/ABIVersions.def
@@ -167,6 +167,8 @@ ABI_VER_MAJOR(22)
 ///     faithfully reproduces Clang 23, including its crash on aggregates such
 ///     as a run of `__int128` bit-fields, where skipping the unnamed field
 ///     leaves part of a wider access unit unclassified.)
+///   - On 32-bit x86, use the legacy `regparm` classification for floating-point
+///     types other than `float` and `double`, complex types, and unions.
 ABI_VER_MAJOR(23)
 
 /// Conform to the underlying platform's C and C++ ABIs as closely as we can.

diff  --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 21f9929cfa882..c0947c481d2b0 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -101,10 +101,7 @@ struct CCState {
 
 /// X86_32ABIInfo - The X86-32 ABI information.
 class X86_32ABIInfo : public ABIInfo {
-  enum Class {
-    Integer,
-    Float
-  };
+  enum Class { Integer, Float, AlwaysStack };
 
   static const unsigned MinABIStackAlignInBytes = 4;
 
@@ -634,25 +631,111 @@ ABIArgInfo X86_32ABIInfo::getIndirectResult(QualType Ty, bool ByVal,
       Realign);
 }
 
+/// Like isSingleElementStruct, but only looks through one level of records.
+/// This mirrors how GCC gives a record the mode of its only field.
+static const Type *getSingleFieldType(QualType T, ASTContext &Context) {
+  const auto *RD = T->getAsRecordDecl();
+  if (!RD)
+    return nullptr;
+
+  if (RD->hasFlexibleArrayMember())
+    return nullptr;
+
+  const Type *Found = nullptr;
+
+  // If this is a C++ record, check the bases first.
+  if (const CXXRecordDecl *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
+    for (const auto &I : CXXRD->bases()) {
+      // Ignore empty records.
+      if (isEmptyRecord(Context, I.getType(), true))
+        continue;
+
+      // If we already found an element then this isn't a single-element struct.
+      if (Found)
+        return nullptr;
+
+      Found = I.getType().getTypePtr();
+    }
+  }
+
+  // Check for single element.
+  for (const auto *FD : RD->fields()) {
+    QualType FT = FD->getType();
+
+    // Ignore empty fields.
+    if (isEmptyField(Context, FD, true))
+      continue;
+
+    // If we already found an element then this isn't a single-element
+    // struct.
+    if (Found)
+      return nullptr;
+
+    // Treat single element arrays as the element.
+    while (const ConstantArrayType *AT = Context.getAsConstantArrayType(FT)) {
+      if (AT->getZExtSize() != 1)
+        break;
+      FT = AT->getElementType();
+    }
+
+    Found = FT.getTypePtr();
+  }
+
+  // We don't consider a struct a single-element struct if it has
+  // padding beyond the element type.
+  if (Found && Context.getTypeSize(Found) != Context.getTypeSize(T))
+    return nullptr;
+
+  return Found;
+}
+
 X86_32ABIInfo::Class X86_32ABIInfo::classify(QualType Ty) const {
-  const Type *T = isSingleElementStruct(Ty, getContext());
-  if (!T)
-    T = Ty.getTypePtr();
+  if (getContext().getLangOpts().isCompatibleWith(
+          LangOptions::ClangABI::Ver23)) {
+    const Type *T = isSingleElementStruct(Ty, getContext());
+    if (!T)
+      T = Ty.getTypePtr();
+
+    if (const BuiltinType *BT = T->getAs<BuiltinType>()) {
+      BuiltinType::Kind K = BT->getKind();
+      if (K == BuiltinType::Float || K == BuiltinType::Double)
+        return Float;
+    }
+    return Integer;
+  }
 
-  if (const BuiltinType *BT = T->getAs<BuiltinType>()) {
-    BuiltinType::Kind K = BT->getKind();
-    if (K == BuiltinType::Float || K == BuiltinType::Double)
-      return Float;
+  while (const RecordDecl *RD = Ty->getAsRecordDecl()) {
+    // Unions are always passed as integers.
+    if (RD->isUnion())
+      return Integer;
+
+    const Type *FT = getSingleFieldType(Ty, getContext());
+
+    // Pick integer for zero-sized types. They will be ignored down the line.
+    if (!FT)
+      return Integer;
+
+    Ty = QualType(FT, 0);
   }
+
+  // All complex values are passed via the stack.
+  if (Ty->isAnyComplexType())
+    return AlwaysStack;
+
+  // Types like bfloat16 or __float128 are all passed like floats.
+  if (const BuiltinType *BT = Ty->getAs<BuiltinType>())
+    if (BT->isFloatingPoint())
+      return Float;
+
   return Integer;
 }
 
 bool X86_32ABIInfo::updateFreeRegs(QualType Ty, CCState &State) const {
-  if (!IsSoftFloatABI) {
-    Class C = classify(Ty);
-    if (C == Float)
-      return false;
-  }
+  Class C = classify(Ty);
+  if (C == AlwaysStack && !IsMCUABI)
+    return false;
+  if (C == Float && !IsSoftFloatABI)
+    return false;
 
   unsigned Size = getContext().getTypeSize(Ty);
   unsigned SizeInRegs = (Size + 31) / 32;

diff  --git a/clang/test/CodeGen/regparm-struct.c b/clang/test/CodeGen/regparm-struct.c
index a533f6cedbba2..5dc2d96c36bfe 100644
--- a/clang/test/CodeGen/regparm-struct.c
+++ b/clang/test/CodeGen/regparm-struct.c
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -triple i386-unknown-unknown %s -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 -triple i386-unknown-linux-gnu -target-feature +sse2 %s -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK,LATEST
+// RUN: %clang_cc1 -triple i386-unknown-linux-gnu -target-feature +sse2 -fclang-abi-compat=23 %s -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK,COMPAT23
 
 __attribute__((regparm(3))) void f1(int a, int b, int c, int d);
 // CHECK: declare void @f1(i32 inreg noundef, i32 inreg noundef, i32 inreg noundef, i32 noundef)
@@ -175,3 +176,89 @@ void g18(void) {
   struct s13 x = {{41}};
   f18(x, 42, 43, 44);
 }
+
+__attribute__((regparm(3))) void pass_f16(_Float16 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_f16(half noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_f16(half inreg noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+
+__attribute__((regparm(3))) void pass_bf16(__bf16 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_bf16(bfloat noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_bf16(bfloat inreg noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+
+__attribute__((regparm(3))) void pass_f80(long double a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_f80(x86_fp80 noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_f80(x86_fp80 inreg noundef %a, i32 noundef %b, i32 noundef %c, i32 noundef %d)
+
+__attribute__((regparm(3))) void pass_f128(__float128 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_f128(fp128 noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_f128(fp128 noundef %a, i32 noundef %b, i32 noundef %c, i32 noundef %d)
+
+struct s14 {
+  _Float16 x1;
+};
+__attribute__((regparm(3))) void pass_wrapped_f16(struct s14 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_wrapped_f16(ptr noundef byval(%struct.s14) align 4 %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_wrapped_f16(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+
+struct s15 {
+  __float128 x1;
+};
+__attribute__((regparm(3))) void pass_wrapped_f128(struct s15 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_wrapped_f128(ptr noundef byval(%struct.s15) align 4 {{%[^,]*}}, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_wrapped_f128(ptr noundef byval(%struct.s15) align 4 {{%[^,]*}}, i32 noundef %b, i32 noundef %c, i32 noundef %d)
+
+__attribute__((regparm(3))) void pass_complex_float(_Complex float a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_complex_float(ptr noundef byval({ float, float }) align 4 %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_complex_float(i32 inreg noundef %a.coerce0, i32 inreg noundef %a.coerce1, i32 inreg noundef %b, i32 noundef %c, i32 noundef %d)
+
+__attribute__((regparm(3))) void pass_complex_int(int a, _Complex int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_complex_int(i32 inreg noundef %a, ptr noundef byval({ i32, i32 }) align 4 %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_complex_int(i32 inreg noundef %a, i32 inreg noundef %b.coerce0, i32 inreg noundef %b.coerce1, i32 noundef %c, i32 noundef %d)
+
+struct s16 {
+  _Complex float x1;
+};
+__attribute__((regparm(3))) void pass_wrapped_complex_float(struct s16 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_wrapped_complex_float(float %a.0, float %a.1, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_wrapped_complex_float(i32 inreg %a.coerce0, i32 inreg %a.coerce1, i32 inreg noundef %b, i32 noundef %c, i32 noundef %d)
+
+struct s17 {
+  _Complex int x1[1];
+};
+__attribute__((regparm(3))) void pass_wrapped_complex_int(struct s17 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_wrapped_complex_int(ptr noundef byval(%struct.s17) align 4 %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_wrapped_complex_int(i32 inreg %a.coerce0, i32 inreg %a.coerce1, i32 inreg noundef %b, i32 noundef %c, i32 noundef %d)
+
+struct s18 {
+  float x1[1];
+};
+__attribute__((regparm(3))) void pass_struct_singleton_array_float(struct s18 a, int b, int c, int d) {}
+// CHECK-LABEL: define{{.*}} void @pass_struct_singleton_array_float(ptr noundef byval(%struct.s18) align 4 %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+// Unions are passed like integers.
+union u1 {
+  float x1;
+};
+__attribute__((regparm(3))) void pass_union_float(union u1 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_union_float(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_union_float(float %a.0, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+union u2 {
+  struct s5 x1;
+};
+__attribute__((regparm(3))) void pass_union_struct_float(union u2 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_union_struct_float(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_union_struct_float(ptr noundef byval(%union.u2) align 4 %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+union u3 {
+  _Complex float x1;
+};
+__attribute__((regparm(3))) void pass_union_complex_float(union u3 a, int b, int c, int d) {}
+// CHECK-LABEL: define{{.*}} void @pass_union_complex_float(i32 inreg %a.coerce0, i32 inreg %a.coerce1, i32 inreg noundef %b, i32 noundef %c, i32 noundef %d)
+
+struct s19 {
+  union u1 x1;
+};
+__attribute__((regparm(3))) void pass_nested_union_float(struct s19 a, int b, int c, int d) {}
+// LATEST-LABEL: define{{.*}} void @pass_nested_union_float(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+// COMPAT23-LABEL: define{{.*}} void @pass_nested_union_float(ptr noundef byval(%struct.s19) align 4 %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)


        


More information about the cfe-commits mailing list