[clang] [llvm] [LLVMABI][AARCH64] Support transparent union arguments (PR #217686)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Thu Aug 20 15:55:05 PDT 2026
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/217686
>From 510e12ce090a18f8cf97528254718f8c551031e8 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 19 Aug 2026 09:16:52 -0700
Subject: [PATCH 1/2] [LLVMABI][AARCH64] Support transparent union arguments
This adds support for using the first field in a transparent union for the
purposes of argument classification.
The `useFirstFieldIfTransparentUnion` function was already implemented for
the `X86_64TargetInfo` class. I moved it to the `TargetInfo` base class
because there is nothing target-specific about it.
Assisted-by: Cursor / various models
---
.../CodeGen/AArch64/abi-classify-arg-types.c | 23 ++++++
llvm/include/llvm/ABI/TargetInfo.h | 4 +
llvm/lib/ABI/TargetInfo.cpp | 11 +++
llvm/lib/ABI/Targets/AArch64.cpp | 2 +
llvm/lib/ABI/Targets/X86.cpp | 13 ----
llvm/unittests/ABI/AArch64TargetInfoTest.cpp | 74 +++++++++++++++++++
6 files changed, 114 insertions(+), 13 deletions(-)
diff --git a/clang/test/CodeGen/AArch64/abi-classify-arg-types.c b/clang/test/CodeGen/AArch64/abi-classify-arg-types.c
index 78be22a7aa3e1..a647586a86547 100644
--- a/clang/test/CodeGen/AArch64/abi-classify-arg-types.c
+++ b/clang/test/CodeGen/AArch64/abi-classify-arg-types.c
@@ -70,3 +70,26 @@ void arg_void_ptr(void* pv) {}
typedef float fx2x2_t __attribute__((matrix_type(2, 2)));
void arg_matrix(fx2x2_t m) {}
// CHECK: define{{.*}} void @arg_matrix(<4 x float> noundef %{{.*}})
+
+// Transparent unions are passed as their first field.
+typedef union {
+ int i;
+ float f;
+} tu_int_t __attribute__((transparent_union));
+void arg_transparent_union_int(tu_int_t tu) {}
+// CHECK: define{{.*}} void @arg_transparent_union_int(i32 %{{.*}})
+
+typedef union {
+ char c;
+ signed char sc;
+} tu_char_t __attribute__((transparent_union));
+void arg_transparent_union_char(tu_char_t tu) {}
+// AAPCS: define{{.*}} void @arg_transparent_union_char(i8 %{{.*}})
+// DARWIN: define{{.*}} void @arg_transparent_union_char(i8 noundef signext %{{.*}})
+
+typedef union {
+ void *p;
+ int *ip;
+} tu_ptr_t __attribute__((transparent_union));
+void arg_transparent_union_ptr(tu_ptr_t tu) {}
+// CHECK: define{{.*}} void @arg_transparent_union_ptr(ptr %{{.*}})
diff --git a/llvm/include/llvm/ABI/TargetInfo.h b/llvm/include/llvm/ABI/TargetInfo.h
index 7ecf8df0edbf0..8132de140064a 100644
--- a/llvm/include/llvm/ABI/TargetInfo.h
+++ b/llvm/include/llvm/ABI/TargetInfo.h
@@ -83,6 +83,10 @@ class TargetInfo {
bool ByVal = true) const;
LLVM_ABI bool isAggregateTypeForABI(const Type *Ty) const;
+ /// If Ty is a transparent union, return its first field type; otherwise
+ /// return Ty unchanged.
+ LLVM_ABI const Type *useFirstFieldIfTransparentUnion(const Type *Ty) const;
+
/// Apply rules for classifying return types that are common to all targets.
LLVM_ABI bool maybeCommonClassifyReturnType(FunctionInfo &FI) const;
};
diff --git a/llvm/lib/ABI/TargetInfo.cpp b/llvm/lib/ABI/TargetInfo.cpp
index a0190bd1fcb8a..955e3b2018c7b 100644
--- a/llvm/lib/ABI/TargetInfo.cpp
+++ b/llvm/lib/ABI/TargetInfo.cpp
@@ -52,6 +52,17 @@ RecordArgABI TargetInfo::getRecordArgABI(const Type *Ty) const {
return getRecordArgABI(RT);
}
+const Type *TargetInfo::useFirstFieldIfTransparentUnion(const Type *Ty) const {
+ if (const auto *RT = dyn_cast<RecordType>(Ty)) {
+ if (RT->isUnion() && RT->isTransparentUnion()) {
+ auto Fields = RT->getFields();
+ assert(!Fields.empty() && "transparent union cannot be empty");
+ return Fields.front().FieldType;
+ }
+ }
+ return Ty;
+}
+
bool TargetInfo::maybeCommonClassifyReturnType(FunctionInfo &FI) const {
const abi::Type *Ty = FI.getReturnType();
diff --git a/llvm/lib/ABI/Targets/AArch64.cpp b/llvm/lib/ABI/Targets/AArch64.cpp
index b1a352c230c8d..90e111ba5147c 100644
--- a/llvm/lib/ABI/Targets/AArch64.cpp
+++ b/llvm/lib/ABI/Targets/AArch64.cpp
@@ -92,6 +92,8 @@ ArgInfo AArch64TargetInfo::classifyReturnType(const Type *RetTy,
ArgInfo AArch64TargetInfo::classifyArgumentType(
const Type *Ty, bool IsVariadicFn, bool IsNamedArg,
unsigned CallingConvention, unsigned &NSRN, unsigned &NPRN) const {
+ Ty = useFirstFieldIfTransparentUnion(Ty);
+
// TODO: Handle variadic functins here when Windows Arm64 EC is supported.
if (Ty->isVector()) {
diff --git a/llvm/lib/ABI/Targets/X86.cpp b/llvm/lib/ABI/Targets/X86.cpp
index 7b5135419e8b5..43e30c5a43a18 100644
--- a/llvm/lib/ABI/Targets/X86.cpp
+++ b/llvm/lib/ABI/Targets/X86.cpp
@@ -111,7 +111,6 @@ class X86_64TargetInfo : public TargetInfo {
ArgInfo classifyArgumentType(const Type *Ty, unsigned FreeIntRegs,
unsigned &NeededInt, unsigned &NeededSse,
bool IsNamedArg, bool IsRegCall = false) const;
- const Type *useFirstFieldIfTransparentUnion(const Type *Ty) const;
public:
X86_64TargetInfo(TypeBuilder &TypeBuilder, X86AVXABILevel AVXABILevel,
@@ -596,18 +595,6 @@ void X86_64TargetInfo::classify(const Type *T, uint64_t OffsetBase, Class &Lo,
Hi = NoClass;
}
-const Type *
-X86_64TargetInfo::useFirstFieldIfTransparentUnion(const Type *Ty) const {
- if (const auto *RT = dyn_cast<RecordType>(Ty)) {
- if (RT->isUnion() && RT->isTransparentUnion()) {
- auto Fields = RT->getFields();
- assert(!Fields.empty() && "transparent union cannot be empty");
- return Fields.front().FieldType;
- }
- }
- return Ty;
-}
-
ArgInfo
X86_64TargetInfo::classifyArgumentType(const Type *Ty, unsigned FreeIntRegs,
unsigned &NeededInt, unsigned &NeededSSE,
diff --git a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
index e73366f007a8c..a977d1d1a9eb7 100644
--- a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
@@ -227,4 +227,78 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectWin64) {
}
}
+static void expectNaturalAlignIndirect(const ArgInfo &Info,
+ llvm::Align ExpectedAlign, bool ByVal) {
+ EXPECT_TRUE(Info.isIndirect());
+ EXPECT_EQ(Info.getIndirectAlign(), ExpectedAlign);
+ EXPECT_EQ(Info.getIndirectByVal(), ByVal);
+}
+
+// Records that cannot be passed in registers (e.g. non-trivial C++ types) are
+// classified as Indirect with ByVal=false under all AArch64 ABI kinds.
+TEST_F(AArch64TargetInfoTest, ClassifyArgumentRecordCannotPassInRegisters) {
+ // A record without CanPassInRegisters is treated like a C++ type with a
+ // non-trivial copy constructor or destructor.
+ const ABIType *CannotPass = TB.getRecordType(
+ {llvm::abi::FieldInfo(I32)}, llvm::TypeSize::getFixed(32), llvm::Align(4),
+ llvm::abi::StructPacking::Default, /*BaseClasses=*/{},
+ /*VirtualBaseClasses=*/{}, llvm::abi::RecordFlags::IsCXXRecord);
+
+ for (AArch64ABIKind Kind :
+ {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64,
+ AArch64ABIKind::AAPCSSoft}) {
+ std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind);
+ std::unique_ptr<FunctionInfo> FI =
+ FunctionInfo::create(llvm::CallingConv::C, Void, {CannotPass});
+ TI->computeInfo(*FI);
+ expectNaturalAlignIndirect(FI->getArgInfo(0).Info, llvm::Align(4),
+ /*ByVal=*/false);
+ }
+}
+
+// Transparent unions are classified as their first field type.
+TEST_F(AArch64TargetInfoTest, ClassifyArgumentTransparentUnion) {
+ using llvm::abi::FieldInfo;
+ using llvm::abi::RecordFlags;
+ using llvm::abi::StructPacking;
+
+ // First field is i32; second field is ignored for classification.
+ const ABIType *TUInt = TB.getUnionType(
+ {FieldInfo(I32), FieldInfo(F32)}, llvm::TypeSize::getFixed(32),
+ llvm::Align(4), StructPacking::Default, RecordFlags::IsTransparent);
+
+ for (AArch64ABIKind Kind :
+ {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64,
+ AArch64ABIKind::AAPCSSoft}) {
+ std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind);
+ std::unique_ptr<FunctionInfo> FI =
+ FunctionInfo::create(llvm::CallingConv::C, Void, {TUInt});
+ TI->computeInfo(*FI);
+ expectUncoercedDirect(FI->getArgInfo(0).Info);
+ }
+
+ // First field is a promotable integer: DarwinPCS extends; others are Direct.
+ const ABIType *TUChar = TB.getUnionType(
+ {FieldInfo(I8), FieldInfo(U8)}, llvm::TypeSize::getFixed(8),
+ llvm::Align(1), StructPacking::Default, RecordFlags::IsTransparent);
+
+ {
+ std::unique_ptr<TargetInfo> TI =
+ createAArch64TargetInfo(TB, AArch64ABIKind::DarwinPCS);
+ std::unique_ptr<FunctionInfo> FI =
+ FunctionInfo::create(llvm::CallingConv::C, Void, {TUChar});
+ TI->computeInfo(*FI);
+ expectExtendInteger(FI->getArgInfo(0).Info, I8, /*IsSigned=*/true);
+ }
+
+ for (AArch64ABIKind Kind : {AArch64ABIKind::AAPCS, AArch64ABIKind::Win64,
+ AArch64ABIKind::AAPCSSoft}) {
+ std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind);
+ std::unique_ptr<FunctionInfo> FI =
+ FunctionInfo::create(llvm::CallingConv::C, Void, {TUChar});
+ TI->computeInfo(*FI);
+ expectUncoercedDirect(FI->getArgInfo(0).Info);
+ }
+}
+
} // namespace
>From 5e9f1b19cc9c23f853e1f7c560e7a665087875a3 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Thu, 20 Aug 2026 15:54:33 -0700
Subject: [PATCH 2/2] Remove test case from bad rebase
---
llvm/unittests/ABI/AArch64TargetInfoTest.cpp | 29 --------------------
1 file changed, 29 deletions(-)
diff --git a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
index a977d1d1a9eb7..237053f78b7b6 100644
--- a/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/AArch64TargetInfoTest.cpp
@@ -227,35 +227,6 @@ TEST_F(AArch64TargetInfoTest, ClassifyArgumentScalarsDirectWin64) {
}
}
-static void expectNaturalAlignIndirect(const ArgInfo &Info,
- llvm::Align ExpectedAlign, bool ByVal) {
- EXPECT_TRUE(Info.isIndirect());
- EXPECT_EQ(Info.getIndirectAlign(), ExpectedAlign);
- EXPECT_EQ(Info.getIndirectByVal(), ByVal);
-}
-
-// Records that cannot be passed in registers (e.g. non-trivial C++ types) are
-// classified as Indirect with ByVal=false under all AArch64 ABI kinds.
-TEST_F(AArch64TargetInfoTest, ClassifyArgumentRecordCannotPassInRegisters) {
- // A record without CanPassInRegisters is treated like a C++ type with a
- // non-trivial copy constructor or destructor.
- const ABIType *CannotPass = TB.getRecordType(
- {llvm::abi::FieldInfo(I32)}, llvm::TypeSize::getFixed(32), llvm::Align(4),
- llvm::abi::StructPacking::Default, /*BaseClasses=*/{},
- /*VirtualBaseClasses=*/{}, llvm::abi::RecordFlags::IsCXXRecord);
-
- for (AArch64ABIKind Kind :
- {AArch64ABIKind::AAPCS, AArch64ABIKind::DarwinPCS, AArch64ABIKind::Win64,
- AArch64ABIKind::AAPCSSoft}) {
- std::unique_ptr<TargetInfo> TI = createAArch64TargetInfo(TB, Kind);
- std::unique_ptr<FunctionInfo> FI =
- FunctionInfo::create(llvm::CallingConv::C, Void, {CannotPass});
- TI->computeInfo(*FI);
- expectNaturalAlignIndirect(FI->getArgInfo(0).Info, llvm::Align(4),
- /*ByVal=*/false);
- }
-}
-
// Transparent unions are classified as their first field type.
TEST_F(AArch64TargetInfoTest, ClassifyArgumentTransparentUnion) {
using llvm::abi::FieldInfo;
More information about the cfe-commits
mailing list