Skip to content

[MIPS][clang] make _Complex ABI match GCC - #212119

Merged
folkertdev merged 2 commits into
llvm:mainfrom
folkertdev:mips-complex-small-int-abi
Jul 29, 2026
Merged

[MIPS][clang] make _Complex ABI match GCC#212119
folkertdev merged 2 commits into
llvm:mainfrom
folkertdev:mips-complex-small-int-abi

Conversation

@folkertdev

@folkertdev folkertdev commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

fixes #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)

@folkertdev
folkertdev marked this pull request as ready for review July 26, 2026 15:28
@llvmorg-github-actions llvmorg-github-actions Bot added clang Clang issues not falling into any other category backend:MIPS clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jul 26, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-mips

@llvm/pr-subscribers-clang

Author: Folkert de Vries (folkertdev)

Changes

fixes #212109

A return of _Complex {integer} is now packed into a single GPR when that fits, matching GCC. The -fclang-abi-compat=23 flag still gives the old behavior of using 2 GPRs, if compatibility with older clang version for this type is needed somewhere.


Full diff: https://github.com/llvm/llvm-project/pull/212119.diff

4 Files Affected:

  • (modified) clang/docs/ReleaseNotes.md (+6)
  • (modified) clang/include/clang/Basic/ABIVersions.def (+2)
  • (modified) clang/lib/CodeGen/Targets/Mips.cpp (+27-1)
  • (added) clang/test/CodeGen/mips-complex-abi.c (+80)
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) {}

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-clang-codegen

Author: Folkert de Vries (folkertdev)

Changes

fixes #212109

A return of _Complex {integer} is now packed into a single GPR when that fits, matching GCC. The -fclang-abi-compat=23 flag still gives the old behavior of using 2 GPRs, if compatibility with older clang version for this type is needed somewhere.


Full diff: https://github.com/llvm/llvm-project/pull/212119.diff

4 Files Affected:

  • (modified) clang/docs/ReleaseNotes.md (+6)
  • (modified) clang/include/clang/Basic/ABIVersions.def (+2)
  • (modified) clang/lib/CodeGen/Targets/Mips.cpp (+27-1)
  • (added) clang/test/CodeGen/mips-complex-abi.c (+80)
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) {}

@folkertdev
folkertdev requested a review from yingopq July 26, 2026 15:29
@yingopq

yingopq commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Do we need apply new function isComplexGnuABI to classifyArgumentType?

@folkertdev

Copy link
Copy Markdown
Contributor Author

Yes, but for a different problem that occurs when the argument slots run out and can only fit half of a complex float or double. Clang and GCC diverged there too which I have now fixed.

I've now validated the implementation with https://github.com/folkertdev/powerpc-complex-abi-validation which compiles various test programs with GCC and Clang and checks that values make it from one side to the other.

@yingopq

yingopq commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

LGTM.

@folkertdev folkertdev changed the title [MIPS][clang] make _Complex {integer} returns match GCC [MIPS][clang] make _Complex ABI match GCC Jul 29, 2026
@folkertdev
folkertdev merged commit f53596d into llvm:main Jul 29, 2026
13 checks passed
asudarsa-qti pushed a commit to asudarsa-qti/llvm-project that referenced this pull request Jul 29, 2026
fixes llvm#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)
jgreenbaum pushed a commit to jgreenbaum/llvm-project that referenced this pull request Aug 3, 2026
fixes llvm#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)
folkertdev added a commit that referenced this pull request Aug 5, 2026
Modify the ABI of `_Complex` so that it matches GCC for all types,
specifically:

- On SPARC, a `_Complex` value with an integer element type is now
passed and
returned packed into the one or two integer registers it fits in,
matching GCC.
Clang previously passed such a value indirectly and returned it with one
part
  per register. 
  `-fclang-abi-compat=23` restores the previous behavior.

- On SPARC64, a `_Complex char` or `_Complex short` is now
right-justified in its slot in the parameter array, like every other
scalar
narrower than a slot, rather than left-justified the way a small struct
is.
  `-fclang-abi-compat=23` restores the previous behavior.

Complex integers are a GNU extension, but generally clang is compatible
with GCC. Really, you might as well be, deviating can only bite users.

I've now validated the implementation with
https://github.com/folkertdev/powerpc-complex-abi-validation which
compiles various signatures using `_Complex` with GCC and Clang and
checks that values make it from one side to the other.

related:

- rust-lang/rust#154023
- #208917
- #212119
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request Aug 5, 2026
Modify the ABI of `_Complex` so that it matches GCC for all types,
specifically:

- On SPARC, a `_Complex` value with an integer element type is now
passed and
returned packed into the one or two integer registers it fits in,
matching GCC.
Clang previously passed such a value indirectly and returned it with one
part
  per register.
  `-fclang-abi-compat=23` restores the previous behavior.

- On SPARC64, a `_Complex char` or `_Complex short` is now
right-justified in its slot in the parameter array, like every other
scalar
narrower than a slot, rather than left-justified the way a small struct
is.
  `-fclang-abi-compat=23` restores the previous behavior.

Complex integers are a GNU extension, but generally clang is compatible
with GCC. Really, you might as well be, deviating can only bite users.

I've now validated the implementation with
https://github.com/folkertdev/powerpc-complex-abi-validation which
compiles various signatures using `_Complex` with GCC and Clang and
checks that values make it from one side to the other.

related:

- rust-lang/rust#154023
- llvm/llvm-project#208917
- llvm/llvm-project#212119
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Aug 5, 2026
Modify the ABI of `_Complex` so that it matches GCC for all types,
specifically:

- On SPARC, a `_Complex` value with an integer element type is now
passed and
returned packed into the one or two integer registers it fits in,
matching GCC.
Clang previously passed such a value indirectly and returned it with one
part
  per register.
  `-fclang-abi-compat=23` restores the previous behavior.

- On SPARC64, a `_Complex char` or `_Complex short` is now
right-justified in its slot in the parameter array, like every other
scalar
narrower than a slot, rather than left-justified the way a small struct
is.
  `-fclang-abi-compat=23` restores the previous behavior.

Complex integers are a GNU extension, but generally clang is compatible
with GCC. Really, you might as well be, deviating can only bite users.

I've now validated the implementation with
https://github.com/folkertdev/powerpc-complex-abi-validation which
compiles various signatures using `_Complex` with GCC and Clang and
checks that values make it from one side to the other.

related:

- rust-lang/rust#154023
- llvm/llvm-project#208917
- llvm/llvm-project#212119
jinge90 pushed a commit to jinge90/llvm-project that referenced this pull request Aug 6, 2026
Modify the ABI of `_Complex` so that it matches GCC for all types,
specifically:

- On SPARC, a `_Complex` value with an integer element type is now
passed and
returned packed into the one or two integer registers it fits in,
matching GCC.
Clang previously passed such a value indirectly and returned it with one
part
  per register. 
  `-fclang-abi-compat=23` restores the previous behavior.

- On SPARC64, a `_Complex char` or `_Complex short` is now
right-justified in its slot in the parameter array, like every other
scalar
narrower than a slot, rather than left-justified the way a small struct
is.
  `-fclang-abi-compat=23` restores the previous behavior.

Complex integers are a GNU extension, but generally clang is compatible
with GCC. Really, you might as well be, deviating can only bite users.

I've now validated the implementation with
https://github.com/folkertdev/powerpc-complex-abi-validation which
compiles various signatures using `_Complex` with GCC and Clang and
checks that values make it from one side to the other.

related:

- rust-lang/rust#154023
- llvm#208917
- llvm#212119
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:MIPS clang:codegen IR generation bugs: mangling, exceptions, etc. clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clang][MIPS] ABI incompatibility with GNU on small integer complex arguments

2 participants