[llvm] [ABI] Add AMDGPU target ABI classifier to the LLVM ABI library (PR #220177)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 1 00:19:37 PDT 2026
https://github.com/skc7 created https://github.com/llvm/llvm-project/pull/220177
**Summary:**
- Port the classic CodeGen AMDGPUABIInfo call-convention classifier to the new language-agnostic LLVM ABI library.
**Changes:**
- LLVM ABI library. AMDGPUTargetInfo implements return, kernel-argument, and regular-argument classification
- Hoist isSingleElementStruct() from X86 into the shared TargetInfo base so AMDGPU and X86 (and future targets) reuse one implementation.
- Unit tests covering the classifier's argument/return branches.
**TODO:**
- HIP `generic->global` kernel-pointer coercion
- `CanBeFlattened=false` on direct struct arguments
Assisted by: Claude Opus 4.8
>From 65443a3672827fbf6d4d87d31b603e877caa890b Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Tue, 1 Sep 2026 12:35:15 +0530
Subject: [PATCH] [ABI] Add AMDGPU target ABI classifier to the LLVM ABI
library
---
llvm/include/llvm/ABI/TargetInfo.h | 6 +
llvm/lib/ABI/CMakeLists.txt | 1 +
llvm/lib/ABI/TargetInfo.cpp | 54 ++++
llvm/lib/ABI/Targets/AMDGPU.cpp | 248 ++++++++++++++++++
llvm/lib/ABI/Targets/X86.cpp | 55 ----
llvm/unittests/ABI/AMDGPUTargetInfoTest.cpp | 274 ++++++++++++++++++++
llvm/unittests/ABI/CMakeLists.txt | 1 +
7 files changed, 584 insertions(+), 55 deletions(-)
create mode 100644 llvm/lib/ABI/Targets/AMDGPU.cpp
create mode 100644 llvm/unittests/ABI/AMDGPUTargetInfoTest.cpp
diff --git a/llvm/include/llvm/ABI/TargetInfo.h b/llvm/include/llvm/ABI/TargetInfo.h
index 8132de140064a..552574d93069b 100644
--- a/llvm/include/llvm/ABI/TargetInfo.h
+++ b/llvm/include/llvm/ABI/TargetInfo.h
@@ -87,12 +87,18 @@ class TargetInfo {
/// return Ty unchanged.
LLVM_ABI const Type *useFirstFieldIfTransparentUnion(const Type *Ty) const;
+ /// If the record reduces to a single scalar element,
+ /// return that element type; otherwise null.
+ LLVM_ABI const Type *isSingleElementStruct(const Type *Ty) const;
+
/// Apply rules for classifying return types that are common to all targets.
LLVM_ABI bool maybeCommonClassifyReturnType(FunctionInfo &FI) const;
};
LLVM_ABI std::unique_ptr<TargetInfo> createBPFTargetInfo(TypeBuilder &TB);
+LLVM_ABI std::unique_ptr<TargetInfo> createAMDGPUTargetInfo(TypeBuilder &TB);
+
/// The AVX ABI level for X86 targets.
enum class X86AVXABILevel {
None,
diff --git a/llvm/lib/ABI/CMakeLists.txt b/llvm/lib/ABI/CMakeLists.txt
index 39e725ca9fd3b..54307bae8e07d 100644
--- a/llvm/lib/ABI/CMakeLists.txt
+++ b/llvm/lib/ABI/CMakeLists.txt
@@ -4,6 +4,7 @@ add_llvm_component_library(LLVMABI
TargetInfo.cpp
IRTypeMapper.cpp
Targets/AArch64.cpp
+ Targets/AMDGPU.cpp
Targets/BPF.cpp
Targets/X86.cpp
diff --git a/llvm/lib/ABI/TargetInfo.cpp b/llvm/lib/ABI/TargetInfo.cpp
index 955e3b2018c7b..33a7e682a6874 100644
--- a/llvm/lib/ABI/TargetInfo.cpp
+++ b/llvm/lib/ABI/TargetInfo.cpp
@@ -63,6 +63,60 @@ const Type *TargetInfo::useFirstFieldIfTransparentUnion(const Type *Ty) const {
return Ty;
}
+const Type *TargetInfo::isSingleElementStruct(const Type *Ty) const {
+ const auto *RT = dyn_cast<RecordType>(Ty);
+ if (!RT)
+ return nullptr;
+
+ if (RT->hasFlexibleArrayMember())
+ return nullptr;
+
+ const Type *Found = nullptr;
+
+ for (const auto &Base : RT->getBaseClasses()) {
+ const Type *BaseTy = Base.FieldType;
+ const auto *BaseRT = dyn_cast<RecordType>(BaseTy);
+ if (!BaseRT || BaseRT->isEmpty())
+ continue;
+
+ const Type *Elem = isSingleElementStruct(BaseTy);
+ if (!Elem || Found)
+ return nullptr;
+ Found = Elem;
+ }
+
+ for (const auto &FI : RT->getFields()) {
+ if (FI.isEmpty())
+ continue;
+
+ const Type *FTy = FI.FieldType;
+
+ // A single-element array is transparent for this reduction.
+ while (const auto *AT = dyn_cast<ArrayType>(FTy)) {
+ if (AT->getNumElements() != 1)
+ break;
+ FTy = AT->getElementType();
+ }
+
+ const Type *Elem;
+ if (const auto *InnerRT = dyn_cast<RecordType>(FTy))
+ Elem = isSingleElementStruct(InnerRT);
+ else
+ Elem = FTy;
+ if (!Elem || Found)
+ return nullptr;
+ Found = Elem;
+ }
+
+ if (!Found)
+ return nullptr;
+ // The reduced element must cover the whole record (no tail padding).
+ if (Found->getSizeInBits() != Ty->getSizeInBits())
+ return nullptr;
+
+ return Found;
+}
+
bool TargetInfo::maybeCommonClassifyReturnType(FunctionInfo &FI) const {
const abi::Type *Ty = FI.getReturnType();
diff --git a/llvm/lib/ABI/Targets/AMDGPU.cpp b/llvm/lib/ABI/Targets/AMDGPU.cpp
new file mode 100644
index 0000000000000..92130664f8b62
--- /dev/null
+++ b/llvm/lib/ABI/Targets/AMDGPU.cpp
@@ -0,0 +1,248 @@
+//===- AMDGPU.cpp - AMDGPU ABI Implementation ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ABI/FunctionInfo.h"
+#include "llvm/ABI/TargetInfo.h"
+#include "llvm/ABI/Types.h"
+#include "llvm/Support/AMDGPUAddrSpace.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/TypeSize.h"
+#include <algorithm>
+#include <cassert>
+#include <cstdint>
+
+namespace llvm {
+namespace abi {
+
+class AMDGPUTargetInfo : public TargetInfo {
+private:
+ TypeBuilder &TB;
+ static const unsigned MaxNumRegsForArgsRet = 16;
+
+ ArgInfo classifyReturnType(const Type *RetTy) const;
+ ArgInfo classifyKernelArgumentType(const Type *Ty) const;
+ ArgInfo classifyArgumentType(const Type *Ty, bool Variadic,
+ unsigned &NumRegsLeft) const;
+
+ /// Target-independent fallback, mirroring classic CodeGen's DefaultABIInfo.
+ ArgInfo classifyDefaultType(const Type *Ty, bool IsReturn) const;
+
+ /// Estimate number of registers the type will use when passed in registers.
+ uint64_t numRegsForType(const Type *Ty) const;
+
+public:
+ AMDGPUTargetInfo(TypeBuilder &TypeBuilder, const ABICompatInfo &Compat)
+ : TargetInfo(Compat), TB(TypeBuilder) {}
+
+ void computeInfo(FunctionInfo &FI) const override;
+};
+
+uint64_t AMDGPUTargetInfo::numRegsForType(const Type *Ty) const {
+ uint64_t NumRegs = 0;
+
+ if (const auto *VT = dyn_cast<VectorType>(Ty)) {
+ // Compute from the number of elements. The reported size is based on the
+ // in-memory size, which includes the padding 4th element for 3-vectors.
+ const Type *EltTy = VT->getElementType();
+ uint64_t EltSize = EltTy->getSizeInBits().getFixedValue();
+ unsigned NumElts = VT->getNumElements().getFixedValue();
+
+ // 16-bit element vectors should be passed as packed.
+ if (EltSize == 16)
+ return (NumElts + 1) / 2;
+
+ uint64_t EltNumRegs = (EltSize + 31) / 32;
+ return EltNumRegs * NumElts;
+ }
+
+ if (const auto *RT = dyn_cast<RecordType>(Ty)) {
+ for (const FieldInfo &Field : RT->getFields())
+ NumRegs += numRegsForType(Field.FieldType);
+ return NumRegs;
+ }
+
+ return (Ty->getSizeInBits().getFixedValue() + 31) / 32;
+}
+
+ArgInfo AMDGPUTargetInfo::classifyDefaultType(const Type *Ty,
+ bool IsReturn) const {
+ if (IsReturn && Ty->isVoid())
+ return ArgInfo::getIgnore();
+
+ if (isAggregateTypeForABI(Ty)) {
+ if (RecordArgABI RAA = getRecordArgABI(Ty); RAA != RAA_Default)
+ return getNaturalAlignIndirect(Ty, /*ByVal=*/RAA == RAA_DirectInMemory);
+ return getNaturalAlignIndirect(Ty, /*ByVal=*/!IsReturn);
+ }
+
+ if (const auto *IT = dyn_cast<IntegerType>(Ty))
+ if (isPromotableInteger(IT))
+ return ArgInfo::getExtend(Ty);
+
+ return ArgInfo::getDirect();
+}
+
+ArgInfo AMDGPUTargetInfo::classifyReturnType(const Type *RetTy) const {
+ if (RetTy->isVoid())
+ return ArgInfo::getIgnore();
+
+ if (isAggregateTypeForABI(RetTy)) {
+ // Records with non-trivial destructors/copy-constructors should not be
+ // returned by value.
+ if (getRecordArgABI(RetTy) == RAA_Default) {
+ const auto *RT = dyn_cast<RecordType>(RetTy);
+
+ // Ignore empty structs/unions.
+ if (RT && RT->isEmpty())
+ return ArgInfo::getIgnore();
+
+ // Lower single-element structs to just return a regular value.
+ if (const Type *SeltTy = isSingleElementStruct(RetTy))
+ return ArgInfo::getDirect(SeltTy);
+
+ if (RT && RT->hasFlexibleArrayMember())
+ return classifyDefaultType(RetTy, /*IsReturn=*/true);
+
+ // Pack aggregates <= 4 bytes into single VGPR or pair.
+ uint64_t Size = RetTy->getSizeInBits().getFixedValue();
+ if (Size <= 16)
+ return ArgInfo::getDirect(TB.getIntegerType(16, Align(2), false));
+
+ if (Size <= 32)
+ return ArgInfo::getDirect(TB.getIntegerType(32, Align(4), false));
+
+ if (Size <= 64) {
+ const Type *I32Ty = TB.getIntegerType(32, Align(4), false);
+ return ArgInfo::getDirect(TB.getArrayType(I32Ty, 2, /*SizeInBits=*/64));
+ }
+
+ if (numRegsForType(RetTy) <= MaxNumRegsForArgsRet)
+ return ArgInfo::getDirect();
+ }
+ }
+
+ // Otherwise just do the default thing.
+ return classifyDefaultType(RetTy, /*IsReturn=*/true);
+}
+
+/// For kernels all parameters are really passed in a special buffer. It doesn't
+/// make sense to pass anything byval, so everything must be direct.
+ArgInfo AMDGPUTargetInfo::classifyKernelArgumentType(const Type *Ty) const {
+ Ty = useFirstFieldIfTransparentUnion(Ty);
+
+ if (const Type *SeltTy = isSingleElementStruct(Ty))
+ Ty = SeltTy;
+
+ // TODO: Classic coerces HIP scalar pointers from generic to global here; that
+ // depends on LangOptions the ABI library cannot see, so it is skipped.
+ if (isAggregateTypeForABI(Ty))
+ return ArgInfo::getIndirect(Ty->getAlignment(), /*ByVal=*/false,
+ /*AddrSpace=*/AMDGPUAS::CONSTANT_ADDRESS);
+
+ // TODO: Classic passes CanBeFlattened=false here to keep a struct intact;
+ // ArgInfo cannot model that yet, so a multi-field record coerce may be
+ // flattened.
+ return ArgInfo::getDirect(Ty);
+}
+
+ArgInfo AMDGPUTargetInfo::classifyArgumentType(const Type *Ty, bool Variadic,
+ unsigned &NumRegsLeft) const {
+ assert(NumRegsLeft <= MaxNumRegsForArgsRet && "register estimate underflow");
+
+ Ty = useFirstFieldIfTransparentUnion(Ty);
+
+ // TODO: Classic sets CanBeFlattened=false for variadics; not modeled here.
+ if (Variadic)
+ return ArgInfo::getDirect();
+
+ if (isAggregateTypeForABI(Ty)) {
+ // Records with non-trivial destructors/copy-constructors should not be
+ // passed by value.
+ if (RecordArgABI RAA = getRecordArgABI(Ty); RAA != RAA_Default)
+ return ArgInfo::getIndirect(Ty->getAlignment(),
+ /*ByVal=*/RAA == RAA_DirectInMemory,
+ /*AddrSpace=*/AMDGPUAS::PRIVATE_ADDRESS);
+
+ // Ignore empty structs/unions.
+ if (const auto *RT = dyn_cast<RecordType>(Ty); RT && RT->isEmpty())
+ return ArgInfo::getIgnore();
+
+ // Lower single-element structs to just pass a regular value.
+ if (const Type *SeltTy = isSingleElementStruct(Ty))
+ return ArgInfo::getDirect(SeltTy);
+
+ if (const auto *RT = dyn_cast<RecordType>(Ty);
+ RT && RT->hasFlexibleArrayMember())
+ return classifyDefaultType(Ty, /*IsReturn=*/false);
+
+ // Pack aggregates <= 8 bytes into single VGPR or pair.
+ uint64_t Size = Ty->getSizeInBits().getFixedValue();
+ if (Size <= 64) {
+ unsigned NumRegs = (Size + 31) / 32;
+ NumRegsLeft -= std::min(NumRegsLeft, NumRegs);
+
+ if (Size <= 16)
+ return ArgInfo::getDirect(TB.getIntegerType(16, Align(2), false));
+
+ if (Size <= 32)
+ return ArgInfo::getDirect(TB.getIntegerType(32, Align(4), false));
+
+ const Type *I32Ty = TB.getIntegerType(32, Align(4), false);
+ return ArgInfo::getDirect(TB.getArrayType(I32Ty, 2, /*SizeInBits=*/64));
+ }
+
+ if (NumRegsLeft > 0) {
+ uint64_t NumRegs = numRegsForType(Ty);
+ if (NumRegsLeft >= NumRegs) {
+ NumRegsLeft -= NumRegs;
+ return ArgInfo::getDirect();
+ }
+ }
+
+ // Pass a struct argument by reference rather than by value.
+ return ArgInfo::getIndirect(Ty->getAlignment(), /*ByVal=*/false,
+ /*AddrSpace=*/AMDGPUAS::PRIVATE_ADDRESS);
+ }
+
+ // Otherwise just do the default thing.
+ ArgInfo AI = classifyDefaultType(Ty, /*IsReturn=*/false);
+ if (!AI.isIndirect()) {
+ uint64_t NumRegs = numRegsForType(Ty);
+ NumRegsLeft -= std::min(NumRegs, uint64_t{NumRegsLeft});
+ }
+
+ return AI;
+}
+
+void AMDGPUTargetInfo::computeInfo(FunctionInfo &FI) const {
+ CallingConv::ID CC = FI.getCallingConvention();
+
+ if (!maybeCommonClassifyReturnType(FI))
+ FI.getReturnInfo() = classifyReturnType(FI.getReturnType());
+
+ unsigned ArgumentIndex = 0;
+ const unsigned NumFixedArguments = FI.getNumRequiredArgs();
+
+ unsigned NumRegsLeft = MaxNumRegsForArgsRet;
+ for (ArgEntry &Arg : FI.arguments()) {
+ if (CC == CallingConv::AMDGPU_KERNEL) {
+ Arg.Info = classifyKernelArgumentType(Arg.ABIType);
+ } else {
+ bool FixedArgument = ArgumentIndex++ < NumFixedArguments;
+ Arg.Info = classifyArgumentType(Arg.ABIType, !FixedArgument, NumRegsLeft);
+ }
+ }
+}
+
+std::unique_ptr<TargetInfo> createAMDGPUTargetInfo(TypeBuilder &TB) {
+ return std::make_unique<AMDGPUTargetInfo>(TB, ABICompatInfo());
+}
+
+} // namespace abi
+} // namespace llvm
diff --git a/llvm/lib/ABI/Targets/X86.cpp b/llvm/lib/ABI/Targets/X86.cpp
index 306068110c849..39ac0bdf76305 100644
--- a/llvm/lib/ABI/Targets/X86.cpp
+++ b/llvm/lib/ABI/Targets/X86.cpp
@@ -100,7 +100,6 @@ class X86_64TargetInfo : public TargetInfo {
ArgInfo getIndirectReturnResult(const Type *Ty) const;
const Type *getFPTypeAtOffset(const Type *Ty, unsigned Offset) const;
- const Type *isSingleElementStruct(const Type *Ty) const;
const Type *getByteVectorType(const Type *Ty) const;
const Type *createPairType(const Type *Lo, const Type *Hi) const;
@@ -1244,60 +1243,6 @@ const Type *X86_64TargetInfo::getByteVectorType(const Type *Ty) const {
ElementCount::getFixed(Size / 64), Align(Size / 8));
}
-// Returns the single element if this is a single-element struct wrapper
-const Type *X86_64TargetInfo::isSingleElementStruct(const Type *Ty) const {
- const auto *RT = dyn_cast<RecordType>(Ty);
- if (!RT)
- return nullptr;
-
- if (RT->hasFlexibleArrayMember())
- return nullptr;
-
- const Type *Found = nullptr;
-
- for (const auto &Base : RT->getBaseClasses()) {
- const Type *BaseTy = Base.FieldType;
- auto *BaseRT = dyn_cast<RecordType>(BaseTy);
-
- if (!BaseRT || BaseRT->isEmpty())
- continue;
-
- const Type *Elem = isSingleElementStruct(BaseTy);
- if (!Elem || Found)
- return nullptr;
- Found = Elem;
- }
-
- for (const auto &FI : RT->getFields()) {
- if (FI.isEmpty())
- continue;
-
- const Type *FTy = FI.FieldType;
-
- while (auto *AT = dyn_cast<ArrayType>(FTy)) {
- if (AT->getNumElements() != 1)
- break;
- FTy = AT->getElementType();
- }
-
- const Type *Elem;
- if (auto *InnerRT = dyn_cast<RecordType>(FTy))
- Elem = isSingleElementStruct(InnerRT);
- else
- Elem = FTy;
- if (!Elem || Found)
- return nullptr;
- Found = Elem;
- }
-
- if (!Found)
- return nullptr;
- if (Found->getSizeInBits() != Ty->getSizeInBits())
- return nullptr;
-
- return Found;
-}
-
bool X86_64TargetInfo::isIllegalVectorType(const Type *Ty) const {
if (const auto *VecTy = dyn_cast<VectorType>(Ty)) {
uint64_t Size = VecTy->getSizeInBits().getFixedValue();
diff --git a/llvm/unittests/ABI/AMDGPUTargetInfoTest.cpp b/llvm/unittests/ABI/AMDGPUTargetInfoTest.cpp
new file mode 100644
index 0000000000000..38d2bbfc37017
--- /dev/null
+++ b/llvm/unittests/ABI/AMDGPUTargetInfoTest.cpp
@@ -0,0 +1,274 @@
+//===- AMDGPUTargetInfoTest.cpp - AMDGPU ABI unit tests -------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ABI/FunctionInfo.h"
+#include "llvm/ABI/TargetInfo.h"
+#include "llvm/ABI/Types.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/IR/CallingConv.h"
+#include "llvm/Support/AMDGPUAddrSpace.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Allocator.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+// RecordFlags' bitmask operators are declared in namespace llvm, so combining
+// two of them needs that namespace visible.
+using namespace llvm;
+
+using ABIType = llvm::abi::Type;
+using llvm::abi::ABICompatInfo;
+using llvm::abi::ArgInfo;
+using llvm::abi::createAMDGPUTargetInfo;
+using llvm::abi::FieldInfo;
+using llvm::abi::FunctionInfo;
+using llvm::abi::RecordFlags;
+using llvm::abi::RequiredArgs;
+using llvm::abi::StructPacking;
+using llvm::abi::TargetInfo;
+using llvm::abi::TypeBuilder;
+
+class AMDGPUTargetInfoTest : public ::testing::Test {
+protected:
+ llvm::BumpPtrAllocator Alloc;
+ TypeBuilder TB;
+ const ABIType *I8;
+ const ABIType *I16;
+ const ABIType *I32;
+ const ABIType *F32;
+ const ABIType *Void;
+ /// An empty class: a record with no fields, one byte wide, register-passable.
+ const ABIType *Empty;
+
+ AMDGPUTargetInfoTest()
+ : TB(Alloc), I8(TB.getIntegerType(8, llvm::Align(1), /*Signed=*/true)),
+ I16(TB.getIntegerType(16, llvm::Align(2), /*Signed=*/true)),
+ I32(TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true)),
+ F32(TB.getFloatType(llvm::APFloat::IEEEsingle(), llvm::Align(4))),
+ Void(TB.getVoidType()),
+ Empty(TB.getRecordType({}, llvm::TypeSize::getFixed(8), llvm::Align(1),
+ StructPacking::Default, {}, {},
+ RecordFlags::CanPassInRegisters)) {}
+
+ std::unique_ptr<TargetInfo> target() const {
+ return createAMDGPUTargetInfo(const_cast<TypeBuilder &>(TB));
+ }
+
+ /// A register-passable record with the given fields, size and alignment.
+ const ABIType *recordOf(llvm::ArrayRef<FieldInfo> Fields, uint64_t SizeInBits,
+ llvm::Align Alignment) {
+ return TB.getRecordType(Fields, llvm::TypeSize::getFixed(SizeInBits),
+ Alignment, StructPacking::Default, {}, {},
+ RecordFlags::CanPassInRegisters);
+ }
+
+ /// The argument classification the target computes for a single parameter
+ /// under calling convention \p CC.
+ const ArgInfo &classifyArg(const ABIType *ArgTy,
+ std::unique_ptr<FunctionInfo> &FI,
+ std::unique_ptr<TargetInfo> &TI,
+ CallingConv::ID CC = CallingConv::C) {
+ TI = target();
+ FI = FunctionInfo::create(CC, Void, {ArgTy});
+ TI->computeInfo(*FI);
+ return FI->getArgInfo(0).Info;
+ }
+
+ /// The return classification the target computes for \p RetTy.
+ const ArgInfo &classifyRet(const ABIType *RetTy,
+ std::unique_ptr<FunctionInfo> &FI,
+ std::unique_ptr<TargetInfo> &TI) {
+ TI = target();
+ FI = FunctionInfo::create(CallingConv::C, RetTy, {});
+ TI->computeInfo(*FI);
+ return FI->getReturnInfo();
+ }
+};
+
+static void expectUncoercedDirect(const ArgInfo &Info) {
+ ASSERT_TRUE(Info.isDirect());
+ EXPECT_EQ(Info.getCoerceToType(), nullptr);
+}
+
+static void expectDirectInteger(const ArgInfo &Info, unsigned Bits) {
+ ASSERT_TRUE(Info.isDirect());
+ const ABIType *Coerce = Info.getCoerceToType();
+ ASSERT_NE(Coerce, nullptr);
+ const auto *IT = llvm::dyn_cast<llvm::abi::IntegerType>(Coerce);
+ ASSERT_NE(IT, nullptr);
+ EXPECT_EQ(IT->getSizeInBits().getFixedValue(), Bits);
+}
+
+static void expectDirectFloat(const ArgInfo &Info,
+ const llvm::fltSemantics &Sem) {
+ ASSERT_TRUE(Info.isDirect());
+ const ABIType *Coerce = Info.getCoerceToType();
+ ASSERT_NE(Coerce, nullptr);
+ const auto *FT = llvm::dyn_cast<llvm::abi::FloatType>(Coerce);
+ ASSERT_NE(FT, nullptr);
+ EXPECT_EQ(FT->getSemantics(), &Sem);
+}
+
+// A <= 8-byte aggregate coerces to [2 x i32].
+static void expectDirectI32Pair(const ArgInfo &Info) {
+ ASSERT_TRUE(Info.isDirect());
+ const auto *AT =
+ llvm::dyn_cast_or_null<llvm::abi::ArrayType>(Info.getCoerceToType());
+ ASSERT_NE(AT, nullptr);
+ EXPECT_EQ(AT->getNumElements(), 2u);
+ const auto *IT = llvm::dyn_cast<llvm::abi::IntegerType>(AT->getElementType());
+ ASSERT_NE(IT, nullptr);
+ EXPECT_EQ(IT->getSizeInBits().getFixedValue(), 32u);
+}
+
+static void expectIndirect(const ArgInfo &Info, llvm::Align ExpectedAlign,
+ bool ByVal, unsigned AddrSpace) {
+ ASSERT_TRUE(Info.isIndirect());
+ EXPECT_EQ(Info.getIndirectAlign(), ExpectedAlign);
+ EXPECT_EQ(Info.getIndirectByVal(), ByVal);
+ EXPECT_EQ(Info.getIndirectAddrSpace(), AddrSpace);
+}
+
+// A 32-bit integer and a pointer-sized scalar pass directly in their own type.
+TEST_F(AMDGPUTargetInfoTest, ScalarPassesDirect) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ expectUncoercedDirect(classifyArg(I32, FI, TI));
+ expectUncoercedDirect(classifyArg(F32, FI, TI));
+}
+
+// A sub-word integer is sign/zero extended to fill its register.
+TEST_F(AMDGPUTargetInfoTest, PromotableIntegerExtends) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ const ArgInfo &Info = classifyArg(I8, FI, TI);
+ ASSERT_TRUE(Info.isExtend());
+ EXPECT_TRUE(Info.isSignExt());
+}
+
+// Aggregates <= 16/32/64 bits pack into i16 / i32 / [2 x i32].
+TEST_F(AMDGPUTargetInfoTest, SmallAggregatesPackIntoRegisters) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+
+ const ABIType *S16 =
+ recordOf({FieldInfo(I8, 0), FieldInfo(I8, 8)}, 16, llvm::Align(1));
+ expectDirectInteger(classifyArg(S16, FI, TI), 16);
+
+ const ABIType *S32 =
+ recordOf({FieldInfo(I16, 0), FieldInfo(I16, 16)}, 32, llvm::Align(2));
+ expectDirectInteger(classifyArg(S32, FI, TI), 32);
+
+ const ABIType *S64 =
+ recordOf({FieldInfo(I32, 0), FieldInfo(I32, 32)}, 64, llvm::Align(4));
+ expectDirectI32Pair(classifyArg(S64, FI, TI));
+}
+
+// An empty struct is dropped from the argument list.
+TEST_F(AMDGPUTargetInfoTest, EmptyAggregateIsIgnored) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ EXPECT_TRUE(classifyArg(Empty, FI, TI).isIgnore());
+}
+
+// A single-element struct is passed as its inner scalar.
+TEST_F(AMDGPUTargetInfoTest, SingleElementStructUnwraps) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ const ABIType *Wrapper = recordOf({FieldInfo(F32, 0)}, 32, llvm::Align(4));
+ expectDirectFloat(classifyArg(Wrapper, FI, TI), llvm::APFloat::IEEEsingle());
+}
+
+// A large aggregate that does not fit the 16-register budget is passed by
+// reference in the private address space.
+TEST_F(AMDGPUTargetInfoTest, OversizedAggregateIsIndirectPrivate) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ // Two i32[10] fields => 20 registers, above MaxNumRegsForArgsRet (16).
+ const ABIType *ArrTy = TB.getArrayType(I32, /*NumElements=*/10,
+ /*SizeInBits=*/320);
+ const ABIType *Big = recordOf({FieldInfo(ArrTy, 0), FieldInfo(ArrTy, 320)},
+ 640, llvm::Align(4));
+ expectIndirect(classifyArg(Big, FI, TI), llvm::Align(4), /*ByVal=*/false,
+ llvm::AMDGPUAS::PRIVATE_ADDRESS);
+}
+
+// A record that cannot pass in registers (non-trivial C++ type) is passed
+// indirectly in the private address space.
+TEST_F(AMDGPUTargetInfoTest, NonTrivialRecordIsIndirectPrivate) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ const ABIType *CannotPass = TB.getRecordType(
+ {FieldInfo(I32, 0)}, llvm::TypeSize::getFixed(32), llvm::Align(4),
+ StructPacking::Default, {}, {}, RecordFlags::IsCXXRecord);
+ expectIndirect(classifyArg(CannotPass, FI, TI), llvm::Align(4),
+ /*ByVal=*/false, llvm::AMDGPUAS::PRIVATE_ADDRESS);
+}
+
+// A variadic argument bypasses register packing and passes through unchanged.
+TEST_F(AMDGPUTargetInfoTest, VariadicArgumentPassesDirect) {
+ std::unique_ptr<TargetInfo> TI = target();
+ const ABIType *S16 =
+ recordOf({FieldInfo(I8, 0), FieldInfo(I8, 8)}, 16, llvm::Align(1));
+ // Zero declared parameters, so the sole argument is variadic.
+ std::unique_ptr<FunctionInfo> FI =
+ FunctionInfo::create(CallingConv::C, Void, {S16}, RequiredArgs(0));
+ TI->computeInfo(*FI);
+ expectUncoercedDirect(FI->getArgInfo(0).Info);
+}
+
+// Kernel aggregate arguments are passed by reference in the constant address
+// space (the kernarg segment), never byval.
+TEST_F(AMDGPUTargetInfoTest, KernelAggregateIsIndirectConstant) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ const ABIType *S =
+ recordOf({FieldInfo(I32, 0), FieldInfo(I32, 32)}, 64, llvm::Align(4));
+ expectIndirect(classifyArg(S, FI, TI, CallingConv::AMDGPU_KERNEL),
+ llvm::Align(4), /*ByVal=*/false,
+ llvm::AMDGPUAS::CONSTANT_ADDRESS);
+}
+
+// Kernel scalar arguments are passed directly.
+TEST_F(AMDGPUTargetInfoTest, KernelScalarIsDirect) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ expectDirectInteger(classifyArg(I32, FI, TI, CallingConv::AMDGPU_KERNEL), 32);
+}
+
+// A void return is ignored.
+TEST_F(AMDGPUTargetInfoTest, VoidReturnIsIgnored) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ EXPECT_TRUE(classifyRet(Void, FI, TI).isIgnore());
+}
+
+// A <= 8-byte aggregate return packs into [2 x i32].
+TEST_F(AMDGPUTargetInfoTest, SmallAggregateReturnPacks) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ const ABIType *S64 =
+ recordOf({FieldInfo(I32, 0), FieldInfo(I32, 32)}, 64, llvm::Align(4));
+ expectDirectI32Pair(classifyRet(S64, FI, TI));
+}
+
+// A record that cannot pass in registers is returned indirectly (sret-style,
+// ByVal=false) via the target-independent return rule.
+TEST_F(AMDGPUTargetInfoTest, NonTrivialRecordReturnIsIndirect) {
+ std::unique_ptr<FunctionInfo> FI;
+ std::unique_ptr<TargetInfo> TI;
+ const ABIType *CannotPass = TB.getRecordType(
+ {FieldInfo(I32, 0)}, llvm::TypeSize::getFixed(32), llvm::Align(4),
+ StructPacking::Default, {}, {}, RecordFlags::IsCXXRecord);
+ const ArgInfo &Info = classifyRet(CannotPass, FI, TI);
+ ASSERT_TRUE(Info.isIndirect());
+ EXPECT_FALSE(Info.getIndirectByVal());
+}
+
+} // namespace
diff --git a/llvm/unittests/ABI/CMakeLists.txt b/llvm/unittests/ABI/CMakeLists.txt
index a26da474bd83a..6a6a8556663fe 100644
--- a/llvm/unittests/ABI/CMakeLists.txt
+++ b/llvm/unittests/ABI/CMakeLists.txt
@@ -6,5 +6,6 @@ set(LLVM_LINK_COMPONENTS
add_llvm_unittest(ABITests
AArch64TargetInfoTest.cpp
+ AMDGPUTargetInfoTest.cpp
X86TargetInfoTest.cpp
)
More information about the llvm-commits
mailing list