[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