[llvm] [ABI] Add DefaultTargetInfo for default argument/return classification (PR #225009)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 22 00:32:42 PDT 2026


https://github.com/skc7 updated https://github.com/llvm/llvm-project/pull/225009

>From 85ae9c165caa427889b627ccc52701640ab3cb2c Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Mon, 21 Sep 2026 11:45:48 +0530
Subject: [PATCH 1/4] [ABI] Add default argument/return classifiers to
 TargetInfo

---
 llvm/include/llvm/ABI/TargetInfo.h    |   8 ++
 llvm/lib/ABI/TargetInfo.cpp           |  39 ++++++++
 llvm/unittests/ABI/TargetInfoTest.cpp | 129 ++++++++++++++++++++++++++
 3 files changed, 176 insertions(+)

diff --git a/llvm/include/llvm/ABI/TargetInfo.h b/llvm/include/llvm/ABI/TargetInfo.h
index 956c68ddb7a63..a818bd7761dcc 100644
--- a/llvm/include/llvm/ABI/TargetInfo.h
+++ b/llvm/include/llvm/ABI/TargetInfo.h
@@ -95,6 +95,14 @@ class TargetInfo {
   /// Returns the scalar a single-element struct reduces to, else null.
   LLVM_ABI const Type *isSingleElementStruct(const Type *Ty) const;
 
+  /// Default argument classification for targets without special rules.
+  /// Aggregates are passed indirectly and small integers are promoted.
+  LLVM_ABI ArgInfo classifyDefaultArgumentType(const Type *Ty) const;
+
+  /// Default return classification for targets without special rules.
+  /// Void is ignored and aggregates are returned indirectly.
+  LLVM_ABI ArgInfo classifyDefaultReturnType(const Type *RetTy) 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 6fe2b68c001bc..8b2bfcd98de2d 100644
--- a/llvm/lib/ABI/TargetInfo.cpp
+++ b/llvm/lib/ABI/TargetInfo.cpp
@@ -128,6 +128,45 @@ const Type *TargetInfo::isSingleElementStruct(const Type *Ty) const {
   return Found;
 }
 
+ArgInfo TargetInfo::classifyDefaultArgumentType(const Type *Ty) const {
+  Ty = useFirstFieldIfTransparentUnion(Ty);
+
+  if (isAggregateTypeForABI(Ty)) {
+    // A record that must stay in memory keeps ByVal, everything else indirect.
+    if (RecordArgABI RAA = getRecordArgABI(Ty))
+      return getNaturalAlignIndirect(Ty, /*ByVal=*/RAA == RAA_DirectInMemory);
+    return getNaturalAlignIndirect(Ty);
+  }
+
+  if (const auto *IT = dyn_cast<IntegerType>(Ty)) {
+    // A _BitInt wider than 128 bits does not fit in registers.
+    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
+      return getNaturalAlignIndirect(Ty);
+    if (isPromotableInteger(IT))
+      return ArgInfo::getExtend(Ty);
+  }
+
+  return ArgInfo::getDirect();
+}
+
+ArgInfo TargetInfo::classifyDefaultReturnType(const Type *RetTy) const {
+  if (RetTy->isVoid())
+    return ArgInfo::getIgnore();
+
+  // Return values never use ByVal, matching the library's return convention.
+  if (isAggregateTypeForABI(RetTy))
+    return getNaturalAlignIndirect(RetTy, /*ByVal=*/false);
+
+  if (const auto *IT = dyn_cast<IntegerType>(RetTy)) {
+    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
+      return getNaturalAlignIndirect(RetTy, /*ByVal=*/false);
+    if (isPromotableInteger(IT))
+      return ArgInfo::getExtend(RetTy);
+  }
+
+  return ArgInfo::getDirect();
+}
+
 bool TargetInfo::maybeCommonClassifyReturnType(FunctionInfo &FI) const {
   const abi::Type *Ty = FI.getReturnType();
 
diff --git a/llvm/unittests/ABI/TargetInfoTest.cpp b/llvm/unittests/ABI/TargetInfoTest.cpp
index c26d2cf6bea9a..4e20fb27a2fca 100644
--- a/llvm/unittests/ABI/TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/TargetInfoTest.cpp
@@ -15,7 +15,10 @@
 #include "llvm/ABI/TargetInfo.h"
 #include "llvm/ABI/FunctionInfo.h"
 #include "llvm/ABI/Types.h"
+<<<<<<< HEAD
 #include "llvm/ADT/APFloat.h"
+=======
+>>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 #include "llvm/Support/Alignment.h"
 #include "llvm/Support/Allocator.h"
 #include "gtest/gtest.h"
@@ -27,6 +30,10 @@ namespace {
 using namespace llvm;
 
 using ABIType = llvm::abi::Type;
+<<<<<<< HEAD
+=======
+using llvm::abi::ArgInfo;
+>>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 using llvm::abi::FieldInfo;
 using llvm::abi::FunctionInfo;
 using llvm::abi::RecordFlags;
@@ -34,8 +41,14 @@ using llvm::abi::StructPacking;
 using llvm::abi::TargetInfo;
 using llvm::abi::TypeBuilder;
 
+<<<<<<< HEAD
 // A minimal concrete target that re-exposes the shared, protected helpers so
 // they can be exercised directly, independent of any real target.
+=======
+// A minimal concrete target that re-exposes the shared, protected default
+// classifiers so they can be exercised directly, independent of any real
+// target.
+>>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 class TestTargetInfo : public TargetInfo {
 public:
   explicit TestTargetInfo(TypeBuilder &Builder) : TargetInfo(Builder) {}
@@ -43,7 +56,12 @@ class TestTargetInfo : public TargetInfo {
   const llvm::abi::ABICompatInfo &getABICompatInfo() const override {
     return Compat;
   }
+<<<<<<< HEAD
   using TargetInfo::isSingleElementStruct;
+=======
+  using TargetInfo::classifyDefaultArgumentType;
+  using TargetInfo::classifyDefaultReturnType;
+>>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 
 private:
   llvm::abi::ABICompatInfo Compat;
@@ -55,13 +73,20 @@ class TargetInfoTest : public ::testing::Test {
   TypeBuilder TB;
   const ABIType *I16;
   const ABIType *I32;
+<<<<<<< HEAD
   const ABIType *F32;
   /// An empty class: a record with no fields, one byte wide.
   const ABIType *Empty;
+=======
+  const ABIType *Void;
+  /// A _BitInt wider than 128 bits, which cannot be passed in registers.
+  const ABIType *WideBitInt;
+>>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 
   TargetInfoTest()
       : TB(Alloc), I16(TB.getIntegerType(16, llvm::Align(2), /*Signed=*/true)),
         I32(TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true)),
+<<<<<<< HEAD
         F32(TB.getFloatType(llvm::APFloat::IEEEsingle(), llvm::Align(4))),
         Empty(TB.getRecordType({}, llvm::TypeSize::getFixed(8), llvm::Align(1),
                                /*UnadjustedAlign=*/llvm::Align(1),
@@ -142,6 +167,110 @@ TEST_F(TargetInfoTest, SingleElementStructNestedSingleElementReduces) {
 // A non-record type is never a single-element struct.
 TEST_F(TargetInfoTest, SingleElementStructNonRecordReturnsNull) {
   EXPECT_EQ(singleElement(I32), nullptr);
+=======
+        Void(TB.getVoidType()),
+        WideBitInt(TB.getIntegerType(129, llvm::Align(8), /*Signed=*/true,
+                                     /*IsBitInt=*/true)) {}
+
+  /// A record with a single int field, passable in registers.
+  const ABIType *recordInReg() {
+    return TB.getRecordType({FieldInfo(I32, 0)}, llvm::TypeSize::getFixed(32),
+                            llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4),
+                            StructPacking::Default, {}, {},
+                            RecordFlags::CanPassInRegisters);
+  }
+
+  /// The same record marked as unable to pass in registers, e.g. a non-trivial
+  /// C++ type. This takes the RAA_Indirect path.
+  const ABIType *recordInMemory() {
+    return TB.getRecordType({FieldInfo(I32, 0)}, llvm::TypeSize::getFixed(32),
+                            llvm::Align(4), /*UnadjustedAlign=*/llvm::Align(4),
+                            StructPacking::Default, {}, {}, RecordFlags::None);
+  }
+
+  ArgInfo classifyArg(const ABIType *Ty) {
+    TestTargetInfo TI(TB);
+    return TI.classifyDefaultArgumentType(Ty);
+  }
+
+  ArgInfo classifyRet(const ABIType *Ty) {
+    TestTargetInfo TI(TB);
+    return TI.classifyDefaultReturnType(Ty);
+  }
+};
+
+// --- Argument classification -------------------------------------------------
+
+// A word-sized integer is passed directly.
+TEST_F(TargetInfoTest, DefaultArgIntIsDirect) {
+  ArgInfo Info = classifyArg(I32);
+  EXPECT_TRUE(Info.isDirect());
+}
+
+// A sub-word integer is promoted.
+TEST_F(TargetInfoTest, DefaultArgSmallIntIsExtended) {
+  EXPECT_TRUE(classifyArg(I16).isExtend());
+}
+
+// A record that fits in registers is passed indirectly by value.
+TEST_F(TargetInfoTest, DefaultArgRecordInRegIsIndirectByVal) {
+  ArgInfo Info = classifyArg(recordInReg());
+  ASSERT_TRUE(Info.isIndirect());
+  EXPECT_TRUE(Info.getIndirectByVal());
+}
+
+// A record that cannot pass in registers goes indirect without ByVal.
+TEST_F(TargetInfoTest, DefaultArgRecordInMemoryIsIndirectNoByVal) {
+  ArgInfo Info = classifyArg(recordInMemory());
+  ASSERT_TRUE(Info.isIndirect());
+  EXPECT_FALSE(Info.getIndirectByVal());
+}
+
+// A _BitInt wider than 128 bits is passed indirectly.
+TEST_F(TargetInfoTest, DefaultArgWideBitIntIsIndirect) {
+  EXPECT_TRUE(classifyArg(WideBitInt).isIndirect());
+}
+
+// A transparent union is classified as its first field, so a union of one int
+// is passed directly rather than as an aggregate.
+TEST_F(TargetInfoTest, DefaultArgTransparentUnionUsesFirstField) {
+  const ABIType *U = TB.getUnionType(
+      {FieldInfo(I32, 0)}, llvm::TypeSize::getFixed(32), llvm::Align(4),
+      /*UnadjustedAlign=*/llvm::Align(4), StructPacking::Default,
+      RecordFlags::IsTransparent | RecordFlags::CanPassInRegisters);
+  EXPECT_TRUE(classifyArg(U).isDirect());
+}
+
+// --- Return classification ---------------------------------------------------
+
+// Void returns are ignored.
+TEST_F(TargetInfoTest, DefaultReturnVoidIsIgnored) {
+  EXPECT_TRUE(classifyRet(Void).isIgnore());
+}
+
+// A word-sized integer is returned directly.
+TEST_F(TargetInfoTest, DefaultReturnIntIsDirect) {
+  EXPECT_TRUE(classifyRet(I32).isDirect());
+}
+
+// A sub-word integer is promoted on return.
+TEST_F(TargetInfoTest, DefaultReturnSmallIntIsExtended) {
+  EXPECT_TRUE(classifyRet(I16).isExtend());
+}
+
+// An aggregate is returned indirectly, and returns never use ByVal.
+TEST_F(TargetInfoTest, DefaultReturnRecordIsIndirectNoByVal) {
+  ArgInfo Info = classifyRet(recordInReg());
+  ASSERT_TRUE(Info.isIndirect());
+  EXPECT_FALSE(Info.getIndirectByVal());
+}
+
+// A _BitInt wider than 128 bits is returned indirectly.
+TEST_F(TargetInfoTest, DefaultReturnWideBitIntIsIndirect) {
+  ArgInfo Info = classifyRet(WideBitInt);
+  ASSERT_TRUE(Info.isIndirect());
+  EXPECT_FALSE(Info.getIndirectByVal());
+>>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 }
 
 } // namespace

>From 24088afc7205cb7b6b31cc80021d24ab5fa0113f Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Mon, 21 Sep 2026 11:53:38 +0530
Subject: [PATCH 2/4] update

---
 llvm/lib/ABI/TargetInfo.cpp           |   6 +-
 llvm/unittests/ABI/TargetInfoTest.cpp | 105 --------------------------
 2 files changed, 3 insertions(+), 108 deletions(-)

diff --git a/llvm/lib/ABI/TargetInfo.cpp b/llvm/lib/ABI/TargetInfo.cpp
index 8b2bfcd98de2d..ec92ccbe2e3f0 100644
--- a/llvm/lib/ABI/TargetInfo.cpp
+++ b/llvm/lib/ABI/TargetInfo.cpp
@@ -153,13 +153,13 @@ ArgInfo TargetInfo::classifyDefaultReturnType(const Type *RetTy) const {
   if (RetTy->isVoid())
     return ArgInfo::getIgnore();
 
-  // Return values never use ByVal, matching the library's return convention.
   if (isAggregateTypeForABI(RetTy))
-    return getNaturalAlignIndirect(RetTy, /*ByVal=*/false);
+    return getNaturalAlignIndirect(RetTy);
 
   if (const auto *IT = dyn_cast<IntegerType>(RetTy)) {
+    // A _BitInt wider than 128 bits does not fit in registers.
     if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
-      return getNaturalAlignIndirect(RetTy, /*ByVal=*/false);
+      return getNaturalAlignIndirect(RetTy);
     if (isPromotableInteger(IT))
       return ArgInfo::getExtend(RetTy);
   }
diff --git a/llvm/unittests/ABI/TargetInfoTest.cpp b/llvm/unittests/ABI/TargetInfoTest.cpp
index 4e20fb27a2fca..1f53776417ea2 100644
--- a/llvm/unittests/ABI/TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/TargetInfoTest.cpp
@@ -15,10 +15,6 @@
 #include "llvm/ABI/TargetInfo.h"
 #include "llvm/ABI/FunctionInfo.h"
 #include "llvm/ABI/Types.h"
-<<<<<<< HEAD
-#include "llvm/ADT/APFloat.h"
-=======
->>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 #include "llvm/Support/Alignment.h"
 #include "llvm/Support/Allocator.h"
 #include "gtest/gtest.h"
@@ -30,10 +26,7 @@ namespace {
 using namespace llvm;
 
 using ABIType = llvm::abi::Type;
-<<<<<<< HEAD
-=======
 using llvm::abi::ArgInfo;
->>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 using llvm::abi::FieldInfo;
 using llvm::abi::FunctionInfo;
 using llvm::abi::RecordFlags;
@@ -41,14 +34,9 @@ using llvm::abi::StructPacking;
 using llvm::abi::TargetInfo;
 using llvm::abi::TypeBuilder;
 
-<<<<<<< HEAD
-// A minimal concrete target that re-exposes the shared, protected helpers so
-// they can be exercised directly, independent of any real target.
-=======
 // A minimal concrete target that re-exposes the shared, protected default
 // classifiers so they can be exercised directly, independent of any real
 // target.
->>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 class TestTargetInfo : public TargetInfo {
 public:
   explicit TestTargetInfo(TypeBuilder &Builder) : TargetInfo(Builder) {}
@@ -56,12 +44,8 @@ class TestTargetInfo : public TargetInfo {
   const llvm::abi::ABICompatInfo &getABICompatInfo() const override {
     return Compat;
   }
-<<<<<<< HEAD
-  using TargetInfo::isSingleElementStruct;
-=======
   using TargetInfo::classifyDefaultArgumentType;
   using TargetInfo::classifyDefaultReturnType;
->>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 
 private:
   llvm::abi::ABICompatInfo Compat;
@@ -73,101 +57,13 @@ class TargetInfoTest : public ::testing::Test {
   TypeBuilder TB;
   const ABIType *I16;
   const ABIType *I32;
-<<<<<<< HEAD
-  const ABIType *F32;
-  /// An empty class: a record with no fields, one byte wide.
-  const ABIType *Empty;
-=======
   const ABIType *Void;
   /// A _BitInt wider than 128 bits, which cannot be passed in registers.
   const ABIType *WideBitInt;
->>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 
   TargetInfoTest()
       : TB(Alloc), I16(TB.getIntegerType(16, llvm::Align(2), /*Signed=*/true)),
         I32(TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true)),
-<<<<<<< HEAD
-        F32(TB.getFloatType(llvm::APFloat::IEEEsingle(), llvm::Align(4))),
-        Empty(TB.getRecordType({}, llvm::TypeSize::getFixed(8), llvm::Align(1),
-                               /*UnadjustedAlign=*/llvm::Align(1),
-                               StructPacking::Default, {}, {},
-                               RecordFlags::CanPassInRegisters)) {}
-
-  const ABIType *recordOf(llvm::ArrayRef<FieldInfo> Fields, uint64_t SizeInBits,
-                          llvm::Align Alignment) {
-    return TB.getRecordType(Fields, llvm::TypeSize::getFixed(SizeInBits),
-                            Alignment, Alignment, StructPacking::Default, {},
-                            {}, RecordFlags::CanPassInRegisters);
-  }
-
-  /// The single-element reduction of \p Ty, or null if it is not a
-  /// single-element struct. Exercises the shared TargetInfo helper directly.
-  const ABIType *singleElement(const ABIType *Ty) {
-    TestTargetInfo TI(TB);
-    return TI.isSingleElementStruct(Ty);
-  }
-};
-
-// The shared single-element-struct reduction, used by getByteVectorType and by
-// other targets, looks through single-element wrappers to the scalar element.
-
-// A struct of one float reduces to its single float element.
-TEST_F(TargetInfoTest, SingleElementStructSingleScalarFieldReduces) {
-  const ABIType *S = recordOf({FieldInfo(F32, 0)}, 32, llvm::Align(4));
-  EXPECT_EQ(singleElement(S), F32);
-}
-
-// A single-element array is transparent, so a struct of one float[1] reduces
-// to the element type.
-TEST_F(TargetInfoTest, SingleElementStructSingleElementArrayReduces) {
-  const ABIType *Arr =
-      TB.getArrayType(F32, /*NumElements=*/1, /*SizeInBits=*/32);
-  const ABIType *S = recordOf({FieldInfo(Arr, 0)}, 32, llvm::Align(4));
-  EXPECT_EQ(singleElement(S), F32);
-}
-
-// A multi-element array is an aggregate that does not itself reduce, so a
-// struct of one short[2] is NOT a single-element struct.
-TEST_F(TargetInfoTest, SingleElementStructMultiElementArrayDoesNotReduce) {
-  const ABIType *Arr =
-      TB.getArrayType(I16, /*NumElements=*/2, /*SizeInBits=*/32);
-  const ABIType *S = recordOf({FieldInfo(Arr, 0)}, 32, llvm::Align(2));
-  EXPECT_EQ(singleElement(S), nullptr);
-}
-
-// Two data members: not a single-element struct.
-TEST_F(TargetInfoTest, SingleElementStructTwoFieldsDoNotReduce) {
-  const ABIType *S =
-      recordOf({FieldInfo(I32, 0), FieldInfo(I32, 32)}, 64, llvm::Align(4));
-  EXPECT_EQ(singleElement(S), nullptr);
-}
-
-// A single member that leaves tail padding does not cover the record, so it
-// does not reduce.
-TEST_F(TargetInfoTest, SingleElementStructTailPaddingDoesNotReduce) {
-  const ABIType *S = recordOf({FieldInfo(I32, 0)}, 64, llvm::Align(8));
-  EXPECT_EQ(singleElement(S), nullptr);
-}
-
-// An empty member supplies no data and is skipped, so a struct of an empty
-// member plus an int reduces to the int.
-TEST_F(TargetInfoTest, SingleElementStructEmptyMemberIsSkipped) {
-  const ABIType *S =
-      recordOf({FieldInfo(Empty, 0), FieldInfo(I32, 0)}, 32, llvm::Align(4));
-  EXPECT_EQ(singleElement(S), I32);
-}
-
-// A nested single-element struct reduces to the inner scalar.
-TEST_F(TargetInfoTest, SingleElementStructNestedSingleElementReduces) {
-  const ABIType *Inner = recordOf({FieldInfo(F32, 0)}, 32, llvm::Align(4));
-  const ABIType *Outer = recordOf({FieldInfo(Inner, 0)}, 32, llvm::Align(4));
-  EXPECT_EQ(singleElement(Outer), F32);
-}
-
-// A non-record type is never a single-element struct.
-TEST_F(TargetInfoTest, SingleElementStructNonRecordReturnsNull) {
-  EXPECT_EQ(singleElement(I32), nullptr);
-=======
         Void(TB.getVoidType()),
         WideBitInt(TB.getIntegerType(129, llvm::Align(8), /*Signed=*/true,
                                      /*IsBitInt=*/true)) {}
@@ -270,7 +166,6 @@ TEST_F(TargetInfoTest, DefaultReturnWideBitIntIsIndirect) {
   ArgInfo Info = classifyRet(WideBitInt);
   ASSERT_TRUE(Info.isIndirect());
   EXPECT_FALSE(Info.getIndirectByVal());
->>>>>>> [ABI] Add default argument/return classifiers to TargetInfo
 }
 
 } // namespace

>From bb5dcd51678132fa1b2dd3216b03b6a2ad06a0ec Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Tue, 22 Sep 2026 10:19:19 +0530
Subject: [PATCH 3/4] Create DefaultTargetInfo class

---
 llvm/include/llvm/ABI/DefaultTargetInfo.h | 38 ++++++++++++++
 llvm/lib/ABI/CMakeLists.txt               |  1 +
 llvm/lib/ABI/DefaultTargetInfo.cpp        | 60 +++++++++++++++++++++++
 llvm/unittests/ABI/TargetInfoTest.cpp     | 22 ++++-----
 4 files changed, 109 insertions(+), 12 deletions(-)
 create mode 100644 llvm/include/llvm/ABI/DefaultTargetInfo.h
 create mode 100644 llvm/lib/ABI/DefaultTargetInfo.cpp

diff --git a/llvm/include/llvm/ABI/DefaultTargetInfo.h b/llvm/include/llvm/ABI/DefaultTargetInfo.h
new file mode 100644
index 0000000000000..1df4191aca43c
--- /dev/null
+++ b/llvm/include/llvm/ABI/DefaultTargetInfo.h
@@ -0,0 +1,38 @@
+//===----- DefaultTargetInfo.h - Default ABI classification ------- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Default ABI classification shared by targets without special rules. This is
+/// the llvm::abi analogue of clang::CodeGen::DefaultABIInfo: targets delegate
+/// to it for the cases they do not handle themselves.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_ABI_DEFAULTTARGETINFO_H
+#define LLVM_ABI_DEFAULTTARGETINFO_H
+
+#include "llvm/ABI/TargetInfo.h"
+
+namespace llvm {
+namespace abi {
+
+/// Self-consistent classification that conforms to no particular ABI.
+class DefaultTargetInfo : public TargetInfo {
+public:
+  using TargetInfo::TargetInfo;
+
+  LLVM_ABI ArgInfo classifyArgumentType(const Type *Ty) const;
+  LLVM_ABI ArgInfo classifyReturnType(const Type *RetTy) const;
+
+  LLVM_ABI void computeInfo(FunctionInfo &FI) const override;
+};
+
+} // namespace abi
+} // namespace llvm
+
+#endif // LLVM_ABI_DEFAULTTARGETINFO_H
diff --git a/llvm/lib/ABI/CMakeLists.txt b/llvm/lib/ABI/CMakeLists.txt
index 39e725ca9fd3b..831ea0715f7c0 100644
--- a/llvm/lib/ABI/CMakeLists.txt
+++ b/llvm/lib/ABI/CMakeLists.txt
@@ -2,6 +2,7 @@ add_llvm_component_library(LLVMABI
   Types.cpp
   FunctionInfo.cpp
   TargetInfo.cpp
+  DefaultTargetInfo.cpp
   IRTypeMapper.cpp
   Targets/AArch64.cpp
   Targets/BPF.cpp
diff --git a/llvm/lib/ABI/DefaultTargetInfo.cpp b/llvm/lib/ABI/DefaultTargetInfo.cpp
new file mode 100644
index 0000000000000..01380c106e6a3
--- /dev/null
+++ b/llvm/lib/ABI/DefaultTargetInfo.cpp
@@ -0,0 +1,60 @@
+//===- DefaultTargetInfo.cpp - Default ABI classification -----------------===//
+//
+// 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/DefaultTargetInfo.h"
+#include "llvm/Support/Casting.h"
+
+using namespace llvm::abi;
+using llvm::dyn_cast;
+
+ArgInfo DefaultTargetInfo::classifyArgumentType(const Type *Ty) const {
+  Ty = useFirstFieldIfTransparentUnion(Ty);
+
+  if (isAggregateTypeForABI(Ty)) {
+    // Records with non-trivial destructors/copy-constructors should not be
+    // passed by value.
+    if (RecordArgABI RAA = getRecordArgABI(Ty))
+      return getNaturalAlignIndirect(Ty, /*ByVal=*/RAA == RAA_DirectInMemory);
+    return getNaturalAlignIndirect(Ty);
+  }
+
+  if (const auto *IT = dyn_cast<IntegerType>(Ty)) {
+    // A _BitInt wider than 128 bits does not fit in registers.
+    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
+      return getNaturalAlignIndirect(Ty);
+    if (isPromotableInteger(IT))
+      return ArgInfo::getExtend(Ty);
+  }
+
+  return ArgInfo::getDirect();
+}
+
+ArgInfo DefaultTargetInfo::classifyReturnType(const Type *RetTy) const {
+  if (RetTy->isVoid())
+    return ArgInfo::getIgnore();
+
+  if (isAggregateTypeForABI(RetTy))
+    return getNaturalAlignIndirect(RetTy);
+
+  if (const auto *IT = dyn_cast<IntegerType>(RetTy)) {
+    // A _BitInt wider than 128 bits does not fit in registers.
+    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
+      return getNaturalAlignIndirect(RetTy);
+    if (isPromotableInteger(IT))
+      return ArgInfo::getExtend(RetTy);
+  }
+
+  return ArgInfo::getDirect();
+}
+
+void DefaultTargetInfo::computeInfo(FunctionInfo &FI) const {
+  if (!maybeCommonClassifyReturnType(FI))
+    FI.getReturnInfo() = classifyReturnType(FI.getReturnType());
+  for (auto &I : FI.arguments())
+    I.Info = classifyArgumentType(I.ABIType);
+}
diff --git a/llvm/unittests/ABI/TargetInfoTest.cpp b/llvm/unittests/ABI/TargetInfoTest.cpp
index 1f53776417ea2..d4e537ab55838 100644
--- a/llvm/unittests/ABI/TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/TargetInfoTest.cpp
@@ -6,13 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// Tests for target-independent helpers on the TargetInfo base class. These are
+// Tests for the shared default classification in DefaultTargetInfo. These are
 // exercised through a minimal concrete target so any new shared default lands
 // here rather than in a per-target test file.
 //
 //===----------------------------------------------------------------------===//
 
 #include "llvm/ABI/TargetInfo.h"
+#include "llvm/ABI/DefaultTargetInfo.h"
 #include "llvm/ABI/FunctionInfo.h"
 #include "llvm/ABI/Types.h"
 #include "llvm/Support/Alignment.h"
@@ -27,25 +28,22 @@ using namespace llvm;
 
 using ABIType = llvm::abi::Type;
 using llvm::abi::ArgInfo;
+using llvm::abi::DefaultTargetInfo;
 using llvm::abi::FieldInfo;
 using llvm::abi::FunctionInfo;
 using llvm::abi::RecordFlags;
 using llvm::abi::StructPacking;
-using llvm::abi::TargetInfo;
 using llvm::abi::TypeBuilder;
 
-// A minimal concrete target that re-exposes the shared, protected default
-// classifiers so they can be exercised directly, independent of any real
-// target.
-class TestTargetInfo : public TargetInfo {
+// A minimal concrete target that inherits the shared default classifiers so
+// they can be exercised directly, independent of any real target. Only
+// getABICompatInfo() is left to supply.
+class TestTargetInfo : public DefaultTargetInfo {
 public:
-  explicit TestTargetInfo(TypeBuilder &Builder) : TargetInfo(Builder) {}
-  void computeInfo(FunctionInfo &) const override {}
+  explicit TestTargetInfo(TypeBuilder &Builder) : DefaultTargetInfo(Builder) {}
   const llvm::abi::ABICompatInfo &getABICompatInfo() const override {
     return Compat;
   }
-  using TargetInfo::classifyDefaultArgumentType;
-  using TargetInfo::classifyDefaultReturnType;
 
 private:
   llvm::abi::ABICompatInfo Compat;
@@ -86,12 +84,12 @@ class TargetInfoTest : public ::testing::Test {
 
   ArgInfo classifyArg(const ABIType *Ty) {
     TestTargetInfo TI(TB);
-    return TI.classifyDefaultArgumentType(Ty);
+    return TI.classifyArgumentType(Ty);
   }
 
   ArgInfo classifyRet(const ABIType *Ty) {
     TestTargetInfo TI(TB);
-    return TI.classifyDefaultReturnType(Ty);
+    return TI.classifyReturnType(Ty);
   }
 };
 

>From 7bc7f2bd2b2eb9a44ef313f5909dc6d14aaeeadc Mon Sep 17 00:00:00 2001
From: skc7 <Krishna.Sankisa at amd.com>
Date: Tue, 22 Sep 2026 12:28:44 +0530
Subject: [PATCH 4/4] [llvm][ABI] Make the _BitInt register threshold
 target-driven

---
 llvm/include/llvm/ABI/TargetInfo.h    |  17 ++--
 llvm/lib/ABI/DefaultTargetInfo.cpp    |  10 ++-
 llvm/lib/ABI/TargetInfo.cpp           |  43 +--------
 llvm/unittests/ABI/TargetInfoTest.cpp | 125 +++++++++++++++++++++++++-
 4 files changed, 140 insertions(+), 55 deletions(-)

diff --git a/llvm/include/llvm/ABI/TargetInfo.h b/llvm/include/llvm/ABI/TargetInfo.h
index a818bd7761dcc..58ef3dcf43693 100644
--- a/llvm/include/llvm/ABI/TargetInfo.h
+++ b/llvm/include/llvm/ABI/TargetInfo.h
@@ -80,10 +80,19 @@ class TargetInfo {
   /// store a derived object and return that as an ABICompatInfo reference.
   virtual const ABICompatInfo &getABICompatInfo() const = 0;
 
+  /// Whether the target has a 128-bit integer type. Default true.
+  virtual bool hasInt128Type() const { return true; }
+
+  /// Width of `long long` in bits. Default 64.
+  virtual unsigned getLongLongWidth() const { return 64; }
+
 protected:
   LLVM_ABI RecordArgABI getRecordArgABI(const RecordType *RT) const;
   LLVM_ABI RecordArgABI getRecordArgABI(const Type *Ty) const;
   LLVM_ABI bool isPromotableInteger(const IntegerType *IT) const;
+
+  /// Bit width above which a _BitInt cannot stay in registers.
+  LLVM_ABI unsigned getBitIntRegThreshold() const;
   LLVM_ABI ArgInfo getNaturalAlignIndirect(const Type *Ty,
                                            bool ByVal = true) const;
   LLVM_ABI bool isAggregateTypeForABI(const Type *Ty) const;
@@ -95,14 +104,6 @@ class TargetInfo {
   /// Returns the scalar a single-element struct reduces to, else null.
   LLVM_ABI const Type *isSingleElementStruct(const Type *Ty) const;
 
-  /// Default argument classification for targets without special rules.
-  /// Aggregates are passed indirectly and small integers are promoted.
-  LLVM_ABI ArgInfo classifyDefaultArgumentType(const Type *Ty) const;
-
-  /// Default return classification for targets without special rules.
-  /// Void is ignored and aggregates are returned indirectly.
-  LLVM_ABI ArgInfo classifyDefaultReturnType(const Type *RetTy) 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/DefaultTargetInfo.cpp b/llvm/lib/ABI/DefaultTargetInfo.cpp
index 01380c106e6a3..4f2b5dbaaa269 100644
--- a/llvm/lib/ABI/DefaultTargetInfo.cpp
+++ b/llvm/lib/ABI/DefaultTargetInfo.cpp
@@ -24,8 +24,9 @@ ArgInfo DefaultTargetInfo::classifyArgumentType(const Type *Ty) const {
   }
 
   if (const auto *IT = dyn_cast<IntegerType>(Ty)) {
-    // A _BitInt wider than 128 bits does not fit in registers.
-    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
+    // A _BitInt too wide for the largest integer register goes indirect.
+    if (IT->isBitInt() &&
+        IT->getSizeInBits().getFixedValue() > getBitIntRegThreshold())
       return getNaturalAlignIndirect(Ty);
     if (isPromotableInteger(IT))
       return ArgInfo::getExtend(Ty);
@@ -42,8 +43,9 @@ ArgInfo DefaultTargetInfo::classifyReturnType(const Type *RetTy) const {
     return getNaturalAlignIndirect(RetTy);
 
   if (const auto *IT = dyn_cast<IntegerType>(RetTy)) {
-    // A _BitInt wider than 128 bits does not fit in registers.
-    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
+    // A _BitInt too wide for the largest integer register goes indirect.
+    if (IT->isBitInt() &&
+        IT->getSizeInBits().getFixedValue() > getBitIntRegThreshold())
       return getNaturalAlignIndirect(RetTy);
     if (isPromotableInteger(IT))
       return ArgInfo::getExtend(RetTy);
diff --git a/llvm/lib/ABI/TargetInfo.cpp b/llvm/lib/ABI/TargetInfo.cpp
index ec92ccbe2e3f0..7fcfdfcd5c6f8 100644
--- a/llvm/lib/ABI/TargetInfo.cpp
+++ b/llvm/lib/ABI/TargetInfo.cpp
@@ -41,6 +41,10 @@ bool TargetInfo::isPromotableInteger(const IntegerType *IT) const {
   return BitWidth < 32;
 }
 
+unsigned TargetInfo::getBitIntRegThreshold() const {
+  return hasInt128Type() ? 128 : getLongLongWidth();
+}
+
 ArgInfo TargetInfo::getNaturalAlignIndirect(const Type *Ty, bool ByVal) const {
   return ArgInfo::getIndirect(Ty->getAlignment(), ByVal);
 }
@@ -128,45 +132,6 @@ const Type *TargetInfo::isSingleElementStruct(const Type *Ty) const {
   return Found;
 }
 
-ArgInfo TargetInfo::classifyDefaultArgumentType(const Type *Ty) const {
-  Ty = useFirstFieldIfTransparentUnion(Ty);
-
-  if (isAggregateTypeForABI(Ty)) {
-    // A record that must stay in memory keeps ByVal, everything else indirect.
-    if (RecordArgABI RAA = getRecordArgABI(Ty))
-      return getNaturalAlignIndirect(Ty, /*ByVal=*/RAA == RAA_DirectInMemory);
-    return getNaturalAlignIndirect(Ty);
-  }
-
-  if (const auto *IT = dyn_cast<IntegerType>(Ty)) {
-    // A _BitInt wider than 128 bits does not fit in registers.
-    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
-      return getNaturalAlignIndirect(Ty);
-    if (isPromotableInteger(IT))
-      return ArgInfo::getExtend(Ty);
-  }
-
-  return ArgInfo::getDirect();
-}
-
-ArgInfo TargetInfo::classifyDefaultReturnType(const Type *RetTy) const {
-  if (RetTy->isVoid())
-    return ArgInfo::getIgnore();
-
-  if (isAggregateTypeForABI(RetTy))
-    return getNaturalAlignIndirect(RetTy);
-
-  if (const auto *IT = dyn_cast<IntegerType>(RetTy)) {
-    // A _BitInt wider than 128 bits does not fit in registers.
-    if (IT->isBitInt() && IT->getSizeInBits().getFixedValue() > 128)
-      return getNaturalAlignIndirect(RetTy);
-    if (isPromotableInteger(IT))
-      return ArgInfo::getExtend(RetTy);
-  }
-
-  return ArgInfo::getDirect();
-}
-
 bool TargetInfo::maybeCommonClassifyReturnType(FunctionInfo &FI) const {
   const abi::Type *Ty = FI.getReturnType();
 
diff --git a/llvm/unittests/ABI/TargetInfoTest.cpp b/llvm/unittests/ABI/TargetInfoTest.cpp
index d4e537ab55838..fac91dd113577 100644
--- a/llvm/unittests/ABI/TargetInfoTest.cpp
+++ b/llvm/unittests/ABI/TargetInfoTest.cpp
@@ -6,9 +6,10 @@
 //
 //===----------------------------------------------------------------------===//
 //
-// Tests for the shared default classification in DefaultTargetInfo. These are
-// exercised through a minimal concrete target so any new shared default lands
-// here rather than in a per-target test file.
+// Tests for target-independent helpers on TargetInfo and the shared default
+// classification in DefaultTargetInfo. These are exercised through a minimal
+// concrete target so any new shared default lands here rather than in a
+// per-target test file.
 //
 //===----------------------------------------------------------------------===//
 
@@ -16,6 +17,7 @@
 #include "llvm/ABI/DefaultTargetInfo.h"
 #include "llvm/ABI/FunctionInfo.h"
 #include "llvm/ABI/Types.h"
+#include "llvm/ADT/APFloat.h"
 #include "llvm/Support/Alignment.h"
 #include "llvm/Support/Allocator.h"
 #include "gtest/gtest.h"
@@ -33,6 +35,7 @@ using llvm::abi::FieldInfo;
 using llvm::abi::FunctionInfo;
 using llvm::abi::RecordFlags;
 using llvm::abi::StructPacking;
+using llvm::abi::TargetInfo;
 using llvm::abi::TypeBuilder;
 
 // A minimal concrete target that inherits the shared default classifiers so
@@ -44,27 +47,48 @@ class TestTargetInfo : public DefaultTargetInfo {
   const llvm::abi::ABICompatInfo &getABICompatInfo() const override {
     return Compat;
   }
+  using TargetInfo::isSingleElementStruct;
 
 private:
   llvm::abi::ABICompatInfo Compat;
 };
 
+// A target lacking a 128-bit integer type, so the _BitInt register threshold
+// is the width of `long long`.
+class NoInt128TargetInfo : public TestTargetInfo {
+public:
+  using TestTargetInfo::TestTargetInfo;
+  bool hasInt128Type() const override { return false; }
+};
+
 class TargetInfoTest : public ::testing::Test {
 protected:
   llvm::BumpPtrAllocator Alloc;
   TypeBuilder TB;
   const ABIType *I16;
   const ABIType *I32;
+  const ABIType *F32;
   const ABIType *Void;
+  /// An empty class: a record with no fields, one byte wide.
+  const ABIType *Empty;
   /// A _BitInt wider than 128 bits, which cannot be passed in registers.
   const ABIType *WideBitInt;
+  /// A _BitInt between `long long` (64) and 128 bits wide.
+  const ABIType *MidBitInt;
 
   TargetInfoTest()
       : TB(Alloc), 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),
+                               /*UnadjustedAlign=*/llvm::Align(1),
+                               StructPacking::Default, {}, {},
+                               RecordFlags::CanPassInRegisters)),
         WideBitInt(TB.getIntegerType(129, llvm::Align(8), /*Signed=*/true,
-                                     /*IsBitInt=*/true)) {}
+                                     /*IsBitInt=*/true)),
+        MidBitInt(TB.getIntegerType(100, llvm::Align(8), /*Signed=*/true,
+                                    /*IsBitInt=*/true)) {}
 
   /// A record with a single int field, passable in registers.
   const ABIType *recordInReg() {
@@ -91,6 +115,25 @@ class TargetInfoTest : public ::testing::Test {
     TestTargetInfo TI(TB);
     return TI.classifyReturnType(Ty);
   }
+
+  ArgInfo classifyArgNoInt128(const ABIType *Ty) {
+    NoInt128TargetInfo TI(TB);
+    return TI.classifyArgumentType(Ty);
+  }
+
+  const ABIType *recordOf(llvm::ArrayRef<FieldInfo> Fields, uint64_t SizeInBits,
+                          llvm::Align Alignment) {
+    return TB.getRecordType(Fields, llvm::TypeSize::getFixed(SizeInBits),
+                            Alignment, Alignment, StructPacking::Default, {},
+                            {}, RecordFlags::CanPassInRegisters);
+  }
+
+  /// The single-element reduction of \p Ty, or null if it is not a
+  /// single-element struct. Exercises the shared TargetInfo helper directly.
+  const ABIType *singleElement(const ABIType *Ty) {
+    TestTargetInfo TI(TB);
+    return TI.isSingleElementStruct(Ty);
+  }
 };
 
 // --- Argument classification -------------------------------------------------
@@ -125,6 +168,17 @@ TEST_F(TargetInfoTest, DefaultArgWideBitIntIsIndirect) {
   EXPECT_TRUE(classifyArg(WideBitInt).isIndirect());
 }
 
+// With a 128-bit integer type, a _BitInt no wider than 128 stays direct.
+TEST_F(TargetInfoTest, DefaultArgMidBitIntIsDirectWithInt128) {
+  EXPECT_TRUE(classifyArg(MidBitInt).isDirect());
+}
+
+// Without a 128-bit integer type the threshold drops to long long (64), so the
+// same _BitInt is passed indirectly.
+TEST_F(TargetInfoTest, DefaultArgMidBitIntIsIndirectWithoutInt128) {
+  EXPECT_TRUE(classifyArgNoInt128(MidBitInt).isIndirect());
+}
+
 // A transparent union is classified as its first field, so a union of one int
 // is passed directly rather than as an aggregate.
 TEST_F(TargetInfoTest, DefaultArgTransparentUnionUsesFirstField) {
@@ -166,4 +220,67 @@ TEST_F(TargetInfoTest, DefaultReturnWideBitIntIsIndirect) {
   EXPECT_FALSE(Info.getIndirectByVal());
 }
 
+// --- Single-element struct reduction -----------------------------------------
+
+// The shared single-element-struct reduction, used by getByteVectorType and by
+// other targets, looks through single-element wrappers to the scalar element.
+
+// A struct of one float reduces to its single float element.
+TEST_F(TargetInfoTest, SingleElementStructSingleScalarFieldReduces) {
+  const ABIType *S = recordOf({FieldInfo(F32, 0)}, 32, llvm::Align(4));
+  EXPECT_EQ(singleElement(S), F32);
+}
+
+// A single-element array is transparent, so a struct of one float[1] reduces
+// to the element type.
+TEST_F(TargetInfoTest, SingleElementStructSingleElementArrayReduces) {
+  const ABIType *Arr =
+      TB.getArrayType(F32, /*NumElements=*/1, /*SizeInBits=*/32);
+  const ABIType *S = recordOf({FieldInfo(Arr, 0)}, 32, llvm::Align(4));
+  EXPECT_EQ(singleElement(S), F32);
+}
+
+// A multi-element array is an aggregate that does not itself reduce, so a
+// struct of one short[2] is NOT a single-element struct.
+TEST_F(TargetInfoTest, SingleElementStructMultiElementArrayDoesNotReduce) {
+  const ABIType *Arr =
+      TB.getArrayType(I16, /*NumElements=*/2, /*SizeInBits=*/32);
+  const ABIType *S = recordOf({FieldInfo(Arr, 0)}, 32, llvm::Align(2));
+  EXPECT_EQ(singleElement(S), nullptr);
+}
+
+// Two data members: not a single-element struct.
+TEST_F(TargetInfoTest, SingleElementStructTwoFieldsDoNotReduce) {
+  const ABIType *S =
+      recordOf({FieldInfo(I32, 0), FieldInfo(I32, 32)}, 64, llvm::Align(4));
+  EXPECT_EQ(singleElement(S), nullptr);
+}
+
+// A single member that leaves tail padding does not cover the record, so it
+// does not reduce.
+TEST_F(TargetInfoTest, SingleElementStructTailPaddingDoesNotReduce) {
+  const ABIType *S = recordOf({FieldInfo(I32, 0)}, 64, llvm::Align(8));
+  EXPECT_EQ(singleElement(S), nullptr);
+}
+
+// An empty member supplies no data and is skipped, so a struct of an empty
+// member plus an int reduces to the int.
+TEST_F(TargetInfoTest, SingleElementStructEmptyMemberIsSkipped) {
+  const ABIType *S =
+      recordOf({FieldInfo(Empty, 0), FieldInfo(I32, 0)}, 32, llvm::Align(4));
+  EXPECT_EQ(singleElement(S), I32);
+}
+
+// A nested single-element struct reduces to the inner scalar.
+TEST_F(TargetInfoTest, SingleElementStructNestedSingleElementReduces) {
+  const ABIType *Inner = recordOf({FieldInfo(F32, 0)}, 32, llvm::Align(4));
+  const ABIType *Outer = recordOf({FieldInfo(Inner, 0)}, 32, llvm::Align(4));
+  EXPECT_EQ(singleElement(Outer), F32);
+}
+
+// A non-record type is never a single-element struct.
+TEST_F(TargetInfoTest, SingleElementStructNonRecordReturnsNull) {
+  EXPECT_EQ(singleElement(I32), nullptr);
+}
+
 } // namespace



More information about the llvm-commits mailing list