Author: Folkert de Vries Date: 2026-07-29T14:11:12+02:00 New Revision: f53596de0e42e9df10831cb50438bc8b85f826b3
URL: https://github.com/llvm/llvm-project/commit/f53596de0e42e9df10831cb50438bc8b85f826b3 DIFF: https://github.com/llvm/llvm-project/commit/f53596de0e42e9df10831cb50438bc8b85f826b3.diff LOG: [MIPS][clang] make `_Complex` ABI match GCC (#212119) fixes https://github.com/llvm/llvm-project/issues/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) Added: clang/test/CodeGen/mips-complex-abi.c Modified: clang/docs/ReleaseNotes.md clang/include/clang/Basic/ABIVersions.def clang/lib/CodeGen/Targets/Mips.cpp Removed: ################################################################################ diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index f631d9f858f9f..0736df269e67a 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -62,6 +62,18 @@ 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 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) + ### 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..42ee3c3b5f210 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 MIPS, return a `_Complex` value with an integer element type with one +/// part per integer register, instead of packing it into a single register +/// where it fits. +/// - On MIPS N32/N64, always pass a `_Complex float` or `_Complex double` +/// argument as its two parts, one floating-point register each, instead of +/// packing it into integer registers once there is no room for both. 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..c093cfc668c2e 100644 --- a/clang/lib/CodeGen/Targets/Mips.cpp +++ b/clang/lib/CodeGen/Targets/Mips.cpp @@ -23,9 +23,22 @@ class MipsABIInfo : public ABIInfo { const unsigned MinABIStackAlignInBytes, StackAlignInBytes; void CoerceToIntArgs(uint64_t TySize, SmallVectorImpl<llvm::Type *> &ArgList) const; - llvm::Type* HandleAggregates(QualType Ty, uint64_t TySize) const; + llvm::Type *HandleAggregates(QualType Ty, uint64_t TySize, + bool ComplexFitsInFPRs) 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), @@ -142,7 +155,8 @@ void MipsABIInfo::CoerceToIntArgs( // In N32/64, an aligned double precision floating point field is passed in // a register. -llvm::Type* MipsABIInfo::HandleAggregates(QualType Ty, uint64_t TySize) const { +llvm::Type *MipsABIInfo::HandleAggregates(QualType Ty, uint64_t TySize, + bool ComplexFitsInFPRs) const { SmallVector<llvm::Type*, 8> ArgList, IntArgList; if (IsO32) { @@ -150,8 +164,15 @@ llvm::Type* MipsABIInfo::HandleAggregates(QualType Ty, uint64_t TySize) const { return llvm::StructType::get(getVMContext(), ArgList); } - if (Ty->isComplexType()) - return CGT.ConvertType(Ty); + // A `_Complex` value that stays in FPRs is passed as its two parts. + // When that does not fit, it is passed like an integer of the same size. + if (Ty->isComplexType()) { + if (ComplexFitsInFPRs) + return CGT.ConvertType(Ty); + + CoerceToIntArgs(TySize, ArgList); + return llvm::StructType::get(getVMContext(), ArgList); + } const RecordType *RT = Ty->getAsCanonical<RecordType>(); @@ -219,6 +240,26 @@ MipsABIInfo::classifyArgumentType(QualType Ty, uint64_t &Offset) const { unsigned CurrOffset = llvm::alignTo(Offset, Align); Offset = CurrOffset + llvm::alignTo(TySize, Align * 8) / 8; + // Only pass _Complex float and _Complex double in FPRs when there are 2 free + // slots, otherwise use GPRs (or the stack). + // + // _Complex long double never uses GPRs. Its parts are an FPR pair each, + // so passing them as they are puts each part in a pair and spills to + // the stack the parts that don't fit. + bool ComplexFitsInFPRs = true; + if (!IsO32 && Ty->isComplexType() && isComplexGnuABI() && TySize < 256) { + unsigned NumArgSlots = 8; + uint64_t SlotsUsed = CurrOffset / MinABIStackAlignInBytes; + if (SlotsUsed + 2 <= NumArgSlots) + // Claim 2 slots. Only a `_Complex float` needs this, + // a `_Complex double` is already two slots. + Offset = CurrOffset + 2 * MinABIStackAlignInBytes; + else + // Pass like an integer of the same size, packing both parts into GPRs + // (or the stack). + ComplexFitsInFPRs = false; + } + if (isAggregateTypeForABI(Ty) || Ty->isVectorType()) { // Ignore empty aggregates. if (TySize == 0) @@ -234,8 +275,8 @@ MipsABIInfo::classifyArgumentType(QualType Ty, uint64_t &Offset) const { // another structure type. Padding is inserted if the offset of the // aggregate is unaligned. ABIArgInfo ArgInfo = - ABIArgInfo::getDirect(HandleAggregates(Ty, TySize), 0, - getPaddingType(OrigOffset, CurrOffset)); + ABIArgInfo::getDirect(HandleAggregates(Ty, TySize, ComplexFitsInFPRs), + 0, getPaddingType(OrigOffset, CurrOffset)); ArgInfo.setInReg(true); return ArgInfo; } @@ -301,6 +342,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 +370,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..b0eb76af2809e --- /dev/null +++ b/clang/test/CodeGen/mips-complex-abi.c @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -triple mips-none-linux-gnu -emit-llvm -o - %s \ +// RUN: | FileCheck %s --check-prefixes=GPR,GPR32 +// RUN: %clang_cc1 -triple mipsel-none-linux-gnu -emit-llvm -o - %s \ +// RUN: | FileCheck %s --check-prefixes=GPR,GPR32 + +// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n32 -emit-llvm -o - %s \ +// RUN: | FileCheck %s --check-prefixes=GPR,GPR64 +// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n64 -emit-llvm -o - %s \ +// RUN: | FileCheck %s --check-prefixes=GPR,GPR64 +// RUN: %clang_cc1 -triple mips64el-none-linux-gnu -target-abi n64 -emit-llvm -o - %s \ +// RUN: | FileCheck %s --check-prefixes=GPR,GPR64 + +// RUN: %clang_cc1 -triple mips-none-linux-gnu -fclang-abi-compat=23 -emit-llvm -o - %s \ +// RUN: | FileCheck %s --check-prefixes=COMPAT23,COMPAT23-GPR32 +// RUN: %clang_cc1 -triple mips64-none-linux-gnu -target-abi n64 -fclang-abi-compat=23 \ +// RUN: -emit-llvm -o - %s | FileCheck %s --check-prefixes=COMPAT23,COMPAT23-GPR64 + +// Test how MIPS passes and returns `_Complex` values. + +// With Clang 23 and before, `_Complex {integer}` was returned in 2 registers. +// Later versions are compatible with GCC and pack such types into a single +// GPR if possible. + +// GPR-LABEL: define{{.*}} i16 @ret_complex_char( +// COMPAT23-LABEL: define{{.*}} { i8, i8 } @ret_complex_char( +_Complex char ret_complex_char(void) { return 0; } + +// GPR-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; } + +// GPR-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. + +// GPR-LABEL: define{{.*}} { float, float } @ret_complex_float( +// COMPAT23-LABEL: define{{.*}} { float, float } @ret_complex_float( +_Complex float ret_complex_float(void) { return 0; } + +// GPR-LABEL: define{{.*}} { double, double } @ret_complex_double( +// COMPAT23-LABEL: define{{.*}} { double, double } @ret_complex_double( +_Complex double ret_complex_double(void) { return 0; } + +// GPR32-LABEL: define{{.*}} { double, double } @ret_complex_long_double( +// COMPAT23-GPR32-LABEL: define{{.*}} { double, double } @ret_complex_long_double( +// GPR64-LABEL: define{{.*}} void @ret_complex_long_double(ptr {{.*}}sret({ fp128, fp128 }) +// COMPAT23-GPR64-LABEL: define{{.*}} void @ret_complex_long_double(ptr {{.*}}sret({ fp128, fp128 }) +_Complex long double ret_complex_long_double(void) { return 0; } + +// Arguments + +// GPR-LABEL: define{{.*}} void @arg_complex_char(i16 inreg noundef %c.coerce) +void arg_complex_char(_Complex char c) {} + +// GPR-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) {} + +// GPR32-LABEL: define{{.*}} void @arg_complex_long_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_long_double(fp128 inreg noundef %c.coerce0, fp128 inreg noundef %c.coerce1) +void arg_complex_long_double(_Complex long double c) {} + +// Straddling the FPR/GPR border + +// With Clang 23 and before, `_Complex float` and `_Complex` double were passed as two floats or doubles, +// even when that would not fit in the remaining float registers. Later versions match GCC, which will +// cast to one (float) or two (double) i64 values which are passed via GPRs (or the stack). + +// Just padding to fill the slots. +#define SIX_SLOTS long long a0, long long a1, long long a2, \ + long long a3, long long a4, long long a5 +#define SEVEN_SLOTS SIX_SLOTS, long long a6 + +// Six slots used, so two are still free and the FPR pair is still used. + +// GPR32-LABEL: define{{.*}} @arg_complex_float_6slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1) +// GPR64-LABEL: define{{.*}} @arg_complex_float_6slots(i64{{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1) +// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_float_6slots(i64{{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1) +void arg_complex_float_6slots(SIX_SLOTS, _Complex float c) {} + +// GPR32-LABEL: define{{.*}} @arg_complex_double_6slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3) +// GPR64-LABEL: define{{.*}} @arg_complex_double_6slots(i64{{.*}}, double inreg noundef %c.coerce0, double inreg noundef %c.coerce1) +// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_double_6slots(i64{{.*}}, double inreg noundef %c.coerce0, double inreg noundef %c.coerce1) +void arg_complex_double_6slots(SIX_SLOTS, _Complex double c) {} + +// GPR32-LABEL: define{{.*}} @arg_complex_float_7slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1) +// GPR64-LABEL: define{{.*}} @arg_complex_float_7slots(i64{{.*}}, i64 inreg noundef %c.coerce) +// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_float_7slots(i64{{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1) +void arg_complex_float_7slots(SEVEN_SLOTS, _Complex float c) {} + +// GPR32-LABEL: define{{.*}} @arg_complex_double_7slots(i64{{.*}}, i32 inreg noundef %c.coerce0, i32 inreg noundef %c.coerce1, i32 inreg noundef %c.coerce2, i32 inreg noundef %c.coerce3) +// GPR64-LABEL: define{{.*}} @arg_complex_double_7slots(i64{{.*}}, i64 inreg noundef %c.coerce0, i64 inreg noundef %c.coerce1) +// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_double_7slots(i64{{.*}}, double inreg noundef %c.coerce0, double inreg noundef %c.coerce1) +void arg_complex_double_7slots(SEVEN_SLOTS, _Complex double c) {} + +// GPR64-LABEL: define{{.*}} @arg_complex_float_after_3({{.*}}, float inreg noundef %c.coerce0, float inreg noundef %c.coerce1) +void arg_complex_float_after_3(_Complex float p0, _Complex float p1, _Complex float p2, _Complex float c) {} + +// GPR64-LABEL: define{{.*}} @arg_complex_float_after_4({{.*}}, i64 inreg noundef %c.coerce) +void arg_complex_float_after_4(_Complex float p0, _Complex float p1, _Complex float p2, _Complex float p3, _Complex float c) {} + +// GPR64-LABEL: define{{.*}} @arg_complex_long_double_7slots(i64{{.*}}, fp128 inreg noundef %c.coerce0, fp128 inreg noundef %c.coerce1) +// COMPAT23-GPR64-LABEL: define{{.*}} @arg_complex_long_double_7slots(i64{{.*}}, fp128 inreg noundef %c.coerce0, fp128 inreg noundef %c.coerce1) +void arg_complex_long_double_7slots(SEVEN_SLOTS, _Complex long double c) {} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
