[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