Skip to content

[PowerPC] make _Complex ABI GCC-compatible - #208917

Open
folkertdev wants to merge 11 commits into
llvm:mainfrom
folkertdev:fix-powerpc-complex-abi
Open

[PowerPC] make _Complex ABI GCC-compatible#208917
folkertdev wants to merge 11 commits into
llvm:mainfrom
folkertdev:fix-powerpc-complex-abi

Conversation

@folkertdev

@folkertdev folkertdev commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

closes #77732
cc #44482

Replaces #77732 and truly fixes #56023.

The design, as per #77732 (comment), is that -fclang-abi-compat=22 continues to give the old ABI behavior, and in 23 (or 24, we're close to the cutoff and this might not make it) gives the new GCC-compatible behavior.

So, the challenge is of course how to test and review all this. I have a collection of input file that I used for testing empirically versus GCC:

https://github.com/folkertdev/powerpc-complex-abi-validation

I'd happily just add (a subset of) those and run the CHECK generation on them, but I'm not sure that wall of text is worth it: you can't really see the ABI the LLVM IR, and you certainly can't see easily that it's equivalent to GCC. On the other hand, as you can see from the current tests that were touched, the testing is rather thin.

And the GCC logic is non-trivial: some types must be converted to integers, vectors or arrays to get the right behavior. There is register alignment for some types.

Future work is to make va_arg work. The pre-existing implementation has a big FIXME for it, but it should be possible to implement in a GCC-compatible way now.

@folkertdev
folkertdev force-pushed the fix-powerpc-complex-abi branch from 8b25c94 to 7f395d8 Compare July 11, 2026 15:38
@folkertdev
folkertdev marked this pull request as ready for review July 11, 2026 18:07
@llvmorg-github-actions llvmorg-github-actions Bot added clang Clang issues not falling into any other category backend:PowerPC clang:codegen IR generation bugs: mangling, exceptions, etc. labels Jul 11, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Jul 11, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-powerpc

@llvm/pr-subscribers-clang-codegen

Author: Folkert de Vries (folkertdev)

Changes

closes #77732
cc #44482

Replaces #77732 and truly fixes #56023.

The design, as per #77732 (comment), is that -fclang-abi-compat=22 continues to give the old ABI behavior, and in 23 (or 24, we're close to the cutoff and this might not make it) gives the new GCC-compatible behavior.

So, the challenge is of course how to test and review all this. I have a collection of input file that I used for testing empirically versus GCC:

https://github.com/folkertdev/powerpc-complex-abi-validation

I'd happily just add (a subset of) those and run the CHECK generation on them, but I'm not sure that's really worth it. As you can see though from the current tests that were touched, the testing is rather thin.

And the GCC logic is non-trivial: some types must be converted to integers, vectors or arrays to get the right behavior. There is register alignment for some types.

Future work is to make va_arg work. The pre-existing implementation has a big FIXME for it, but it should be possible to implement in a GCC-compatible way now.


Patch is 60.86 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/208917.diff

5 Files Affected:

  • (modified) clang/lib/CodeGen/Targets/PPC.cpp (+111-2)
  • (modified) clang/test/CodeGen/PowerPC/builtins-ppc-xlcompat-cmplx.c (+230-130)
  • (modified) clang/test/CodeGen/PowerPC/powerpc-c99complex.c (+72-20)
  • (modified) clang/test/CodeGen/long_double_fp128.cpp (+6-3)
  • (modified) clang/test/CodeGen/math-libcalls-tbaa-indirect-args.c (+42-29)
diff --git a/clang/lib/CodeGen/Targets/PPC.cpp b/clang/lib/CodeGen/Targets/PPC.cpp
index ab069bfbd1b51..fc07e57656d09 100644
--- a/clang/lib/CodeGen/Targets/PPC.cpp
+++ b/clang/lib/CodeGen/Targets/PPC.cpp
@@ -361,7 +361,18 @@ class PPC32_SVR4_ABIInfo : public DefaultABIInfo {
   bool IsSoftFloatABI;
   bool IsRetSmallStructInRegABI;
 
+  // Number of GPRs (r3..=r10) available for passing arguments, and their width.
+  static const int NumArgGPRs = 8;
+  static const unsigned GPRBits = 32;
+
+  bool isComplexGnuABI() const {
+    return !getTarget().getTriple().isOSDarwin() &&
+           !getContext().getLangOpts().isCompatibleWith(
+               LangOptions::ClangABI::Ver22);
+  }
+
   CharUnits getParamTypeAlignment(QualType Ty) const;
+  ABIArgInfo classifyComplexType(QualType Ty) const;
 
 public:
   PPC32_SVR4_ABIInfo(CodeGen::CodeGenTypes &CGT, bool SoftFloatABI,
@@ -370,12 +381,15 @@ class PPC32_SVR4_ABIInfo : public DefaultABIInfo {
         IsRetSmallStructInRegABI(RetSmallStructInRegABI) {}
 
   ABIArgInfo classifyReturnType(QualType RetTy) const;
+  ABIArgInfo classifyArgumentType(QualType Ty, int &ArgGPRsLeft) const;
 
   void computeInfo(CGFunctionInfo &FI) const override {
     if (!getCXXABI().classifyReturnType(FI))
       FI.getReturnInfo() = classifyReturnType(FI.getReturnType());
+
+    int ArgGPRsLeft = NumArgGPRs;
     for (auto &I : FI.arguments())
-      I.info = classifyArgumentType(I.type);
+      I.info = classifyArgumentType(I.type, ArgGPRsLeft);
   }
 
   RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty,
@@ -426,9 +440,102 @@ CharUnits PPC32_SVR4_ABIInfo::getParamTypeAlignment(QualType Ty) const {
   return CharUnits::fromQuantity(4);
 }
 
+ABIArgInfo PPC32_SVR4_ABIInfo::classifyComplexType(QualType Ty) const {
+  uint64_t Size = getContext().getTypeSize(Ty);
+  llvm::LLVMContext &VMC = getVMContext();
+  llvm::Type *I32 = llvm::Type::getInt32Ty(VMC);
+
+  assert(Ty->isAnyComplexType() && "not a complex type");
+  QualType ElemTy = Ty->castAs<ComplexType>()->getElementType();
+
+  // Work around https://github.com/llvm/llvm-project/issues/44482.
+  // This is a bug where the two halves of a ppc_fp128 are swapped.
+  // Using i128 for the ABI instead circumvents this issue.
+  if (CGT.ConvertType(ElemTy)->isPPC_FP128Ty())
+    return ABIArgInfo::getDirect(
+        llvm::ArrayType::get(llvm::Type::getInt128Ty(VMC), 2));
+
+  // Coerce to an integer for _Complex char and _Complex short.
+  if (Size <= GPRBits)
+    return ABIArgInfo::getDirect(llvm::IntegerType::get(VMC, Size));
+
+  // Coerce to a vector <N x i32> for _Complex float and _Complex int.
+  // A vector gives this the correct 8-byte register alignment.
+  if (Size == 2 * GPRBits)
+    return ABIArgInfo::getDirect(llvm::FixedVectorType::get(I32, 2));
+
+  // Coerce to an array [N x i32] for _Complex double and similar.
+  // An array of i32 gives the correct 4-byte register alignment.
+  return ABIArgInfo::getDirect(llvm::ArrayType::get(I32, Size / GPRBits));
+}
+
+ABIArgInfo PPC32_SVR4_ABIInfo::classifyArgumentType(QualType Ty,
+                                                    int &ArgGPRsLeft) const {
+  assert(ArgGPRsLeft <= NumArgGPRs && "Arg GPR tracking underflow");
+  Ty = useFirstFieldIfTransparentUnion(Ty);
+
+  bool IsComplex = Ty->isAnyComplexType() && isComplexGnuABI();
+
+  // Use the default implementation when this argument is not relevant for GPR
+  // budget:
+  //
+  // - floating-point types are passed in FPRs
+  // - when GPRs are already exhausted
+  //
+  // Complex types (when GNU compatible) always need custom handling.
+  if (!IsComplex && (!ArgGPRsLeft || (Ty->isFloatingType() && !IsSoftFloatABI)))
+    return DefaultABIInfo::classifyArgumentType(Ty);
+
+  uint64_t TypeSize = getContext().getTypeSize(Ty);
+  uint64_t RegsNeeded = (TypeSize + GPRBits - 1) / GPRBits;
+
+  if (IsComplex || !isAggregateTypeForABI(Ty)) {
+    // Complex values and scalars are passed in GPRs.
+
+    // An 8-byte value (e.g. _Complex float, _Complex int, i64, soft-float
+    // double) must start in an even-numbered GPR, so skip an odd register to
+    // keep the pair aligned.
+    if (TypeSize == 2 * GPRBits && ArgGPRsLeft % 2 == 1)
+      ArgGPRsLeft -= 1;
+
+    if (RegsNeeded <= (uint64_t)ArgGPRsLeft) {
+      // All fits.
+      ArgGPRsLeft -= RegsNeeded;
+
+      // _Complex needs a type coercion to be passed correctly.
+      if (IsComplex)
+        return classifyComplexType(Ty);
+    } else if (IsComplex) {
+      // Never split a Complex value across GPRs and the stack. When a Complex
+      // value does not fit in the remaining GPRs, it is passed via the stack
+      // and the remaining GPRs are considered consumed, so any further
+      // arguments will be passed via the stack as well.
+
+      // _Complex needs a type coercion to be passed correctly.
+      llvm::Type *CoerceTy = classifyComplexType(Ty).getCoerceToType();
+
+      // Soak up the remaining GPRs with a padding argument.
+      llvm::Type *I32 = llvm::Type::getInt32Ty(getVMContext());
+      llvm::Type *Padding =
+          ArgGPRsLeft > 0 ? llvm::ArrayType::get(I32, ArgGPRsLeft) : nullptr;
+
+      ArgGPRsLeft = 0;
+      return ABIArgInfo::getDirect(CoerceTy, /*Offset=*/0, Padding);
+    }
+  } else {
+    // Other aggregates are passed indirectly, and consume one GPR.
+    ArgGPRsLeft -= 1;
+  }
+
+  return DefaultABIInfo::classifyArgumentType(Ty);
+}
+
 ABIArgInfo PPC32_SVR4_ABIInfo::classifyReturnType(QualType RetTy) const {
   uint64_t Size;
 
+  if (RetTy->isAnyComplexType() && isComplexGnuABI())
+    return classifyComplexType(RetTy);
+
   // -msvr4-struct-return puts small aggregates in GPR3 and GPR4.
   if (isAggregateTypeForABI(RetTy) && IsRetSmallStructInRegABI &&
       (Size = getContext().getTypeSize(RetTy)) <= 64) {
@@ -463,8 +570,10 @@ RValue PPC32_SVR4_ABIInfo::EmitVAArg(CodeGenFunction &CGF, Address VAList,
     TI.Align = getParamTypeAlignment(Ty);
 
     CharUnits SlotSize = CharUnits::fromQuantity(4);
+    int ArgGPRsLeft = NumArgGPRs;
     return emitVoidPtrVAArg(CGF, VAList, Ty,
-                            classifyArgumentType(Ty).isIndirect(), TI, SlotSize,
+                            classifyArgumentType(Ty, ArgGPRsLeft).isIndirect(),
+                            TI, SlotSize,
                             /*AllowHigherAlign=*/true, Slot);
   }
 
diff --git a/clang/test/CodeGen/PowerPC/builtins-ppc-xlcompat-cmplx.c b/clang/test/CodeGen/PowerPC/builtins-ppc-xlcompat-cmplx.c
index cf03af38b3784..ae224aef66da9 100644
--- a/clang/test/CodeGen/PowerPC/builtins-ppc-xlcompat-cmplx.c
+++ b/clang/test/CodeGen/PowerPC/builtins-ppc-xlcompat-cmplx.c
@@ -6,10 +6,14 @@
 // RUN:   -emit-llvm %s -o -  -target-cpu pwr8 | FileCheck %s --check-prefix=64BITLE
 // RUN: %clang_cc1 -triple powerpc64-unknown-aix \
 // RUN:    -emit-llvm %s -o -  -target-cpu pwr7 | FileCheck %s --check-prefix=64BITAIX
-// RUN: %clang_cc1 -triple powerpc-unknown-linux-gnu \
-// RUN:    -emit-llvm %s -o -  -target-cpu pwr7 | FileCheck %s --check-prefix=32BIT
-// RUN: %clang_cc1 -triple powerpcle-unknown-linux-gnu \
-// RUN:   -emit-llvm %s -o -  -target-cpu pwr8 | FileCheck %s --check-prefix=32BITLE
+// RUN: %clang_cc1 -triple powerpc-unknown-linux-gnu -fclang-abi-compat=22 \
+// RUN:    -emit-llvm %s -o -  -target-cpu pwr7 | FileCheck %s --check-prefix=32BIT-CLANG22
+// RUN: %clang_cc1 -triple powerpc-unknown-linux-gnu -fclang-abi-compat=23 \
+// RUN:    -emit-llvm %s -o -  -target-cpu pwr7 | FileCheck %s --check-prefix=32BIT-CLANG23
+// RUN: %clang_cc1 -triple powerpcle-unknown-linux-gnu -fclang-abi-compat=22 \
+// RUN:   -emit-llvm %s -o -  -target-cpu pwr8 | FileCheck %s --check-prefix=32BITLE-CLANG22
+// RUN: %clang_cc1 -triple powerpcle-unknown-linux-gnu -fclang-abi-compat=23 \
+// RUN:   -emit-llvm %s -o -  -target-cpu pwr8 | FileCheck %s --check-prefix=32BITLE-CLANG23
 // RUN: %clang_cc1 -triple powerpc-unknown-aix \
 // RUN:    -emit-llvm %s -o -  -target-cpu pwr7 | FileCheck %s --check-prefix=32BITAIX
 
@@ -61,49 +65,81 @@
 // 64BITAIX-NEXT:    [[TMP2:%.*]] = load { double, double }, ptr [[RETVAL]], align 4
 // 64BITAIX-NEXT:    ret { double, double } [[TMP2]]
 //
-// 32BIT-LABEL: @testcmplx(
-// 32BIT-NEXT:  entry:
-// 32BIT-NEXT:    [[REAL_ADDR:%.*]] = alloca double, align 8
-// 32BIT-NEXT:    [[IMAG_ADDR:%.*]] = alloca double, align 8
-// 32BIT-NEXT:    store double [[REAL:%.*]], ptr [[REAL_ADDR]], align 8
-// 32BIT-NEXT:    store double [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 8
-// 32BIT-NEXT:    [[TMP0:%.*]] = load double, ptr [[REAL_ADDR]], align 8
-// 32BIT-NEXT:    [[TMP1:%.*]] = load double, ptr [[IMAG_ADDR]], align 8
-// 32BIT-NEXT:    [[AGG_RESULT_REALP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT:%.*]], i32 0, i32 0
-// 32BIT-NEXT:    [[AGG_RESULT_IMAGP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BIT-NEXT:    store double [[TMP0]], ptr [[AGG_RESULT_REALP]], align 8
-// 32BIT-NEXT:    store double [[TMP1]], ptr [[AGG_RESULT_IMAGP]], align 8
-// 32BIT-NEXT:    [[AGG_RESULT_REALP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
-// 32BIT-NEXT:    [[AGG_RESULT_REAL:%.*]] = load double, ptr [[AGG_RESULT_REALP1]], align 8
-// 32BIT-NEXT:    [[AGG_RESULT_IMAGP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BIT-NEXT:    [[AGG_RESULT_IMAG:%.*]] = load double, ptr [[AGG_RESULT_IMAGP2]], align 8
-// 32BIT-NEXT:    [[AGG_RESULT_REALP3:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
-// 32BIT-NEXT:    [[AGG_RESULT_IMAGP4:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BIT-NEXT:    store double [[AGG_RESULT_REAL]], ptr [[AGG_RESULT_REALP3]], align 8
-// 32BIT-NEXT:    store double [[AGG_RESULT_IMAG]], ptr [[AGG_RESULT_IMAGP4]], align 8
-// 32BIT-NEXT:    ret void
+// 32BIT-CLANG22-LABEL: @testcmplx(
+// 32BIT-CLANG22-NEXT:  entry:
+// 32BIT-CLANG22-NEXT:    [[REAL_ADDR:%.*]] = alloca double, align 8
+// 32BIT-CLANG22-NEXT:    [[IMAG_ADDR:%.*]] = alloca double, align 8
+// 32BIT-CLANG22-NEXT:    store double [[REAL:%.*]], ptr [[REAL_ADDR]], align 8
+// 32BIT-CLANG22-NEXT:    store double [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 8
+// 32BIT-CLANG22-NEXT:    [[TMP0:%.*]] = load double, ptr [[REAL_ADDR]], align 8
+// 32BIT-CLANG22-NEXT:    [[TMP1:%.*]] = load double, ptr [[IMAG_ADDR]], align 8
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_REALP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT:%.*]], i32 0, i32 0
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_IMAGP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
+// 32BIT-CLANG22-NEXT:    store double [[TMP0]], ptr [[AGG_RESULT_REALP]], align 8
+// 32BIT-CLANG22-NEXT:    store double [[TMP1]], ptr [[AGG_RESULT_IMAGP]], align 8
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_REALP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_REAL:%.*]] = load double, ptr [[AGG_RESULT_REALP1]], align 8
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_IMAGP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_IMAG:%.*]] = load double, ptr [[AGG_RESULT_IMAGP2]], align 8
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_REALP3:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
+// 32BIT-CLANG22-NEXT:    [[AGG_RESULT_IMAGP4:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
+// 32BIT-CLANG22-NEXT:    store double [[AGG_RESULT_REAL]], ptr [[AGG_RESULT_REALP3]], align 8
+// 32BIT-CLANG22-NEXT:    store double [[AGG_RESULT_IMAG]], ptr [[AGG_RESULT_IMAGP4]], align 8
+// 32BIT-CLANG22-NEXT:    ret void
 //
-// 32BITLE-LABEL: @testcmplx(
-// 32BITLE-NEXT:  entry:
-// 32BITLE-NEXT:    [[REAL_ADDR:%.*]] = alloca double, align 8
-// 32BITLE-NEXT:    [[IMAG_ADDR:%.*]] = alloca double, align 8
-// 32BITLE-NEXT:    store double [[REAL:%.*]], ptr [[REAL_ADDR]], align 8
-// 32BITLE-NEXT:    store double [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 8
-// 32BITLE-NEXT:    [[TMP0:%.*]] = load double, ptr [[REAL_ADDR]], align 8
-// 32BITLE-NEXT:    [[TMP1:%.*]] = load double, ptr [[IMAG_ADDR]], align 8
-// 32BITLE-NEXT:    [[AGG_RESULT_REALP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT:%.*]], i32 0, i32 0
-// 32BITLE-NEXT:    [[AGG_RESULT_IMAGP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BITLE-NEXT:    store double [[TMP0]], ptr [[AGG_RESULT_REALP]], align 8
-// 32BITLE-NEXT:    store double [[TMP1]], ptr [[AGG_RESULT_IMAGP]], align 8
-// 32BITLE-NEXT:    [[AGG_RESULT_REALP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
-// 32BITLE-NEXT:    [[AGG_RESULT_REAL:%.*]] = load double, ptr [[AGG_RESULT_REALP1]], align 8
-// 32BITLE-NEXT:    [[AGG_RESULT_IMAGP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BITLE-NEXT:    [[AGG_RESULT_IMAG:%.*]] = load double, ptr [[AGG_RESULT_IMAGP2]], align 8
-// 32BITLE-NEXT:    [[AGG_RESULT_REALP3:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
-// 32BITLE-NEXT:    [[AGG_RESULT_IMAGP4:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BITLE-NEXT:    store double [[AGG_RESULT_REAL]], ptr [[AGG_RESULT_REALP3]], align 8
-// 32BITLE-NEXT:    store double [[AGG_RESULT_IMAG]], ptr [[AGG_RESULT_IMAGP4]], align 8
-// 32BITLE-NEXT:    ret void
+// 32BIT-CLANG23-LABEL: @testcmplx(
+// 32BIT-CLANG23-NEXT:  entry:
+// 32BIT-CLANG23-NEXT:    [[RETVAL:%.*]] = alloca { double, double }, align 8
+// 32BIT-CLANG23-NEXT:    [[REAL_ADDR:%.*]] = alloca double, align 8
+// 32BIT-CLANG23-NEXT:    [[IMAG_ADDR:%.*]] = alloca double, align 8
+// 32BIT-CLANG23-NEXT:    store double [[REAL:%.*]], ptr [[REAL_ADDR]], align 8
+// 32BIT-CLANG23-NEXT:    store double [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 8
+// 32BIT-CLANG23-NEXT:    [[TMP0:%.*]] = load double, ptr [[REAL_ADDR]], align 8
+// 32BIT-CLANG23-NEXT:    [[TMP1:%.*]] = load double, ptr [[IMAG_ADDR]], align 8
+// 32BIT-CLANG23-NEXT:    [[RETVAL_REALP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RETVAL]], i32 0, i32 0
+// 32BIT-CLANG23-NEXT:    [[RETVAL_IMAGP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RETVAL]], i32 0, i32 1
+// 32BIT-CLANG23-NEXT:    store double [[TMP0]], ptr [[RETVAL_REALP]], align 8
+// 32BIT-CLANG23-NEXT:    store double [[TMP1]], ptr [[RETVAL_IMAGP]], align 8
+// 32BIT-CLANG23-NEXT:    [[TMP2:%.*]] = load [4 x i32], ptr [[RETVAL]], align 8
+// 32BIT-CLANG23-NEXT:    ret [4 x i32] [[TMP2]]
+//
+// 32BITLE-CLANG22-LABEL: @testcmplx(
+// 32BITLE-CLANG22-NEXT:  entry:
+// 32BITLE-CLANG22-NEXT:    [[REAL_ADDR:%.*]] = alloca double, align 8
+// 32BITLE-CLANG22-NEXT:    [[IMAG_ADDR:%.*]] = alloca double, align 8
+// 32BITLE-CLANG22-NEXT:    store double [[REAL:%.*]], ptr [[REAL_ADDR]], align 8
+// 32BITLE-CLANG22-NEXT:    store double [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 8
+// 32BITLE-CLANG22-NEXT:    [[TMP0:%.*]] = load double, ptr [[REAL_ADDR]], align 8
+// 32BITLE-CLANG22-NEXT:    [[TMP1:%.*]] = load double, ptr [[IMAG_ADDR]], align 8
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_REALP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT:%.*]], i32 0, i32 0
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_IMAGP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
+// 32BITLE-CLANG22-NEXT:    store double [[TMP0]], ptr [[AGG_RESULT_REALP]], align 8
+// 32BITLE-CLANG22-NEXT:    store double [[TMP1]], ptr [[AGG_RESULT_IMAGP]], align 8
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_REALP1:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_REAL:%.*]] = load double, ptr [[AGG_RESULT_REALP1]], align 8
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_IMAGP2:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_IMAG:%.*]] = load double, ptr [[AGG_RESULT_IMAGP2]], align 8
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_REALP3:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 0
+// 32BITLE-CLANG22-NEXT:    [[AGG_RESULT_IMAGP4:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[AGG_RESULT]], i32 0, i32 1
+// 32BITLE-CLANG22-NEXT:    store double [[AGG_RESULT_REAL]], ptr [[AGG_RESULT_REALP3]], align 8
+// 32BITLE-CLANG22-NEXT:    store double [[AGG_RESULT_IMAG]], ptr [[AGG_RESULT_IMAGP4]], align 8
+// 32BITLE-CLANG22-NEXT:    ret void
+//
+// 32BITLE-CLANG23-LABEL: @testcmplx(
+// 32BITLE-CLANG23-NEXT:  entry:
+// 32BITLE-CLANG23-NEXT:    [[RETVAL:%.*]] = alloca { double, double }, align 8
+// 32BITLE-CLANG23-NEXT:    [[REAL_ADDR:%.*]] = alloca double, align 8
+// 32BITLE-CLANG23-NEXT:    [[IMAG_ADDR:%.*]] = alloca double, align 8
+// 32BITLE-CLANG23-NEXT:    store double [[REAL:%.*]], ptr [[REAL_ADDR]], align 8
+// 32BITLE-CLANG23-NEXT:    store double [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 8
+// 32BITLE-CLANG23-NEXT:    [[TMP0:%.*]] = load double, ptr [[REAL_ADDR]], align 8
+// 32BITLE-CLANG23-NEXT:    [[TMP1:%.*]] = load double, ptr [[IMAG_ADDR]], align 8
+// 32BITLE-CLANG23-NEXT:    [[RETVAL_REALP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RETVAL]], i32 0, i32 0
+// 32BITLE-CLANG23-NEXT:    [[RETVAL_IMAGP:%.*]] = getelementptr inbounds nuw { double, double }, ptr [[RETVAL]], i32 0, i32 1
+// 32BITLE-CLANG23-NEXT:    store double [[TMP0]], ptr [[RETVAL_REALP]], align 8
+// 32BITLE-CLANG23-NEXT:    store double [[TMP1]], ptr [[RETVAL_IMAGP]], align 8
+// 32BITLE-CLANG23-NEXT:    [[TMP2:%.*]] = load [4 x i32], ptr [[RETVAL]], align 8
+// 32BITLE-CLANG23-NEXT:    ret [4 x i32] [[TMP2]]
 //
 // 32BITAIX-LABEL: @testcmplx(
 // 32BITAIX-NEXT:  entry:
@@ -173,49 +209,81 @@ double _Complex testcmplx(double real, double imag) {
 // 64BITAIX-NEXT:    [[TMP2:%.*]] = load { float, float }, ptr [[RETVAL]], align 4
 // 64BITAIX-NEXT:    ret { float, float } [[TMP2]]
 //
-// 32BIT-LABEL: @testcmplxf(
-// 32BIT-NEXT:  entry:
-// 32BIT-NEXT:    [[REAL_ADDR:%.*]] = alloca float, align 4
-// 32BIT-NEXT:    [[IMAG_ADDR:%.*]] = alloca float, align 4
-// 32BIT-NEXT:    store float [[REAL:%.*]], ptr [[REAL_ADDR]], align 4
-// 32BIT-NEXT:    store float [[IMAG:%.*]], ptr [[IMAG_ADDR]], align 4
-// 32BIT-NEXT:    [[TMP0:%.*]] = load float, ptr [[REAL_ADDR]], align 4
-// 32BIT-NEXT:    [[TMP1:%.*]] = load float, ptr [[IMAG_ADDR]], align 4
-// 32BIT-NEXT:    [[AGG_RESULT_REALP:%.*]] = getelementptr inbounds nuw { float, float }, ptr [[AGG_RESULT:%.*]], i32 0, i32 0
-// 32BIT-NEXT:    [[AGG_RESULT_IMAGP:%.*]] = getelementptr inbounds nuw { float, float }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BIT-NEXT:    store float [[TMP0]], ptr [[AGG_RESULT_REALP]], align 4
-// 32BIT-NEXT:    store float [[TMP1]], ptr [[AGG_RESULT_IMAGP]], align 4
-// 32BIT-NEXT:    [[AGG_RESULT_REALP1:%.*]] = getelementptr inbounds nuw { float, float }, ptr [[AGG_RESULT]], i32 0, i32 0
-// 32BIT-NEXT:    [[AGG_RESULT_REAL:%.*]] = load float, ptr [[AGG_RESULT_REALP1]], align 4
-// 32BIT-NEXT:    [[AGG_RESULT_IMAGP2:%.*]] = getelementptr inbounds nuw { float, float }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BIT-NEXT:    [[AGG_RESULT_IMAG:%.*]] = load float, ptr [[AGG_RESULT_IMAGP2]], align 4
-// 32BIT-NEXT:    [[AGG_RESULT_REALP3:%.*]] = getelementptr inbounds nuw { float, float }, ptr [[AGG_RESULT]], i32 0, i32 0
-// 32BIT-NEXT:    [[AGG_RESULT_IMAGP4:%.*]] = getelementptr inbounds nuw { float, float }, ptr [[AGG_RESULT]], i32 0, i32 1
-// 32BIT-NEXT:    store float [[AGG_RESULT_REAL]], ptr [[AGG_RESULT_REALP3]], align 4
-// 32BIT-NEXT:    store float [[AGG_RESULT_IMAG]], ptr [[AGG_RESULT_IMAGP4]], align 4
-// 32BIT-NEXT:    ret void
+// 32BIT-CLANG22-LABEL: @testcmplxf(
+// 32BIT-CLANG22-NEXT:  entry:
+// 32BIT-CLANG22-NEXT:    [[REAL_ADDR:%.*]] = alloca float, align 4
+// 32BIT-CLANG22-NEXT:    [[IMAG_ADDR:%.*]] = alloca float, align 4
+// 32BIT-CLANG22-NEXT:    store float [[REAL:%.*]], ptr [[REAL_ADDR]], align 4
+// 32BIT-CLANG22-NEXT:    store float [[IMAG:%.*]], ptr [[IMAG...
[truncated]

static const unsigned GPRBits = 32;

bool isComplexGnuABI() const {
return !getTarget().getTriple().isOSDarwin() &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jrtc27, should the opt-out apply also to FreeBSD ppc32?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not, FreeBSD tends to follow Linux in these matters? But IANA PowerPC person (nor really a floating-point person). CC @chmeeedalf.

@folkertdev

Copy link
Copy Markdown
Contributor Author

What is maybe not clear from the issue and PR description is that this ABI mismatch means libcalls are just broken in practice, e.g.

// main.c
#include <stdio.h>
#include <complex.h>

int main(void) {
    volatile float re = -1.0f, im = 0.0f;
    _Complex float s = csqrtf(re + im * I);
    printf("%f + %fi\n", crealf(s), cimagf(s));
    return 0;
}

with

powerpc-linux-gnu-gcc -O2 main.c -o gcc.out -lm

# Use clang to compile, GCC as the linker.
clang --target=powerpc-linux-gnu -O2 -c main.c -o clang.o
powerpc-linux-gnu-gcc clang.o -o clang.out -lm

echo "gcc"
qemu-ppc -L /usr/powerpc-linux-gnu ./gcc.out
echo "clang"
qemu-ppc -L /usr/powerpc-linux-gnu ./clang.out

prints

gcc
0.000000 + 1.000000i
clang
4.186081 + 0.000000i

The clang result is nonsense.

@folkertdev
folkertdev force-pushed the fix-powerpc-complex-abi branch from 7f395d8 to b35bb13 Compare August 5, 2026 09:12
@folkertdev

Copy link
Copy Markdown
Contributor Author

Ping for the IBM folks @amy-kwan @daltenty @lei137.

The ABI is clearly broken, people have been running into this for years. Seems like something you'd want to see fixed.

I've updated the references from LLVM 22 -> 23.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 121316 tests passed
  • 5014 tests skipped

✅ The build succeeded and all tests passed.

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
@folkertdev
folkertdev requested a review from tonykuttai August 5, 2026 20:03
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
@folkertdev
folkertdev requested a review from lei137 August 10, 2026 21:14
@folkertdev
folkertdev force-pushed the fix-powerpc-complex-abi branch from 067d939 to 9d89852 Compare August 13, 2026 10:20
@folkertdev

Copy link
Copy Markdown
Contributor Author

Periodic ping @amy-kwan @daltenty @lei137 (idk maybe I just don't have the right people, if so, please recommend someone)

We're moving ahead with complex support on the rust side, and would really like to see this fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:PowerPC clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PowerPC ABI incompatibility with GNU on complex arguments

3 participants