[clang] f53596d - [MIPS][clang] make `_Complex` ABI match GCC (#212119)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 05:11:17 PDT 2026
Author: Folkert de Vries
Date: 2026-07-29T14:11:12+02:00
New Revision: f53596de0e42e9df10831cb50438bc8b85f826b3
URL: https://github.com/llvm/llvm-project/commit/f53596de0e42e9df10831cb50438bc8b85f826b3
DIFF: https://github.com/llvm/llvm-project/commit/f53596de0e42e9df10831cb50438bc8b85f826b3.diff
LOG: [MIPS][clang] make `_Complex` ABI match GCC (#212119)
fixes https://github.com/llvm/llvm-project/issues/212109
>From the edits to the release notes:
- On MIPS, a `_Complex` value with an integer element type is now
returned packed
into a single integer register when it fits in one, matching GCC. A
`_Complex char` or
`_Complex short`, and on N32/N64 also a `_Complex int`, is no longer
returned
with one part per register. `-fclang-abi-compat=23` restores the
previous
behavior. (#GH212109)
- On MIPS N32/N64, a `_Complex float` or `_Complex double` argument is
now packed
into integer registers, or onto the stack, once there is no longer room
to give
each of its parts a floating-point register, matching GCC. Clang
previously
always passed the parts separately. `-fclang-abi-compat=23` restores the
previous
behavior. (#GH212109)
Added:
clang/test/CodeGen/mips-complex-abi.c
Modified:
clang/docs/ReleaseNotes.md
clang/include/clang/Basic/ABIVersions.def
clang/lib/CodeGen/Targets/Mips.cpp
Removed:
################################################################################
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index f631d9f858f9f..0736df269e67a 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -62,6 +62,18 @@ honored, and calls use the caller's features, matching GCC. Per-function
features cannot lower the translation-unit ABI level;
`-fclang-abi-compat=23` restores the previous behavior. (#GH193298)
+- On MIPS, a `_Complex` value with an integer element type is now returned packed
+ into a single integer register when it fits in one, matching GCC. A `_Complex char` or
+ `_Complex short`, and on N32/N64 also a `_Complex int`, is no longer returned
+ with one part per register. `-fclang-abi-compat=23` restores the previous
+ behavior. (#GH212109)
+
+- On MIPS N32/N64, a `_Complex float` or `_Complex double` argument is now packed
+ into integer registers, or onto the stack, once there is no longer room to give
+ each of its parts a floating-point register, matching GCC. Clang previously
+ always passed the parts separately. `-fclang-abi-compat=23` restores the previous
+ behavior. (#GH212109)
+
### AST Dumping Potentially Breaking Changes
### Clang Frontend Potentially Breaking Changes
diff --git a/clang/include/clang/Basic/ABIVersions.def b/clang/include/clang/Basic/ABIVersions.def
index b55e8dfa2bbc1..42ee3c3b5f210 100644
--- a/clang/include/clang/Basic/ABIVersions.def
+++ b/clang/include/clang/Basic/ABIVersions.def
@@ -149,6 +149,12 @@ ABI_VER_MAJOR(22)
/// This causes clang to:
/// - Ignore per-function target attributes when determining the x86 AVX ABI
/// level.
+/// - On MIPS, return a `_Complex` value with an integer element type with one
+/// part per integer register, instead of packing it into a single register
+/// where it fits.
+/// - On MIPS N32/N64, always pass a `_Complex float` or `_Complex double`
+/// argument as its two parts, one floating-point register each, instead of
+/// packing it into integer registers once there is no room for both.
ABI_VER_MAJOR(23)
/// Conform to the underlying platform's C and C++ ABIs as closely as we can.
diff --git a/clang/lib/CodeGen/Targets/Mips.cpp b/clang/lib/CodeGen/Targets/Mips.cpp
index 22fdcd95ea8fa..c093cfc668c2e 100644
--- a/clang/lib/CodeGen/Targets/Mips.cpp
+++ b/clang/lib/CodeGen/Targets/Mips.cpp
@@ -23,9 +23,22 @@ class MipsABIInfo : public ABIInfo {
const unsigned MinABIStackAlignInBytes, StackAlignInBytes;
void CoerceToIntArgs(uint64_t TySize,
SmallVectorImpl<llvm::Type *> &ArgList) const;
- llvm::Type* HandleAggregates(QualType Ty, uint64_t TySize) const;
+ llvm::Type *HandleAggregates(QualType Ty, uint64_t TySize,
+ bool ComplexFitsInFPRs) const;
llvm::Type* returnAggregateInRegs(QualType RetTy, uint64_t Size) const;
llvm::Type* getPaddingType(uint64_t Align, uint64_t Offset) const;
+
+ /// Whether `_Complex` values with an integer element type are returned the
+ /// way GCC returns them. Clang 23 and earlier returned the real and the
+ /// imaginary part in two separate GPRs, later versions match GCC and pack
+ /// them into one when possible.
+ bool isComplexGnuABI() const {
+ return !getContext().getLangOpts().isCompatibleWith(
+ LangOptions::ClangABI::Ver23);
+ }
+
+ ABIArgInfo classifyComplexReturnType(QualType RetTy, uint64_t Size) const;
+
public:
MipsABIInfo(CodeGenTypes &CGT, bool _IsO32) :
ABIInfo(CGT), IsO32(_IsO32), MinABIStackAlignInBytes(IsO32 ? 4 : 8),
@@ -142,7 +155,8 @@ void MipsABIInfo::CoerceToIntArgs(
// In N32/64, an aligned double precision floating point field is passed in
// a register.
-llvm::Type* MipsABIInfo::HandleAggregates(QualType Ty, uint64_t TySize) const {
+llvm::Type *MipsABIInfo::HandleAggregates(QualType Ty, uint64_t TySize,
+ bool ComplexFitsInFPRs) const {
SmallVector<llvm::Type*, 8> ArgList, IntArgList;
if (IsO32) {
@@ -150,8 +164,15 @@ llvm::Type* MipsABIInfo::HandleAggregates(QualType Ty, uint64_t TySize) const {
return llvm::StructType::get(getVMContext(), ArgList);
}
- if (Ty->isComplexType())
- return CGT.ConvertType(Ty);
+ // A `_Complex` value that stays in FPRs is passed as its two parts.
+ // When that does not fit, it is passed like an integer of the same size.
+ if (Ty->isComplexType()) {
+ if (ComplexFitsInFPRs)
+ return CGT.ConvertType(Ty);
+
+ CoerceToIntArgs(TySize, ArgList);
+ return llvm::StructType::get(getVMContext(), ArgList);
+ }
const RecordType *RT = Ty->getAsCanonical<RecordType>();
@@ -219,6 +240,26 @@ MipsABIInfo::classifyArgumentType(QualType Ty, uint64_t &Offset) const {
unsigned CurrOffset = llvm::alignTo(Offset, Align);
Offset = CurrOffset + llvm::alignTo(TySize, Align * 8) / 8;
+ // Only pass _Complex float and _Complex double in FPRs when there are 2 free
+ // slots, otherwise use GPRs (or the stack).
+ //
+ // _Complex long double never uses GPRs. Its parts are an FPR pair each,
+ // so passing them as they are puts each part in a pair and spills to
+ // the stack the parts that don't fit.
+ bool ComplexFitsInFPRs = true;
+ if (!IsO32 && Ty->isComplexType() && isComplexGnuABI() && TySize < 256) {
+ unsigned NumArgSlots = 8;
+ uint64_t SlotsUsed = CurrOffset / MinABIStackAlignInBytes;
+ if (SlotsUsed + 2 <= NumArgSlots)
+ // Claim 2 slots. Only a `_Complex float` needs this,
+ // a `_Complex double` is already two slots.
+ Offset = CurrOffset + 2 * MinABIStackAlignInBytes;
+ else
+ // Pass like an integer of the same size, packing both parts into GPRs
+ // (or the stack).
+ ComplexFitsInFPRs = false;
+ }
+
if (isAggregateTypeForABI(Ty) || Ty->isVectorType()) {
// Ignore empty aggregates.
if (TySize == 0)
@@ -234,8 +275,8 @@ MipsABIInfo::classifyArgumentType(QualType Ty, uint64_t &Offset) const {
// another structure type. Padding is inserted if the offset of the
// aggregate is unaligned.
ABIArgInfo ArgInfo =
- ABIArgInfo::getDirect(HandleAggregates(Ty, TySize), 0,
- getPaddingType(OrigOffset, CurrOffset));
+ ABIArgInfo::getDirect(HandleAggregates(Ty, TySize, ComplexFitsInFPRs),
+ 0, getPaddingType(OrigOffset, CurrOffset));
ArgInfo.setInReg(true);
return ArgInfo;
}
@@ -301,6 +342,20 @@ MipsABIInfo::returnAggregateInRegs(QualType RetTy, uint64_t Size) const {
return llvm::StructType::get(getVMContext(), RTList);
}
+ABIArgInfo MipsABIInfo::classifyComplexReturnType(QualType RetTy,
+ uint64_t Size) const {
+ // A `_Complex` value with a floating-point element type is returned in FPRs,
+ // `_Complex long long` is returned in 2 GPRs. For older ABI versions all
+ // `_Complex {integer}` types are returned in 2 GPRs.
+ uint64_t RegisterWidth = MinABIStackAlignInBytes * 8;
+ if (!isComplexGnuABI() || RetTy->isFloatingType() || Size > RegisterWidth)
+ return ABIArgInfo::getDirect();
+
+ // Match GCC for `_Complex int`, `_Complex short` and `_Complex char` by
+ // packing the real and imaginary field into one GPR.
+ return ABIArgInfo::getDirect(llvm::IntegerType::get(getVMContext(), Size));
+}
+
ABIArgInfo MipsABIInfo::classifyReturnType(QualType RetTy) const {
uint64_t Size = getContext().getTypeSize(RetTy);
@@ -315,7 +370,7 @@ ABIArgInfo MipsABIInfo::classifyReturnType(QualType RetTy) const {
if (isAggregateTypeForABI(RetTy) || RetTy->isVectorType()) {
if (Size <= 128) {
if (RetTy->isAnyComplexType())
- return ABIArgInfo::getDirect();
+ return classifyComplexReturnType(RetTy, Size);
// O32 returns integer vectors in registers and N32/N64 returns all small
// aggregates in registers.
diff --git a/clang/test/CodeGen/mips-complex-abi.c b/clang/test/CodeGen/mips-complex-abi.c
new file mode 100644
index 0000000000000..b0eb76af2809e
--- /dev/null
+++ b/clang/test/CodeGen/mips-complex-abi.c
@@ -0,0 +1,126 @@
+// RUN: %clang_cc1 -triple mips-none-linux-gnu -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefixes=GPR,GPR32
+// RUN: %clang_cc1 -triple mipsel-none-linux-gnu -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefixes=GPR,GPR32
+
+// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n32 -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefixes=GPR,GPR64
+// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n64 -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefixes=GPR,GPR64
+// RUN: %clang_cc1 -triple mips64el-none-linux-gnu -target-abi n64 -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefixes=GPR,GPR64
+
+// RUN: %clang_cc1 -triple mips-none-linux-gnu -fclang-abi-compat=23 -emit-llvm -o - %s \
+// RUN: | FileCheck %s --check-prefixes=COMPAT23,COMPAT23-GPR32
+// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n64 -fclang-abi-compat=23 \
+// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefixes=COMPAT23,COMPAT23-GPR64
+
+// Test how MIPS passes and returns `_Complex` values.
+
+// With Clang 23 and before, `_Complex {integer}` was returned in 2 registers.
+// Later versions are compatible with GCC and pack such types into a single
+// GPR if possible.
+
+// GPR-LABEL: define{{.*}} i16 @ret_complex_char(
+// COMPAT23-LABEL: define{{.*}} { i8, i8 } @ret_complex_char(
+_Complex char ret_complex_char(void) { return 0; }
+
+// GPR-LABEL: define{{.*}} i32 @ret_complex_short(
+// COMPAT23-LABEL: define{{.*}} { i16, i16 } @ret_complex_short(
+_Complex short ret_complex_short(void) { return 0; }
+
+// GPR32-LABEL: define{{.*}} { i32, i32 } @ret_complex_int(
+// GPR64-LABEL: define{{.*}} i64 @ret_complex_int(
+// COMPAT23-LABEL: define{{.*}} { i32, i32 } @ret_complex_int(
+_Complex int ret_complex_int(void) { return 0; }
+
+// GPR-LABEL: define{{.*}} { i64, i64 } @ret_complex_long_long(
+// COMPAT23-LABEL: define{{.*}} { i64, i64 } @ret_complex_long_long(
+_Complex long long ret_complex_long_long(void) { return 0; }
+
+// A `_Complex` value with a floating-point element type is returned in FPRs.
+
+// GPR-LABEL: define{{.*}} { float, float } @ret_complex_float(
+// COMPAT23-LABEL: define{{.*}} { float, float } @ret_complex_float(
+_Complex float ret_complex_float(void) { return 0; }
+
+// GPR-LABEL: define{{.*}} { double, double } @ret_complex_double(
+// COMPAT23-LABEL: define{{.*}} { double, double } @ret_complex_double(
+_Complex double ret_complex_double(void) { return 0; }
+
+// GPR32-LABEL: define{{.*}} { double, double } @ret_complex_long_double(
+// COMPAT23-GPR32-LABEL: define{{.*}} { double, double } @ret_complex_long_double(
+// GPR64-LABEL: define{{.*}} void @ret_complex_long_double(ptr {{.*}}sret({ fp128, fp128 })
+// COMPAT23-GPR64-LABEL: define{{.*}} void @ret_complex_long_double(ptr {{.*}}sret({ fp128, fp128 })
+_Complex long double ret_complex_long_double(void) { return 0; }
+
+// Arguments
+
+// GPR-LABEL: define{{.*}} void @arg_complex_char(i16 inreg noundef %c.coerce)
+void arg_complex_char(_Complex char c) {}
+
+// GPR-LABEL: define{{.*}} void @arg_complex_short(i32 inreg noundef %c.coerce)
+void arg_complex_short(_Complex short c) {}
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_int(i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1)
+// GPR64-LABEL: define{{.*}} void @arg_complex_int(i64 inreg noundef %c.coerce)
+void arg_complex_int(_Complex int c) {}
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_long_long(i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3)
+// GPR64-LABEL: define{{.*}} void @arg_complex_long_long(i64 inreg noundef %c.coerce0, i64 inreg noundef %c.coerce1)
+void arg_complex_long_long(_Complex long long c) {}
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_float(i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1)
+// GPR64-LABEL: define{{.*}} void @arg_complex_float(float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+void arg_complex_float(_Complex float c) {}
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_double(i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3)
+// GPR64-LABEL: define{{.*}} void @arg_complex_double(double inreg noundef %c.coerce0, double inreg noundef %c.coerce1)
+void arg_complex_double(_Complex double c) {}
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_long_double(i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3)
+// GPR64-LABEL: define{{.*}} void @arg_complex_long_double(fp128 inreg noundef %c.coerce0, fp128 inreg noundef %c.coerce1)
+void arg_complex_long_double(_Complex long double c) {}
+
+// Straddling the FPR/GPR border
+
+// With Clang 23 and before, `_Complex float` and `_Complex` double were passed as two floats or doubles,
+// even when that would not fit in the remaining float registers. Later versions match GCC, which will
+// cast to one (float) or two (double) i64 values which are passed via GPRs (or the stack).
+
+// Just padding to fill the slots.
+#define SIX_SLOTS long long a0, long long a1, long long a2, \
+ long long a3, long long a4, long long a5
+#define SEVEN_SLOTS SIX_SLOTS, long long a6
+
+// Six slots used, so two are still free and the FPR pair is still used.
+
+// GPR32-LABEL: define{{.*}} @arg_complex_float_6slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1)
+// GPR64-LABEL: define{{.*}} @arg_complex_float_6slots(i64{{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_float_6slots(i64{{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+void arg_complex_float_6slots(SIX_SLOTS, _Complex float c) {}
+
+// GPR32-LABEL: define{{.*}} @arg_complex_double_6slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3)
+// GPR64-LABEL: define{{.*}} @arg_complex_double_6slots(i64{{.*}}, double inreg noundef %c.coerce0, double inreg noundef %c.coerce1)
+// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_double_6slots(i64{{.*}}, double inreg noundef %c.coerce0, double inreg noundef %c.coerce1)
+void arg_complex_double_6slots(SIX_SLOTS, _Complex double c) {}
+
+// GPR32-LABEL: define{{.*}} @arg_complex_float_7slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1)
+// GPR64-LABEL: define{{.*}} @arg_complex_float_7slots(i64{{.*}}, i64 inreg noundef %c.coerce)
+// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_float_7slots(i64{{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+void arg_complex_float_7slots(SEVEN_SLOTS, _Complex float c) {}
+
+// GPR32-LABEL: define{{.*}} @arg_complex_double_7slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3)
+// GPR64-LABEL: define{{.*}} @arg_complex_double_7slots(i64{{.*}}, i64 inreg noundef %c.coerce0, i64 inreg noundef %c.coerce1)
+// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_double_7slots(i64{{.*}}, double inreg noundef %c.coerce0, double inreg noundef %c.coerce1)
+void arg_complex_double_7slots(SEVEN_SLOTS, _Complex double c) {}
+
+// GPR64-LABEL: define{{.*}} @arg_complex_float_after_3({{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+void arg_complex_float_after_3(_Complex float p0, _Complex float p1, _Complex float p2, _Complex float c) {}
+
+// GPR64-LABEL: define{{.*}} @arg_complex_float_after_4({{.*}}, i64 inreg noundef %c.coerce)
+void arg_complex_float_after_4(_Complex float p0, _Complex float p1, _Complex float p2, _Complex float p3, _Complex float c) {}
+
+// GPR64-LABEL: define{{.*}} @arg_complex_long_double_7slots(i64{{.*}}, fp128 inreg noundef %c.coerce0, fp128 inreg noundef %c.coerce1)
+// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_long_double_7slots(i64{{.*}}, fp128 inreg noundef %c.coerce0, fp128 inreg noundef %c.coerce1)
+void arg_complex_long_double_7slots(SEVEN_SLOTS, _Complex long double c) {}
More information about the cfe-commits
mailing list