Skip to content

[Sparc][clang] make _Complex ABI GCC-compatible - #212340

Merged
folkertdev merged 10 commits into
llvm:mainfrom
folkertdev:sparc-complex-abi
Aug 5, 2026
Merged

[Sparc][clang] make _Complex ABI GCC-compatible#212340
folkertdev merged 10 commits into
llvm:mainfrom
folkertdev:sparc-complex-abi

Conversation

@folkertdev

@folkertdev folkertdev commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

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:

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

llvmorg-github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-codegen

Author: Folkert de Vries (folkertdev)

Changes

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:


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

4 Files Affected:

  • (modified) clang/docs/ReleaseNotes.md (+11)
  • (modified) clang/include/clang/Basic/ABIVersions.def (+6)
  • (modified) clang/lib/CodeGen/Targets/Sparc.cpp (+45-3)
  • (added) clang/test/CodeGen/Sparc/sparc-complex-abi.c (+104)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 61e8378b1704a..a47270ea5219c 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -62,6 +62,17 @@ 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 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.
+
 ### 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..8d0e15ffc527a 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 SPARC, pass a `_Complex` value with an integer element type
+///     indirectly, and return it with one part per integer register, instead of
+///     packing it into the one or two registers it fits in.
+///   - On SPARC64, left-justify a `_Complex` value with an integer element type
+///     that is narrower than a parameter array slot, instead of right-justifying
+///     it the way every other sub-slot scalar is passed.
 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/Sparc.cpp b/clang/lib/CodeGen/Targets/Sparc.cpp
index 3fa4e84823d51..abc72468316f8 100644
--- a/clang/lib/CodeGen/Targets/Sparc.cpp
+++ b/clang/lib/CodeGen/Targets/Sparc.cpp
@@ -13,6 +13,13 @@
 using namespace clang;
 using namespace clang::CodeGen;
 
+/// Whether `_Complex` values with an integer element type are passed and
+/// returned the way GCC passes and returns them.
+static bool isComplexGnuABI(const ABIInfo &Info) {
+  return !Info.getContext().getLangOpts().isCompatibleWith(
+      LangOptions::ClangABI::Ver23);
+}
+
 //===----------------------------------------------------------------------===//
 // SPARC v8 ABI Implementation.
 // Based on the SPARC Compliance Definition version 2.4.1.
@@ -25,12 +32,30 @@ class SparcV8ABIInfo : public DefaultABIInfo {
   SparcV8ABIInfo(CodeGenTypes &CGT) : DefaultABIInfo(CGT) {}
 
 private:
+  llvm::Type *getComplexIntCoerceType(QualType Ty) const;
   ABIArgInfo classifyReturnType(QualType RetTy) const;
   ABIArgInfo classifyArgumentType(QualType Ty) const;
   void computeInfo(CGFunctionInfo &FI) const override;
 };
 } // end anonymous namespace
 
+llvm::Type *SparcV8ABIInfo::getComplexIntCoerceType(QualType Ty) const {
+  if (!isComplexGnuABI(*this))
+    return nullptr;
+
+  const auto *CT = Ty->getAs<ComplexType>();
+  if (!CT || !CT->getElementType()->isIntegerType())
+    return nullptr;
+
+  // The default path already does the right thing for `long long _Complex`.
+  uint64_t Size = getContext().getTypeSize(Ty);
+  if (Size > 64)
+    return nullptr;
+
+  // Coerce to an integer to get the correct scalar-like behavior.
+  return llvm::IntegerType::get(getVMContext(), Size);
+}
+
 ABIArgInfo SparcV8ABIInfo::classifyReturnType(QualType Ty) const {
   const auto *CT = Ty->getAs<ComplexType>();
   const auto *BT = Ty->getAs<BuiltinType>();
@@ -39,9 +64,13 @@ ABIArgInfo SparcV8ABIInfo::classifyReturnType(QualType Ty) const {
   bool IsLongDouble = BT && BT->getKind() == BuiltinType::LongDouble;
 
   // long double _Complex is special in that it should be marked as inreg.
-  if (CT)
-    return IsLongDouble ? ABIArgInfo::getDirectInReg()
-                        : ABIArgInfo::getDirect();
+  if (CT) {
+    if (IsLongDouble)
+      return ABIArgInfo::getDirectInReg();
+    if (llvm::Type *CoerceTy = getComplexIntCoerceType(Ty))
+      return ABIArgInfo::getDirect(CoerceTy);
+    return ABIArgInfo::getDirect();
+  }
 
   if (IsLongDouble)
     return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
@@ -55,6 +84,10 @@ ABIArgInfo SparcV8ABIInfo::classifyArgumentType(QualType Ty) const {
       BT && BT->getKind() == BuiltinType::LongDouble)
     return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace());
 
+  // Complex integers go in registers.
+  if (llvm::Type *CoerceTy = getComplexIntCoerceType(Ty))
+    return ABIArgInfo::getDirect(CoerceTy);
+
   return DefaultABIInfo::classifyArgumentType(Ty);
 }
 
@@ -277,6 +310,15 @@ ABIArgInfo SparcV9ABIInfo::classifyType(QualType Ty, unsigned SizeLimit,
       return ABIArgInfo::getExtend(Ty);
     }
 
+  // When being GCC-compatible, cast a complex integer to an integer type
+  // of the right size to get the correct scalar-like behavior.
+  if (isComplexGnuABI(*this))
+    if (const auto *CT = Ty->getAs<ComplexType>();
+        CT && Size < 64 && CT->getElementType()->isIntegerType()) {
+      RegOffset += 1;
+      return ABIArgInfo::getDirect(llvm::IntegerType::get(VMContext, Size));
+    }
+
   // Other non-aggregates go in registers.
   if (!isAggregateTypeForABI(Ty)) {
     RegOffset += Size / 64;
diff --git a/clang/test/CodeGen/Sparc/sparc-complex-abi.c b/clang/test/CodeGen/Sparc/sparc-complex-abi.c
new file mode 100644
index 0000000000000..6f822134bdb0b
--- /dev/null
+++ b/clang/test/CodeGen/Sparc/sparc-complex-abi.c
@@ -0,0 +1,104 @@
+// RUN: %clang_cc1 -triple sparc-unknown-linux-gnu -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=V8
+// RUN: %clang_cc1 -triple sparcv9-unknown-linux-gnu -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=V9
+
+// RUN: %clang_cc1 -triple sparc-unknown-linux-gnu -fclang-abi-compat=23 \
+// RUN:   -emit-llvm -o - %s | FileCheck %s --check-prefix=COMPAT23-V8
+// RUN: %clang_cc1 -triple sparcv9-unknown-linux-gnu -fclang-abi-compat=23 \
+// RUN:   -emit-llvm -o - %s | FileCheck %s --check-prefix=COMPAT23-V9
+
+// Test how SPARC passes and returns `_Complex` values.
+
+// Returns.
+//
+// A `_Complex` value with an integer element type is returned packed into whole
+// integer registers. Clang 23 and before instead gave each part a register of
+// its own on v8, and on v9 left-justified a value narrower than a register the
+// way a small struct is returned. The new behavior matches GCC.
+
+// COMPAT23-V8-LABEL: define{{.*}} { i8, i8 } @ret_complex_char(
+// V8-LABEL:          define{{.*}} i16 @ret_complex_char(
+// COMPAT23-V9-LABEL: define{{.*}} i64 @ret_complex_char(
+// V9-LABEL:          define{{.*}} i16 @ret_complex_char(
+_Complex char ret_complex_char(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { i16, i16 } @ret_complex_short(
+// V8-LABEL:          define{{.*}} i32 @ret_complex_short(
+// COMPAT23-V9-LABEL: define{{.*}} i64 @ret_complex_short(
+// V9-LABEL:          define{{.*}} i32 @ret_complex_short(
+_Complex short ret_complex_short(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { i32, i32 } @ret_complex_int(
+// V8-LABEL:          define{{.*}} i64 @ret_complex_int(
+// COMPAT23-V9-LABEL: define{{.*}} i64 @ret_complex_int(
+// V9-LABEL:          define{{.*}} i64 @ret_complex_int(
+_Complex int ret_complex_int(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { i64, i64 } @ret_complex_long_long(
+// V8-LABEL:          define{{.*}} { i64, i64 } @ret_complex_long_long(
+// COMPAT23-V9-LABEL: define{{.*}} { i64, i64 } @ret_complex_long_long(
+// V9-LABEL:          define{{.*}} { i64, i64 } @ret_complex_long_long(
+_Complex long long ret_complex_long_long(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { float, float } @ret_complex_float(
+// V8-LABEL:          define{{.*}} { float, float } @ret_complex_float(
+// COMPAT23-V9-LABEL: define{{.*}} inreg { float, float } @ret_complex_float(
+// V9-LABEL:          define{{.*}} inreg { float, float } @ret_complex_float(
+_Complex float ret_complex_float(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { double, double } @ret_complex_double(
+// V8-LABEL:          define{{.*}} { double, double } @ret_complex_double(
+// COMPAT23-V9-LABEL: define{{.*}} { double, double } @ret_complex_double(
+// V9-LABEL:          define{{.*}} { double, double } @ret_complex_double(
+_Complex double ret_complex_double(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} inreg { fp128, fp128 } @ret_complex_long_double(
+// V8-LABEL:          define{{.*}} inreg { fp128, fp128 } @ret_complex_long_double(
+// COMPAT23-V9-LABEL: define{{.*}} { fp128, fp128 } @ret_complex_long_double(
+// V9-LABEL:          define{{.*}} { fp128, fp128 } @ret_complex_long_double(
+_Complex long double ret_complex_long_double(void) { return 0; }
+
+// Arguments.
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_char(ptr noundef byval({ i8, i8 }) align 1 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_char(i16 noundef %c.coerce)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_char(i64 %c.coerce)
+// V9-LABEL:          define{{.*}} void @arg_complex_char(i16 noundef %c.coerce)
+void arg_complex_char(_Complex char c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_short(ptr noundef byval({ i16, i16 }) align 2 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_short(i32 noundef %c.coerce)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_short(i64 %c.coerce)
+// V9-LABEL:          define{{.*}} void @arg_complex_short(i32 noundef %c.coerce)
+void arg_complex_short(_Complex short c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_int(ptr noundef byval({ i32, i32 }) align 4 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_int(i64 noundef %c.coerce)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_int(i64 noundef %c.coerce)
+// V9-LABEL:          define{{.*}} void @arg_complex_int(i64 noundef %c.coerce)
+void arg_complex_int(_Complex int c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_long_long(ptr noundef byval({ i64, i64 }) align 8 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_long_long(ptr noundef byval({ i64, i64 }) align 8 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_long_long(i64 noundef %c.coerce0, i64 noundef %c.coerce1)
+// V9-LABEL:          define{{.*}} void @arg_complex_long_long(i64 noundef %c.coerce0, i64 noundef %c.coerce1)
+void arg_complex_long_long(_Complex long long c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_float(ptr noundef byval({ float, float }) align 4 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_float(ptr noundef byval({ float, float }) align 4 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_float(float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+// V9-LABEL:          define{{.*}} void @arg_complex_float(float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+void arg_complex_float(_Complex float c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_double(ptr noundef byval({ double, double }) align 8 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_double(ptr noundef byval({ double, double }) align 8 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_double(double noundef %c.coerce0, double noundef %c.coerce1)
+// V9-LABEL:          define{{.*}} void @arg_complex_double(double noundef %c.coerce0, double noundef %c.coerce1)
+void arg_complex_double(_Complex double c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_long_double(ptr noundef byval({ fp128, fp128 }) align 8 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_long_double(ptr noundef byval({ fp128, fp128 }) align 8 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_long_double(ptr noundef align 16 dead_on_return %c)
+// V9-LABEL:          define{{.*}} void @arg_complex_long_double(ptr noundef align 16 dead_on_return %c)
+void arg_complex_long_double(_Complex long double c) {}

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-sparc

Author: Folkert de Vries (folkertdev)

Changes

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:


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

4 Files Affected:

  • (modified) clang/docs/ReleaseNotes.md (+11)
  • (modified) clang/include/clang/Basic/ABIVersions.def (+6)
  • (modified) clang/lib/CodeGen/Targets/Sparc.cpp (+45-3)
  • (added) clang/test/CodeGen/Sparc/sparc-complex-abi.c (+104)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 61e8378b1704a..a47270ea5219c 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -62,6 +62,17 @@ 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 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.
+
 ### 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..8d0e15ffc527a 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 SPARC, pass a `_Complex` value with an integer element type
+///     indirectly, and return it with one part per integer register, instead of
+///     packing it into the one or two registers it fits in.
+///   - On SPARC64, left-justify a `_Complex` value with an integer element type
+///     that is narrower than a parameter array slot, instead of right-justifying
+///     it the way every other sub-slot scalar is passed.
 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/Sparc.cpp b/clang/lib/CodeGen/Targets/Sparc.cpp
index 3fa4e84823d51..abc72468316f8 100644
--- a/clang/lib/CodeGen/Targets/Sparc.cpp
+++ b/clang/lib/CodeGen/Targets/Sparc.cpp
@@ -13,6 +13,13 @@
 using namespace clang;
 using namespace clang::CodeGen;
 
+/// Whether `_Complex` values with an integer element type are passed and
+/// returned the way GCC passes and returns them.
+static bool isComplexGnuABI(const ABIInfo &Info) {
+  return !Info.getContext().getLangOpts().isCompatibleWith(
+      LangOptions::ClangABI::Ver23);
+}
+
 //===----------------------------------------------------------------------===//
 // SPARC v8 ABI Implementation.
 // Based on the SPARC Compliance Definition version 2.4.1.
@@ -25,12 +32,30 @@ class SparcV8ABIInfo : public DefaultABIInfo {
   SparcV8ABIInfo(CodeGenTypes &CGT) : DefaultABIInfo(CGT) {}
 
 private:
+  llvm::Type *getComplexIntCoerceType(QualType Ty) const;
   ABIArgInfo classifyReturnType(QualType RetTy) const;
   ABIArgInfo classifyArgumentType(QualType Ty) const;
   void computeInfo(CGFunctionInfo &FI) const override;
 };
 } // end anonymous namespace
 
+llvm::Type *SparcV8ABIInfo::getComplexIntCoerceType(QualType Ty) const {
+  if (!isComplexGnuABI(*this))
+    return nullptr;
+
+  const auto *CT = Ty->getAs<ComplexType>();
+  if (!CT || !CT->getElementType()->isIntegerType())
+    return nullptr;
+
+  // The default path already does the right thing for `long long _Complex`.
+  uint64_t Size = getContext().getTypeSize(Ty);
+  if (Size > 64)
+    return nullptr;
+
+  // Coerce to an integer to get the correct scalar-like behavior.
+  return llvm::IntegerType::get(getVMContext(), Size);
+}
+
 ABIArgInfo SparcV8ABIInfo::classifyReturnType(QualType Ty) const {
   const auto *CT = Ty->getAs<ComplexType>();
   const auto *BT = Ty->getAs<BuiltinType>();
@@ -39,9 +64,13 @@ ABIArgInfo SparcV8ABIInfo::classifyReturnType(QualType Ty) const {
   bool IsLongDouble = BT && BT->getKind() == BuiltinType::LongDouble;
 
   // long double _Complex is special in that it should be marked as inreg.
-  if (CT)
-    return IsLongDouble ? ABIArgInfo::getDirectInReg()
-                        : ABIArgInfo::getDirect();
+  if (CT) {
+    if (IsLongDouble)
+      return ABIArgInfo::getDirectInReg();
+    if (llvm::Type *CoerceTy = getComplexIntCoerceType(Ty))
+      return ABIArgInfo::getDirect(CoerceTy);
+    return ABIArgInfo::getDirect();
+  }
 
   if (IsLongDouble)
     return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace(),
@@ -55,6 +84,10 @@ ABIArgInfo SparcV8ABIInfo::classifyArgumentType(QualType Ty) const {
       BT && BT->getKind() == BuiltinType::LongDouble)
     return getNaturalAlignIndirect(Ty, getDataLayout().getAllocaAddrSpace());
 
+  // Complex integers go in registers.
+  if (llvm::Type *CoerceTy = getComplexIntCoerceType(Ty))
+    return ABIArgInfo::getDirect(CoerceTy);
+
   return DefaultABIInfo::classifyArgumentType(Ty);
 }
 
@@ -277,6 +310,15 @@ ABIArgInfo SparcV9ABIInfo::classifyType(QualType Ty, unsigned SizeLimit,
       return ABIArgInfo::getExtend(Ty);
     }
 
+  // When being GCC-compatible, cast a complex integer to an integer type
+  // of the right size to get the correct scalar-like behavior.
+  if (isComplexGnuABI(*this))
+    if (const auto *CT = Ty->getAs<ComplexType>();
+        CT && Size < 64 && CT->getElementType()->isIntegerType()) {
+      RegOffset += 1;
+      return ABIArgInfo::getDirect(llvm::IntegerType::get(VMContext, Size));
+    }
+
   // Other non-aggregates go in registers.
   if (!isAggregateTypeForABI(Ty)) {
     RegOffset += Size / 64;
diff --git a/clang/test/CodeGen/Sparc/sparc-complex-abi.c b/clang/test/CodeGen/Sparc/sparc-complex-abi.c
new file mode 100644
index 0000000000000..6f822134bdb0b
--- /dev/null
+++ b/clang/test/CodeGen/Sparc/sparc-complex-abi.c
@@ -0,0 +1,104 @@
+// RUN: %clang_cc1 -triple sparc-unknown-linux-gnu -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=V8
+// RUN: %clang_cc1 -triple sparcv9-unknown-linux-gnu -emit-llvm -o - %s \
+// RUN:   | FileCheck %s --check-prefix=V9
+
+// RUN: %clang_cc1 -triple sparc-unknown-linux-gnu -fclang-abi-compat=23 \
+// RUN:   -emit-llvm -o - %s | FileCheck %s --check-prefix=COMPAT23-V8
+// RUN: %clang_cc1 -triple sparcv9-unknown-linux-gnu -fclang-abi-compat=23 \
+// RUN:   -emit-llvm -o - %s | FileCheck %s --check-prefix=COMPAT23-V9
+
+// Test how SPARC passes and returns `_Complex` values.
+
+// Returns.
+//
+// A `_Complex` value with an integer element type is returned packed into whole
+// integer registers. Clang 23 and before instead gave each part a register of
+// its own on v8, and on v9 left-justified a value narrower than a register the
+// way a small struct is returned. The new behavior matches GCC.
+
+// COMPAT23-V8-LABEL: define{{.*}} { i8, i8 } @ret_complex_char(
+// V8-LABEL:          define{{.*}} i16 @ret_complex_char(
+// COMPAT23-V9-LABEL: define{{.*}} i64 @ret_complex_char(
+// V9-LABEL:          define{{.*}} i16 @ret_complex_char(
+_Complex char ret_complex_char(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { i16, i16 } @ret_complex_short(
+// V8-LABEL:          define{{.*}} i32 @ret_complex_short(
+// COMPAT23-V9-LABEL: define{{.*}} i64 @ret_complex_short(
+// V9-LABEL:          define{{.*}} i32 @ret_complex_short(
+_Complex short ret_complex_short(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { i32, i32 } @ret_complex_int(
+// V8-LABEL:          define{{.*}} i64 @ret_complex_int(
+// COMPAT23-V9-LABEL: define{{.*}} i64 @ret_complex_int(
+// V9-LABEL:          define{{.*}} i64 @ret_complex_int(
+_Complex int ret_complex_int(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { i64, i64 } @ret_complex_long_long(
+// V8-LABEL:          define{{.*}} { i64, i64 } @ret_complex_long_long(
+// COMPAT23-V9-LABEL: define{{.*}} { i64, i64 } @ret_complex_long_long(
+// V9-LABEL:          define{{.*}} { i64, i64 } @ret_complex_long_long(
+_Complex long long ret_complex_long_long(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { float, float } @ret_complex_float(
+// V8-LABEL:          define{{.*}} { float, float } @ret_complex_float(
+// COMPAT23-V9-LABEL: define{{.*}} inreg { float, float } @ret_complex_float(
+// V9-LABEL:          define{{.*}} inreg { float, float } @ret_complex_float(
+_Complex float ret_complex_float(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} { double, double } @ret_complex_double(
+// V8-LABEL:          define{{.*}} { double, double } @ret_complex_double(
+// COMPAT23-V9-LABEL: define{{.*}} { double, double } @ret_complex_double(
+// V9-LABEL:          define{{.*}} { double, double } @ret_complex_double(
+_Complex double ret_complex_double(void) { return 0; }
+
+// COMPAT23-V8-LABEL: define{{.*}} inreg { fp128, fp128 } @ret_complex_long_double(
+// V8-LABEL:          define{{.*}} inreg { fp128, fp128 } @ret_complex_long_double(
+// COMPAT23-V9-LABEL: define{{.*}} { fp128, fp128 } @ret_complex_long_double(
+// V9-LABEL:          define{{.*}} { fp128, fp128 } @ret_complex_long_double(
+_Complex long double ret_complex_long_double(void) { return 0; }
+
+// Arguments.
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_char(ptr noundef byval({ i8, i8 }) align 1 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_char(i16 noundef %c.coerce)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_char(i64 %c.coerce)
+// V9-LABEL:          define{{.*}} void @arg_complex_char(i16 noundef %c.coerce)
+void arg_complex_char(_Complex char c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_short(ptr noundef byval({ i16, i16 }) align 2 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_short(i32 noundef %c.coerce)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_short(i64 %c.coerce)
+// V9-LABEL:          define{{.*}} void @arg_complex_short(i32 noundef %c.coerce)
+void arg_complex_short(_Complex short c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_int(ptr noundef byval({ i32, i32 }) align 4 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_int(i64 noundef %c.coerce)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_int(i64 noundef %c.coerce)
+// V9-LABEL:          define{{.*}} void @arg_complex_int(i64 noundef %c.coerce)
+void arg_complex_int(_Complex int c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_long_long(ptr noundef byval({ i64, i64 }) align 8 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_long_long(ptr noundef byval({ i64, i64 }) align 8 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_long_long(i64 noundef %c.coerce0, i64 noundef %c.coerce1)
+// V9-LABEL:          define{{.*}} void @arg_complex_long_long(i64 noundef %c.coerce0, i64 noundef %c.coerce1)
+void arg_complex_long_long(_Complex long long c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_float(ptr noundef byval({ float, float }) align 4 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_float(ptr noundef byval({ float, float }) align 4 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_float(float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+// V9-LABEL:          define{{.*}} void @arg_complex_float(float inreg noundef %c.coerce0, float inreg noundef %c.coerce1)
+void arg_complex_float(_Complex float c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_double(ptr noundef byval({ double, double }) align 8 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_double(ptr noundef byval({ double, double }) align 8 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_double(double noundef %c.coerce0, double noundef %c.coerce1)
+// V9-LABEL:          define{{.*}} void @arg_complex_double(double noundef %c.coerce0, double noundef %c.coerce1)
+void arg_complex_double(_Complex double c) {}
+
+// COMPAT23-V8-LABEL: define{{.*}} void @arg_complex_long_double(ptr noundef byval({ fp128, fp128 }) align 8 %c)
+// V8-LABEL:          define{{.*}} void @arg_complex_long_double(ptr noundef byval({ fp128, fp128 }) align 8 %c)
+// COMPAT23-V9-LABEL: define{{.*}} void @arg_complex_long_double(ptr noundef align 16 dead_on_return %c)
+// V9-LABEL:          define{{.*}} void @arg_complex_long_double(ptr noundef align 16 dead_on_return %c)
+void arg_complex_long_double(_Complex long double c) {}

@folkertdev
folkertdev requested review from jrtc27 and koachan July 29, 2026 12:13
@koachan
koachan requested a review from s-barannikov August 1, 2026 01:13
Comment thread clang/test/CodeGen/Sparc/sparc-complex-abi.c Outdated
Comment thread clang/test/CodeGen/Sparc/sparc-complex-abi.c Outdated
Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
@brad0
brad0 requested a review from MaskRay August 1, 2026 05:39

@s-barannikov s-barannikov left a comment

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.

I'm not sure we need -fclang-abi-compat flag, but I'm not an interested party.
@rorth @brad0

Also, are _Complex types passed in memory the same way? (when we'are out of argument registers)

Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
Comment on lines +120 to +121
// V9-NEXT: store i16 [[C_REAL]], ptr [[RETVAL_REALP]], align 2
// V9-NEXT: store i16 [[C_IMAG]], ptr [[RETVAL_IMAGP]], align 2

@s-barannikov s-barannikov Aug 1, 2026

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.

I suspect the order of parts should be reversed on little-endian SPARC

[offtopic]
but is sparc-el even real?.. gcc doesn't think so. I wonder if sparc-el support in clang/llvm is actually functional and whether we can drop it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This PR does not touch sparcel codegen at all

case llvm::Triple::sparc:
return createSparcV8TargetCodeGenInfo(CGM);
case llvm::Triple::sparcv9:
return createSparcV9TargetCodeGenInfo(CGM);

That falls through to the default sparcel. So, I don't think that EL sparc is relevant for this PR (there is no GCC to be compatible with anyway).

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.

That falls through to the default sparcel.

Interesting. So we don't support it properly after all

@folkertdev folkertdev left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure we need -fclang-abi-compat flag, but I'm not an interested party.

It's just a courtesy, and quite cheap to support. I'm happy to remove it though if it's not required. (it was the recommended strategy for a similar change in the powerpc backend, see #77732 (comment))

Also, are _Complex types passed in memory the same way? (when we'are out of argument registers)

the same way as what?

Comment on lines +120 to +121
// V9-NEXT: store i16 [[C_REAL]], ptr [[RETVAL_REALP]], align 2
// V9-NEXT: store i16 [[C_IMAG]], ptr [[RETVAL_IMAGP]], align 2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This PR does not touch sparcel codegen at all

case llvm::Triple::sparc:
return createSparcV8TargetCodeGenInfo(CGM);
case llvm::Triple::sparcv9:
return createSparcV9TargetCodeGenInfo(CGM);

That falls through to the default sparcel. So, I don't think that EL sparc is relevant for this PR (there is no GCC to be compatible with anyway).

Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
@folkertdev
folkertdev requested a review from s-barannikov August 2, 2026 12:08
@s-barannikov

Copy link
Copy Markdown
Contributor

Also, are _Complex types passed in memory the same way? (when we'are out of argument registers)

the same way as what?

The same way as in registers. When, e.g., _Complex char is passed in a register, it is packed into the lowest 16 bits of the register. If the argument is forced to stack (because it's 10-th argument of a function or so), should it be packed in memory too? Or should it be passed like a struct of two fields in this case (one field per stack slot)?
I suspect there should be no difference, but wanted to confirm.

@folkertdev

Copy link
Copy Markdown
Contributor Author

Yes it's the same, that is what the implementation already does, and it's consistent with GCC.

@s-barannikov s-barannikov left a comment

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.

LGTM, thanks
Please wait a few days in case other reviewers have something to add

Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated

// When being GCC-compatible, cast a complex char, short and int to an integer
// type of the right size to get the correct scalar-like behavior. Other
// comple types fall through and are treated like any other struct, e.g.

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.

Suggested change
// comple types fall through and are treated like any other struct, e.g.
// complex types fall through and are treated like any other struct, e.g.

(nit) "like any other struct" is a bit misleading because _Complex is not a struct type. Maybe something like "like a struct containing the real and imaginary parts"?

Comment thread clang/lib/CodeGen/Targets/Sparc.cpp Outdated
@folkertdev
folkertdev merged commit 08396d7 into llvm:main Aug 5, 2026
13 checks passed
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:Sparc 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.

3 participants