[clang] [clang][X86] Ignore ABI-empty fields and base classes in X86_64 SysV ABI classification algorithm (PR #187814)
Benjamin Luke via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 8 15:06:42 PDT 2026
https://github.com/freaknbigpanda updated https://github.com/llvm/llvm-project/pull/187814
>From c6e5b660cc6a0fa6a92fdf98701b0214271f610e Mon Sep 17 00:00:00 2001
From: Benjamin Luke <benjamin.luke at sony.com>
Date: Fri, 20 Mar 2026 15:21:45 -0700
Subject: [PATCH 1/5] [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 56fd2a0830bab5012724a3f2e0572a504b60a480 Mon Sep 17 00:00:00 2001
From: Benjamin Luke <benjamin.luke at sony.com>
Date: Fri, 3 Apr 2026 09:17:35 -0700
Subject: [PATCH 2/5] Comments by Phoebe
---
clang/include/clang/Basic/ABIVersions.def | 6 ++++++
clang/lib/CodeGen/Targets/X86.cpp | 20 ++++++++++++++++---
.../x86_64-empty-base-vector-abi.cpp | 7 +++++++
3 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/Basic/ABIVersions.def b/clang/include/clang/Basic/ABIVersions.def
index 92edcd830f031..fbfdbc0830bfa 100644
--- a/clang/include/clang/Basic/ABIVersions.def
+++ b/clang/include/clang/Basic/ABIVersions.def
@@ -135,6 +135,12 @@ ABI_VER_MAJOR(20)
/// operator delete.
ABI_VER_MAJOR(21)
+/// Attempt to be ABI-compatible with code generated by Clang 22.0.x.
+/// This causes clang to:
+/// - Not ignore ABI-empty CXX fields and base classes when classifying
+/// x86_64 SysV C++ record arguments and returns.
+ABI_VER_MAJOR(22)
+
/// Conform to the underlying platform's C and C++ ABIs as closely as we can.
ABI_VER_LATEST(Latest)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index a32615c79e1d9..b4e84f3d5fcc2 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 <= 22.0 did not do this, and PlayStation does not do
+ // this.
+ if (getContext().getLangOpts().getClangABICompat() <=
+ LangOptions::ClangABI::Ver22 ||
+ 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)
>From 0024db23822eacacf75f4446b06ccf65e656ac6d Mon Sep 17 00:00:00 2001
From: Benjamin Luke <benjamin.luke at sony.com>
Date: Fri, 3 Apr 2026 09:35:16 -0700
Subject: [PATCH 3/5] Fixup the test to use Clang22 ABI compat mode
---
.../test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
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 54e802e20cf31..705b89d68a54f 100644
--- a/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp
+++ b/clang/test/CodeGenCXX/x86_64-empty-base-vector-abi.cpp
@@ -1,6 +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
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclang-abi-compat=22 -emit-llvm -o - %s -target-feature +avx | FileCheck %s --check-prefix=CLANG22
typedef unsigned long long v4ull __attribute__((vector_size(32)));
@@ -46,8 +46,8 @@ unsigned long long pass_empty_field_then_vector(EmptyFieldThenVector X) {
// 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)
+// Clang22 ABI compatibility mode keeps the legacy ABI behavior: the empty-base case and [[no_unique_address]] empty-field case both as indirect
+// CLANG22-LABEL: define dso_local void @_Z29return_empty_base_then_vectorv(ptr dead_on_unwind noalias writable sret(%struct.EmptyBaseThenVector) align 32 %agg.result)
+// CLANG22-LABEL: define dso_local noundef i64 @_Z27pass_empty_base_then_vector19EmptyBaseThenVector(ptr noundef byval(%struct.EmptyBaseThenVector) align 32 %X)
+// CLANG22-LABEL: define dso_local void @_Z30return_empty_field_then_vectorv(ptr dead_on_unwind noalias writable sret(%struct.EmptyFieldThenVector) align 32 %agg.result)
+// CLANG22-LABEL: define dso_local noundef i64 @_Z28pass_empty_field_then_vector20EmptyFieldThenVector(ptr noundef byval(%struct.EmptyFieldThenVector) align 32 %X)
>From 4ddc1b9f5bd8706562f7b0ed33cba9ed4e302315 Mon Sep 17 00:00:00 2001
From: Benjamin Luke <benjamin.luke at sony.com>
Date: Wed, 8 Apr 2026 13:29:30 -0700
Subject: [PATCH 4/5] small refactor
---
clang/lib/CodeGen/Targets/X86.cpp | 21 +++++++++++++++------
1 file changed, 15 insertions(+), 6 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index b4e84f3d5fcc2..a87052b80ad2b 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -2084,8 +2084,12 @@ 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) &&
- properlyIgnoreEmptyCXXFieldsAndBases();
+
+ // If it is an empty base skip it
+ if (isEmptyRecord(getContext(), I.getType(), true) &&
+ properlyIgnoreEmptyCXXFieldsAndBases())
+ continue;
+
// Classify this field.
//
// AMD64-ABI 3.2.3p2: Rule 3. If the size of the aggregate exceeds a
@@ -2097,7 +2101,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() && !IsEmptyBase &&
+ if (returnCXXRecordGreaterThan128InMem() &&
(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
@@ -2127,6 +2131,11 @@ void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo,
if (BitField && i->isUnnamedBitField())
continue;
+ // Ignore empty fields
+ if (isEmptyField(getContext(), *i, true) &&
+ properlyIgnoreEmptyCXXFieldsAndBases())
+ continue;
+
// AMD64-ABI 3.2.3p2: Rule 1. If the size of an object is larger than
// eight eightbytes, or it contains unaligned fields, it has class MEMORY.
//
@@ -2136,9 +2145,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) &&
- properlyIgnoreEmptyCXXFieldsAndBases();
- if (Size > 128 && !IsEmptyField &&
+ // bool IsEmptyField = isEmptyField(getContext(), *i, true) &&
+ // properlyIgnoreEmptyCXXFieldsAndBases();
+ if (Size > 128 &&
((!IsUnion && Size != getContext().getTypeSize(i->getType())) ||
Size > getNativeVectorSizeForAVXABI(AVXLevel))) {
Lo = Memory;
>From 899e14949f9aaa9910817b8cfe87b5534f35648b Mon Sep 17 00:00:00 2001
From: Benjamin Luke <benjamin.luke at sony.com>
Date: Wed, 8 Apr 2026 15:06:26 -0700
Subject: [PATCH 5/5] remove un-needed code
---
clang/lib/CodeGen/Targets/X86.cpp | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang/lib/CodeGen/Targets/X86.cpp b/clang/lib/CodeGen/Targets/X86.cpp
index a87052b80ad2b..e70cfff329bc1 100644
--- a/clang/lib/CodeGen/Targets/X86.cpp
+++ b/clang/lib/CodeGen/Targets/X86.cpp
@@ -2145,8 +2145,6 @@ 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) &&
- // properlyIgnoreEmptyCXXFieldsAndBases();
if (Size > 128 &&
((!IsUnion && Size != getContext().getTypeSize(i->getType())) ||
Size > getNativeVectorSizeForAVXABI(AVXLevel))) {
More information about the cfe-commits
mailing list