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

Folkert de Vries via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 29 02:02:23 PDT 2026


https://github.com/folkertdev updated https://github.com/llvm/llvm-project/pull/227130

>From 236a98c7a712d2a2ccf066791df177bff2f73e43 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert at folkertdev.nl>
Date: Mon, 28 Sep 2026 23:19:06 +0200
Subject: [PATCH 1/3] [clang][X86] Don't pass non-float/double floating-point
 types in regparm registers

---
 clang/lib/CodeGen/Targets/X86.cpp   |  6 ++----
 clang/test/CodeGen/regparm-struct.c | 26 +++++++++++++++++++++++++-
 2 files changed, 27 insertions(+), 5 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 21f9929cfa882..56cf23b1e9246 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -639,11 +639,9 @@ X86_32ABIInfo::Class X86_32ABIInfo::classify(QualType Ty) const {
   if (!T)
     T = Ty.getTypePtr();
 
-  if (const BuiltinType *BT = T->getAs<BuiltinType>()) {
-    BuiltinType::Kind K = BT->getKind();
-    if (K == BuiltinType::Float || K == BuiltinType::Double)
+  if (const BuiltinType *BT = T->getAs<BuiltinType>())
+    if (BT->isFloatingPoint())
       return Float;
-  }
   return Integer;
 }
 
diff --git a/clang/test/CodeGen/regparm-struct.c b/clang/test/CodeGen/regparm-struct.c
index a533f6cedbba2..54c0ded11e273 100644
--- a/clang/test/CodeGen/regparm-struct.c
+++ b/clang/test/CodeGen/regparm-struct.c
@@ -1,4 +1,4 @@
-// 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
 
 __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 +175,27 @@ 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) {}
+// CHECK-LABEL: define{{.*}} void @pass_f16(half noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+__attribute__((regparm(3))) void pass_bf16(__bf16 a, int b, int c, int d) {}
+// CHECK-LABEL: define{{.*}} void @pass_bf16(bfloat noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+__attribute__((regparm(3))) void pass_f80(long double a, int b, int c, int d) {}
+// CHECK-LABEL: define{{.*}} void @pass_f80(x86_fp80 noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+__attribute__((regparm(3))) void pass_f128(__float128 a, int b, int c, int d) {}
+// CHECK-LABEL: define{{.*}} void @pass_f128(fp128 noundef %a, i32 inreg noundef %b, i32 inreg noundef %c, i32 inreg noundef %d)
+
+struct s14 {
+  _Float16 x1;
+};
+__attribute__((regparm(3))) void pass_wrapped_f16(struct s14 a, int b, int c, int d) {}
+// CHECK-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)
+
+struct s15 {
+  __float128 x1;
+};
+__attribute__((regparm(3))) void pass_wrapped_f128(struct s15 a, int b, int c, int d) {}
+// CHECK-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)

>From f8de6469c414c4f3781b30f9b242705a21b084ac Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert at folkertdev.nl>
Date: Mon, 28 Sep 2026 23:19:13 +0200
Subject: [PATCH 2/3] [clang][X86] Don't pass _Complex values in regparm
 registers

---
 clang/lib/CodeGen/Targets/X86.cpp   | 93 +++++++++++++++++++++++++----
 clang/test/CodeGen/regparm-struct.c | 24 ++++++++
 2 files changed, 104 insertions(+), 13 deletions(-)

diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 56cf23b1e9246..57229e33d25f9 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,23 +631,93 @@ 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();
+  while (Ty->getAsRecordDecl()) {
+    const Type *FT = getSingleFieldType(Ty, getContext());
 
-  if (const BuiltinType *BT = T->getAs<BuiltinType>())
+    // 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 54c0ded11e273..6346cec9653fe 100644
--- a/clang/test/CodeGen/regparm-struct.c
+++ b/clang/test/CodeGen/regparm-struct.c
@@ -199,3 +199,27 @@ struct s15 {
 };
 __attribute__((regparm(3))) void pass_wrapped_f128(struct s15 a, int b, int c, int d) {}
 // CHECK-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)
+
+__attribute__((regparm(3))) void pass_complex_float(_Complex float a, int b, int c, int d) {}
+// CHECK-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)
+
+__attribute__((regparm(3))) void pass_complex_int(int a, _Complex int b, int c, int d) {}
+// CHECK-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)
+
+struct s16 {
+  _Complex float x1;
+};
+__attribute__((regparm(3))) void pass_wrapped_complex_float(struct s16 a, int b, int c, int d) {}
+// CHECK-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)
+
+struct s17 {
+  _Complex int x1[1];
+};
+__attribute__((regparm(3))) void pass_wrapped_complex_int(struct s17 a, int b, int c, int d) {}
+// CHECK-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)
+
+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)

>From a57091159645beb0b5e2f2d49c160bb02b5051d0 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert at folkertdev.nl>
Date: Mon, 28 Sep 2026 23:19:20 +0200
Subject: [PATCH 3/3] [clang][X86] Don't look through unions when classifying
 regparm arguments

---
 clang/lib/CodeGen/Targets/X86.cpp   |  6 +++++-
 clang/test/CodeGen/regparm-struct.c | 25 +++++++++++++++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index 57229e33d25f9..984da407f4403 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -690,7 +690,11 @@ static const Type *getSingleFieldType(QualType T, ASTContext &Context) {
 }
 
 X86_32ABIInfo::Class X86_32ABIInfo::classify(QualType Ty) const {
-  while (Ty->getAsRecordDecl()) {
+  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.
diff --git a/clang/test/CodeGen/regparm-struct.c b/clang/test/CodeGen/regparm-struct.c
index 6346cec9653fe..497c3a5ff0acd 100644
--- a/clang/test/CodeGen/regparm-struct.c
+++ b/clang/test/CodeGen/regparm-struct.c
@@ -223,3 +223,28 @@ struct s18 {
 };
 __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) {}
+// CHECK-LABEL: define{{.*}} void @pass_union_float(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)
+
+union u2 {
+  struct s5 x1;
+};
+__attribute__((regparm(3))) void pass_union_struct_float(union u2 a, int b, int c, int d) {}
+// CHECK-LABEL: define{{.*}} void @pass_union_struct_float(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 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) {}
+// CHECK-LABEL: define{{.*}} void @pass_nested_union_float(i32 inreg %a.coerce, i32 inreg noundef %b, i32 inreg noundef %c, i32 noundef %d)



More information about the cfe-commits mailing list