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
