https://github.com/freaknbigpanda updated https://github.com/llvm/llvm-project/pull/187814
>From 71b5df32993c8dadff91f392d12e0d4abf93bbd4 Mon Sep 17 00:00:00 2001 From: Benjamin Luke <[email protected]> Date: Fri, 20 Mar 2026 15:21:45 -0700 Subject: [PATCH 1/2] [clang][X86] Ignore ABI-empty fields and base classes in X86_64 SysV ABI classification algorithm Previously if there were empty record fields or base classes the SysV classification algorithm would incorrectly pass and return indirect. This adds code to correct the ABI and maintain the previous behaviour for PlayStation. --- clang/lib/CodeGen/Targets/X86.cpp | 6 ++- .../x86_64-empty-base-vector-abi.cpp | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp index e6203db8bc245..a32615c79e1d9 100644 --- a/clang/lib/CodeGen/Targets/X86.cpp +++ b/clang/lib/CodeGen/Targets/X86.cpp @@ -2072,6 +2072,7 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo, assert(!I.isVirtual() && !I.getType()->isDependentType() && "Unexpected base class!"); const auto *Base = I.getType()->castAsCXXRecordDecl(); + bool IsEmptyBase = isEmptyRecord(getContext(), I.getType(), true); // Classify this field. // // AMD64-ABI 3.2.3p2: Rule 3. If the size of the aggregate exceeds a @@ -2083,7 +2084,7 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo, classify(I.getType(), Offset, FieldLo, FieldHi, isNamedArg); Lo = merge(Lo, FieldLo); Hi = merge(Hi, FieldHi); - if (returnCXXRecordGreaterThan128InMem() && + if (returnCXXRecordGreaterThan128InMem() && !IsEmptyBase && (Size > 128 && (Size != getContext().getTypeSize(I.getType()) || Size > getNativeVectorSizeForAVXABI(AVXLevel)))) { // The only case a 256(or 512)-bit wide vector could be used to return @@ -2122,7 +2123,8 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo, // // FIXME: Extended the Lo and Hi logic properly to work for size wider // than 128. - if (Size > 128 && + bool IsEmptyField = isEmptyField(getContext(), *i, true); + if (Size > 128 && (!IsEmptyField || getTarget().getTriple().isPS()) && ((!IsUnion && Size != getContext().getTypeSize(i->getType())) || Size > getNativeVectorSizeForAVXABI(AVXLevel))) { Lo = Memory; diff --git a/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp b/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp new file mode 100644 index 0000000000000..bfd3f66bc1160 --- /dev/null +++ b/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm -o - %s -target-feature +avx | FileCheck %s --check-prefix=SYSV +// RUN: %clang_cc1 -triple x86_64-sie-ps5 -std=c++20 -emit-llvm -o - %s -target-feature +avx | FileCheck %s --check-prefix=PS + +typedef unsigned long long v4ull __attribute__((vector_size(32))); + +struct EmptyBase {}; + +struct EmptyBaseThenVector : EmptyBase { + v4ull Data; +}; + +EmptyBaseThenVector return_empty_base_then_vector() { + return {}; +} + +unsigned long long pass_empty_base_then_vector(EmptyBaseThenVector X) { + return X.Data[0]; +} + +struct EmptyField {}; + +struct EmptyFieldThenVector { + [[no_unique_address]] EmptyField E; + v4ull Data; +}; + +EmptyFieldThenVector return_empty_field_then_vector() { + return {}; +} + +unsigned long long pass_empty_field_then_vector(EmptyFieldThenVector X) { + return X.Data[0]; +} + +// Both the empty base case and empty-field case are now passed in register as per SysV spec +// SYSV-LABEL: define dso_local <4 x i64> @_Z29return_empty_base_then_vectorv() +// SYSV-LABEL: define dso_local noundef i64 @_Z27pass_empty_base_then_vector19EmptyBaseThenVector(<4 x i64> %X.coerce) +// SYSV-LABEL: define dso_local <4 x i64> @_Z30return_empty_field_then_vectorv() +// SYSV-LABEL: define dso_local noundef i64 @_Z28pass_empty_field_then_vector20EmptyFieldThenVector(<4 x i64> %X.coerce) + +// PlayStation keeps the legacy ABI behavior here: the empty-base case was always direct for PlayStation ABI +// while the [[no_unique_address]] empty-field case is still indirect even after this change. +// PS-LABEL: define dso_local <4 x i64> @_Z29return_empty_base_then_vectorv() +// PS-LABEL: define dso_local noundef i64 @_Z27pass_empty_base_then_vector19EmptyBaseThenVector(<4 x i64> %X.coerce) +// PS-LABEL: define dso_local void @_Z30return_empty_field_then_vectorv(ptr dead_on_unwind noalias writable sret(%struct.EmptyFieldThenVector) align 32 %agg.result) +// PS-LABEL: define dso_local noundef i64 @_Z28pass_empty_field_then_vector20EmptyFieldThenVector(ptr noundef byval(%struct.EmptyFieldThenVector) align 32 %X) >From b98f87dc8544663e601f46ea229287d0f4d38e7c Mon Sep 17 00:00:00 2001 From: Benjamin Luke <[email protected]> Date: Fri, 3 Apr 2026 09:17:35 -0700 Subject: [PATCH 2/2] Comments by Pheobe --- clang/include/clang/Basic/ABIVersions.def | 2 ++ clang/lib/CodeGen/Targets/X86.cpp | 20 ++++++++++++++++--- .../x86_64-empty-base-vector-abi.cpp | 7 +++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/clang/include/clang/Basic/ABIVersions.def b/clang/include/clang/Basic/ABIVersions.def index 92edcd830f031..37b7a4851725f 100644 --- a/clang/include/clang/Basic/ABIVersions.def +++ b/clang/include/clang/Basic/ABIVersions.def @@ -133,6 +133,8 @@ ABI_VER_MAJOR(20) /// compatible with scalar deleting destructors emitted by MSVC for the /// cases when the class whose destructor is being emitted defines /// operator delete. +/// - Don't ignore ABI-empty CXX fields and base classes when classifying +/// x86_64 SysV C++ record arguments and returns. ABI_VER_MAJOR(21) /// Conform to the underlying platform's C and C++ ABIs as closely as we can. diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp index a32615c79e1d9..a5d0e0f6fef3a 100644 --- a/clang/lib/CodeGen/Targets/X86.cpp +++ b/clang/lib/CodeGen/Targets/X86.cpp @@ -1359,6 +1359,18 @@ class X86_64ABIInfo : public ABIInfo { return true; } + bool properlyIgnoreEmptyCXXFieldsAndBases() const { + // properly ignore empty CXX Fields and Bases in ABI classification + // algorithm CLang <= 21.0 did not do this, and PlayStation does not do + // this. + if (getContext().getLangOpts().getClangABICompat() <= + LangOptions::ClangABI::Ver21 || + getTarget().getTriple().isPS()) + return false; + + return true; + } + X86AVXABILevel AVXLevel; // Some ABIs (e.g. X32 ABI and Native Client OS) use 32 bit pointers on // 64-bit hardware. @@ -2072,7 +2084,8 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo, assert(!I.isVirtual() && !I.getType()->isDependentType() && "Unexpected base class!"); const auto *Base = I.getType()->castAsCXXRecordDecl(); - bool IsEmptyBase = isEmptyRecord(getContext(), I.getType(), true); + bool IsEmptyBase = isEmptyRecord(getContext(), I.getType(), true) && + properlyIgnoreEmptyCXXFieldsAndBases(); // Classify this field. // // AMD64-ABI 3.2.3p2: Rule 3. If the size of the aggregate exceeds a @@ -2123,8 +2136,9 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo, // // FIXME: Extended the Lo and Hi logic properly to work for size wider // than 128. - bool IsEmptyField = isEmptyField(getContext(), *i, true); - if (Size > 128 && (!IsEmptyField || getTarget().getTriple().isPS()) && + bool IsEmptyField = isEmptyField(getContext(), *i, true) && + properlyIgnoreEmptyCXXFieldsAndBases(); + if (Size > 128 && !IsEmptyField && ((!IsUnion && Size != getContext().getTypeSize(i->getType())) || Size > getNativeVectorSizeForAVXABI(AVXLevel))) { Lo = Memory; diff --git a/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp b/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp index bfd3f66bc1160..54e802e20cf31 100644 --- a/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp +++ b/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp @@ -1,5 +1,6 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm -o - %s -target-feature +avx | FileCheck %s --check-prefix=SYSV // RUN: %clang_cc1 -triple x86_64-sie-ps5 -std=c++20 -emit-llvm -o - %s -target-feature +avx | FileCheck %s --check-prefix=PS +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclang-abi-compat=21 -emit-llvm -o - %s -target-feature +avx | FileCheck %s --check-prefix=CLANG21 typedef unsigned long long v4ull __attribute__((vector_size(32))); @@ -44,3 +45,9 @@ unsigned long long pass_empty_field_then_vector(EmptyFieldThenVector X) { // PS-LABEL: define dso_local noundef i64 @_Z27pass_empty_base_then_vector19EmptyBaseThenVector(<4 x i64> %X.coerce) // PS-LABEL: define dso_local void @_Z30return_empty_field_then_vectorv(ptr dead_on_unwind noalias writable sret(%struct.EmptyFieldThenVector) align 32 %agg.result) // PS-LABEL: define dso_local noundef i64 @_Z28pass_empty_field_then_vector20EmptyFieldThenVector(ptr noundef byval(%struct.EmptyFieldThenVector) align 32 %X) + +// Clang21 ABI compatibility mode keeps the legacy ABI behavior: the empty-base case and [[no_unique_address]] empty-field case both as indirect +// CLANG21-LABEL: define dso_local void @_Z29return_empty_base_then_vectorv(ptr dead_on_unwind noalias writable sret(%struct.EmptyBaseThenVector) align 32 %agg.result) +// CLANG21-LABEL: define dso_local noundef i64 @_Z27pass_empty_base_then_vector19EmptyBaseThenVector(ptr noundef byval(%struct.EmptyBaseThenVector) align 32 %X) +// CLANG21-LABEL: define dso_local void @_Z30return_empty_field_then_vectorv(ptr dead_on_unwind noalias writable sret(%struct.EmptyFieldThenVector) align 32 %agg.result) +// CLANG21-LABEL: define dso_local noundef i64 @_Z28pass_empty_field_then_vector20EmptyFieldThenVector(ptr noundef byval(%struct.EmptyFieldThenVector) align 32 %X) _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
