[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