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 <[email protected]>
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 <[email protected]>
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);
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to