[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