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 <[email protected]> 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 <[email protected]> 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; _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
