[clang] 82825dd - [MIPS] fix zero-sized type causing incorrect register for later float arguments (#213746)

via cfe-commits cfe-commits at lists.llvm.org
Mon Aug 10 01:43:35 PDT 2026


Author: Folkert de Vries
Date: 2026-08-10T10:43:30+02:00
New Revision: 82825dd420c562d5dab1ef884f5d0b8c10eab7a5

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

LOG: [MIPS] fix zero-sized type  causing incorrect register for later float arguments (#213746)

fixes https://github.com/llvm/llvm-project/issues/213540

https://godbolt.org/z/zMW6MMoze

On O32 a zero-sized type is not passed, but it does (in GCC) end the run
of leading float arguments that are passed in float registers. The new
behavior is consistent with GCC. It seems unlikely that people rely on
this (zero-sized structs are rare in C, but much more common in Rust) so
I did not bother with the ABI flag. It could be added though if there is
reason to.

Added: 
    

Modified: 
    clang/lib/CodeGen/Targets/Mips.cpp
    clang/test/CodeGen/mips-zero-sized-struct.c

Removed: 
    


################################################################################
diff  --git a/clang/lib/CodeGen/Targets/Mips.cpp b/clang/lib/CodeGen/Targets/Mips.cpp
index c093cfc668c2e..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,27 @@ 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())
+  // 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);
+
+    // N32 and N64 always pass floating points in float registers.
+    if (!IsO32)
+      continue;
+
+    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)));
+    }
+  }
 }
 
 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..c1f2abede09ec 100644
--- a/clang/test/CodeGen/mips-zero-sized-struct.c
+++ b/clang/test/CodeGen/mips-zero-sized-struct.c
@@ -28,3 +28,118 @@ 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);
+}
+
+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