[clang] [MIPS][clang] make `_Complex {integer}` returns match GCC (PR #212119)

Folkert de Vries via cfe-commits cfe-commits at lists.llvm.org
Sun Jul 26 07:00:09 PDT 2026


https://github.com/folkertdev created https://github.com/llvm/llvm-project/pull/212119

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

The `_Complex {integer}` are now packed into a single GPR when that fits. The `-fclang-abi-compat=23` will retain the old behavior of using 2 GPRs.

>From f4e7a1eed00905586213c688bf826da92a67ae23 Mon Sep 17 00:00:00 2001
From: Folkert de Vries <folkert at folkertdev.nl>
Date: Sun, 26 Jul 2026 15:19:38 +0200
Subject: [PATCH] [MIPS] make `_Complex {integer}` returns match GCC

---
 clang/docs/ReleaseNotes.md                |  6 ++
 clang/include/clang/Basic/ABIVersions.def |  2 +
 clang/lib/CodeGen/Targets/Mips.cpp        | 28 +++++++-
 clang/test/CodeGen/mips-complex-abi.c     | 80 +++++++++++++++++++++++
 4 files changed, 115 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CodeGen/mips-complex-abi.c

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 61e8378b1704a..25b8ef1c8d604 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -62,6 +62,12 @@ 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 in
+  a single GPR if possible, matching GCC. A `_Complex char` or `_Complex short` (and,
+  on N32/N64, a `_Complex int`) fits in a single GPR and is no longer returned
+  using one GPR per part. `-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..c094c2124d3e1 100644
--- a/clang/include/clang/Basic/ABIVersions.def
+++ b/clang/include/clang/Basic/ABIVersions.def
@@ -149,6 +149,8 @@ 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 in one
+///     GPR per part, instead of packing it into as few GPRs as possible.
 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..32d22523300d8 100644
--- a/clang/lib/CodeGen/Targets/Mips.cpp
+++ b/clang/lib/CodeGen/Targets/Mips.cpp
@@ -26,6 +26,18 @@ class MipsABIInfo : public ABIInfo {
   llvm::Type* HandleAggregates(QualType Ty, uint64_t TySize) 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),
@@ -301,6 +313,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 +341,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..8262e9aa60906
--- /dev/null
+++ b/clang/test/CodeGen/mips-complex-abi.c
@@ -0,0 +1,80 @@
+// RUN: %clang_cc1 -triple mips-none-linux-gnu -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=GPR32
+// RUN: %clang_cc1 -triple mipsel-none-linux-gnu -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=GPR32
+
+// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n32 -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=GPR64
+// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n64 -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=GPR64
+// RUN: %clang_cc1 -triple mips64el-none-linux-gnu -target-abi n64 -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=GPR64
+
+// RUN: %clang_cc1 -triple mips-none-linux-gnu -fclang-abi-compat=23 -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=COMPAT23
+// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n64 -fclang-abi-compat=23 \
+// RUN:   -emit-llvm -o - %s | FileCheck %s --check-prefix=COMPAT23
+
+// Test how MIPS passes and returns `_Complex` values.
+
+// With Clang 23 and before, `_Complex {integer}` was passed in 2 registers.
+// Later versions are compatible with GCC and pack such types into a single
+// GPR if possible.
+
+// GPR32-LABEL:    define{{.*}} i16 @ret_complex_char(
+// GPR64-LABEL:    define{{.*}} i16 @ret_complex_char(
+// COMPAT23-LABEL: define{{.*}} { i8, i8 } @ret_complex_char(
+_Complex char ret_complex_char(void) { return 0; }
+
+// GPR32-LABEL:    define{{.*}} i32 @ret_complex_short(
+// GPR64-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; }
+
+// GPR32-LABEL:    define{{.*}} { i64, i64 } @ret_complex_long_long(
+// GPR64-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.
+
+// GPR32-LABEL:    define{{.*}} { float, float } @ret_complex_float(
+// GPR64-LABEL:    define{{.*}} { float, float } @ret_complex_float(
+// COMPAT23-LABEL: define{{.*}} { float, float } @ret_complex_float(
+_Complex float ret_complex_float(void) { return 0; }
+
+// GPR32-LABEL:    define{{.*}} { double, double } @ret_complex_double(
+// GPR64-LABEL:    define{{.*}} { double, double } @ret_complex_double(
+// COMPAT23-LABEL: define{{.*}} { double, double } @ret_complex_double(
+_Complex double ret_complex_double(void) { return 0; }
+
+// Arguments
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_char(i16 inreg noundef %c.coerce)
+// GPR64-LABEL: define{{.*}} void @arg_complex_char(i16 inreg noundef %c.coerce)
+void arg_complex_char(_Complex char c) {}
+
+// GPR32-LABEL: define{{.*}} void @arg_complex_short(i32 inreg noundef %c.coerce)
+// GPR64-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) {}



More information about the cfe-commits mailing list