[clang] [MIPS] fix zero-sized type causing incorrect register for later float arguments (PR #213746)
Folkert de Vries via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 7 04:01:33 PDT 2026
https://github.com/folkertdev updated https://github.com/llvm/llvm-project/pull/213746
>From 13bde8edfb8c407f61b8e0134da006cd8a8fd302 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert at folkertdev.nl>
Date: Mon, 3 Aug 2026 10:53:24 +0200
Subject: [PATCH 1/2] fix zst float issue
---
clang/lib/CodeGen/Targets/Mips.cpp | 19 ++++++++++-
clang/test/CodeGen/mips-zero-sized-struct.c | 37 +++++++++++++++++++++
2 files changed, 55 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CodeGen/Targets/Mips.cpp b/clang/lib/CodeGen/Targets/Mips.cpp
index c093cfc668c2e..14fb1a52d2439 100644
--- a/clang/lib/CodeGen/Targets/Mips.cpp
+++ b/clang/lib/CodeGen/Targets/Mips.cpp
@@ -416,8 +416,25 @@ void MipsABIInfo::computeInfo(CGFunctionInfo &FI) const {
// Check if a pointer to an aggregate is passed as a hidden argument.
uint64_t Offset = RetInfo.isIndirect() ? MinABIStackAlignInBytes : 0;
- for (auto &I : FI.arguments())
+ // Ignored arguments are not passed, but do end the run of floats.
+ bool SawIgnoredArg = false;
+
+ for (auto &I : FI.arguments()) {
I.info = classifyArgumentType(I.type, Offset);
+
+ // N32 and N64 always pass floating points in float registers.
+ if (!IsO32)
+ continue;
+
+ if (I.info.isIgnore())
+ SawIgnoredArg = true;
+ else if (SawIgnoredArg && I.type->isRealFloatingType())
+ // Cast to integer because we now drop the ignored arguments and otherwise
+ // later stages have no way of knowing the argument was there and later
+ // floats should be passed as integers.
+ I.info = ABIArgInfo::getDirect(llvm::IntegerType::get(
+ getVMContext(), getContext().getTypeSize(I.type)));
+ }
}
RValue MipsABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAListAddr,
diff --git a/clang/test/CodeGen/mips-zero-sized-struct.c b/clang/test/CodeGen/mips-zero-sized-struct.c
index a4c5fc87cd9fc..5394ccf348358 100644
--- a/clang/test/CodeGen/mips-zero-sized-struct.c
+++ b/clang/test/CodeGen/mips-zero-sized-struct.c
@@ -28,3 +28,40 @@ T2 T2_retval;
T2 fn28(char arg0) {
return T2_retval;
}
+
+// A zero-sized argument consumes no register, but on O32 it does end the run of
+// leading floating-point arguments, so the arguments after it are passed in
+// integer registers.
+//
+// O32: define{{.*}} void @fn29(i32 noundef %arg1.coerce, i64 noundef %arg2.coerce)
+// O32: declare void @fn30(i32 noundef, i64 noundef)
+//
+// N32: define{{.*}} void @fn29(float noundef %arg1, double noundef %arg2)
+// N32: declare void @fn30(float noundef, double noundef)
+//
+// N64: define{{.*}} void @fn29(float noundef %arg1, double noundef %arg2)
+// N64: declare void @fn30(float noundef, double noundef)
+
+void fn30(T2 arg0, float arg1, double arg2);
+
+void fn29(T2 arg0, float arg1, double arg2) {
+ fn30(arg0, arg1, arg2);
+}
+
+// The arguments before the zero-sized one are unaffected: arg0 is still a
+// leading floating-point argument and stays in a floating-point register.
+//
+// O32: define{{.*}} void @fn31(float noundef %arg0, i32 noundef %arg2.coerce)
+// O32: declare void @fn32(float noundef, i32 noundef)
+//
+// N32: define{{.*}} void @fn31(float noundef %arg0, float noundef %arg2)
+// N32: declare void @fn32(float noundef, float noundef)
+//
+// N64: define{{.*}} void @fn31(float noundef %arg0, float noundef %arg2)
+// N64: declare void @fn32(float noundef, float noundef)
+
+void fn32(float arg0, T2 arg1, float arg2);
+
+void fn31(float arg0, T2 arg1, float arg2) {
+ fn32(arg0, arg1, arg2);
+}
>From 3b6f5f62ab2e571933aa4a9f52d6009d96683fce Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert at folkertdev.nl>
Date: Fri, 7 Aug 2026 12:25:51 +0200
Subject: [PATCH 2/2] fix aligned ZSTs too
---
clang/lib/CodeGen/Targets/Mips.cpp | 27 ++++---
clang/test/CodeGen/mips-zero-sized-struct.c | 78 +++++++++++++++++++++
2 files changed, 95 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/Mips.cpp b/clang/lib/CodeGen/Targets/Mips.cpp
index 14fb1a52d2439..220bdcb5886f8 100644
--- a/clang/lib/CodeGen/Targets/Mips.cpp
+++ b/clang/lib/CodeGen/Targets/Mips.cpp
@@ -261,9 +261,14 @@ MipsABIInfo::classifyArgumentType(QualType Ty, uint64_t &Offset) const {
}
if (isAggregateTypeForABI(Ty) || Ty->isVectorType()) {
- // Ignore empty aggregates.
- if (TySize == 0)
+ // Ignore empty aggregates, but do insert padding for over-aligned
+ // zero-sized types.
+ if (TySize == 0) {
+ if (llvm::Type *Padding = getPaddingType(OrigOffset, CurrOffset))
+ return ABIArgInfo::getExpandWithPadding(/*PaddingInReg=*/false,
+ Padding);
return ABIArgInfo::getIgnore();
+ }
if (CGCXXABI::RecordArgABI RAA = getRecordArgABI(Ty, getCXXABI())) {
Offset = OrigOffset + MinABIStackAlignInBytes;
@@ -416,8 +421,8 @@ void MipsABIInfo::computeInfo(CGFunctionInfo &FI) const {
// Check if a pointer to an aggregate is passed as a hidden argument.
uint64_t Offset = RetInfo.isIndirect() ? MinABIStackAlignInBytes : 0;
- // Ignored arguments are not passed, but do end the run of floats.
- bool SawIgnoredArg = false;
+ // Zero-sized arguments are not passed, but do end the run of floats.
+ bool SawZeroSizedArg = false;
for (auto &I : FI.arguments()) {
I.info = classifyArgumentType(I.type, Offset);
@@ -426,14 +431,16 @@ void MipsABIInfo::computeInfo(CGFunctionInfo &FI) const {
if (!IsO32)
continue;
- if (I.info.isIgnore())
- SawIgnoredArg = true;
- else if (SawIgnoredArg && I.type->isRealFloatingType())
- // Cast to integer because we now drop the ignored arguments and otherwise
- // later stages have no way of knowing the argument was there and later
- // floats should be passed as integers.
+ if (getContext().getTypeSize(I.type) == 0)
+ SawZeroSizedArg = true;
+ else if (SawZeroSizedArg && I.type->isRealFloatingType()) {
+ // A zero-sized type ends the leading run of float arguments that is
+ // passed in FPRs. Any subsequent floats must be passed via GPRs. Cast the
+ // float to an integer now because we drop the zero-sized argument here
+ // and later stages have no way of inferring that it was there.
I.info = ABIArgInfo::getDirect(llvm::IntegerType::get(
getVMContext(), getContext().getTypeSize(I.type)));
+ }
}
}
diff --git a/clang/test/CodeGen/mips-zero-sized-struct.c b/clang/test/CodeGen/mips-zero-sized-struct.c
index 5394ccf348358..c1f2abede09ec 100644
--- a/clang/test/CodeGen/mips-zero-sized-struct.c
+++ b/clang/test/CodeGen/mips-zero-sized-struct.c
@@ -65,3 +65,81 @@ void fn32(float arg0, T2 arg1, float arg2);
void fn31(float arg0, T2 arg1, float arg2) {
fn32(arg0, arg1, arg2);
}
+
+typedef struct T3 { } __attribute__((aligned(8))) T3;
+typedef struct T4 { } __attribute__((aligned(16))) T4;
+
+// An over-aligned zero-sized argument has no value, but does take up the
+// argument slots that its alignment requires.
+//
+// T3 requires an alignment of 8. On O32 that requires 4 bytes of padding
+// (the bar i32), on N32/N64 the GPRs are 8 bytes and not additional
+// padding is needed.
+//
+// O32: define{{.*}} void @fn33(i32 noundef signext %arg0, i32 %0, i32 noundef signext %arg2)
+// O32: declare void @fn34(i32 noundef signext, i32, i32 noundef signext)
+//
+// N32: define{{.*}} void @fn33(i32 noundef signext %arg0, i32 noundef signext %arg2)
+// N32: declare void @fn34(i32 noundef signext, i32 noundef signext)
+//
+// N64: define{{.*}} void @fn33(i32 noundef signext %arg0, i32 noundef signext %arg2)
+// N64: declare void @fn34(i32 noundef signext, i32 noundef signext)
+
+void fn34(int arg0, T3 arg1, int arg2);
+
+void fn33(int arg0, T3 arg1, int arg2) {
+ fn34(arg0, arg1, arg2);
+}
+
+// T4 is over-aligned for every ABI, so it skips a slot on all of them. The alignment
+// is capped at the stack alignment of 8 bytes, so O32 skips only one slot, not three.
+//
+// O32: define{{.*}} void @fn35(i32 noundef signext %arg0, i32 %0, i32 noundef signext %arg2)
+// O32: declare void @fn36(i32 noundef signext, i32, i32 noundef signext)
+//
+// N32: define{{.*}} void @fn35(i32 noundef signext %arg0, i64 %0, i32 noundef signext %arg2)
+// N32: declare void @fn36(i32 noundef signext, i64, i32 noundef signext)
+//
+// N64: define{{.*}} void @fn35(i32 noundef signext %arg0, i64 %0, i32 noundef signext %arg2)
+// N64: declare void @fn36(i32 noundef signext, i64, i32 noundef signext)
+
+void fn36(int arg0, T4 arg1, int arg2);
+
+void fn35(int arg0, T4 arg1, int arg2) {
+ fn36(arg0, arg1, arg2);
+}
+
+// No padding is needed when the slot is already aligned.
+//
+// O32: define{{.*}} void @fn37(i32 noundef signext %arg1, i32 noundef signext %arg2)
+// O32: declare void @fn38(i32 noundef signext, i32 noundef signext)
+//
+// N32: define{{.*}} void @fn37(i32 noundef signext %arg1, i32 noundef signext %arg2)
+// N32: declare void @fn38(i32 noundef signext, i32 noundef signext)
+//
+// N64: define{{.*}} void @fn37(i32 noundef signext %arg1, i32 noundef signext %arg2)
+// N64: declare void @fn38(i32 noundef signext, i32 noundef signext)
+
+void fn38(T3 arg0, int arg1, int arg2);
+
+void fn37(T3 arg0, int arg1, int arg2) {
+ fn38(arg0, arg1, arg2);
+}
+
+// On O32 both effects apply: the skipped slot becomes padding, and the
+// zero-sized argument ends the run of leading floating-point arguments.
+//
+// O32: define{{.*}} void @fn39(float noundef %arg0, i32 %0, i32 noundef %arg2.coerce)
+// O32: declare void @fn40(float noundef, i32, i32 noundef)
+//
+// N32: define{{.*}} void @fn39(float noundef %arg0, float noundef %arg2)
+// N32: declare void @fn40(float noundef, float noundef)
+//
+// N64: define{{.*}} void @fn39(float noundef %arg0, float noundef %arg2)
+// N64: declare void @fn40(float noundef, float noundef)
+
+void fn40(float arg0, T3 arg1, float arg2);
+
+void fn39(float arg0, T3 arg1, float arg2) {
+ fn40(arg0, arg1, arg2);
+}
More information about the cfe-commits
mailing list