[clang] [llvm] [LLVMABI] Add CanBeFlattened to abi::ArgInfo (PR #220558)
Akshay K via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 11 08:47:38 PDT 2026
https://github.com/kumarak updated https://github.com/llvm/llvm-project/pull/220558
>From dcec03150a9f49873bcfa050c76029441c62dc91 Mon Sep 17 00:00:00 2001
From: AkshayK <iit.akshay at gmail.com>
Date: Wed, 2 Sep 2026 12:02:11 -0400
Subject: [PATCH 1/2] [LLVMABI] Add CanBeFlattened to abi::ArgInfo
Mirror clang::CodeGen::ABIArgInfo::CanBeFlattened so a classifier can
keep a Direct record coercion in one piece, as AAPCS-VFP homogeneous
aggregates, AMDGPU direct aggregates and x86 vectorcall HVAs require.
The bit defaults to true, so existing classifiers are unchanged.
Forward it in CodeGenModule::convertABIArgInfo and compare it in the
-fexperimental-abi-lowering parity check. Add FunctionInfoTest.cpp.
---
clang/lib/CodeGen/CGCall.cpp | 8 +-
llvm/include/llvm/ABI/FunctionInfo.h | 17 ++-
llvm/unittests/ABI/CMakeLists.txt | 1 +
llvm/unittests/ABI/FunctionInfoTest.cpp | 100 ++++++++++++++++++
.../gn/secondary/llvm/unittests/ABI/BUILD.gn | 1 +
5 files changed, 125 insertions(+), 2 deletions(-)
create mode 100644 llvm/unittests/ABI/FunctionInfoTest.cpp
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 1221829871b9f..13036b4cdd58c 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -976,6 +976,10 @@ void CodeGenModule::computeABIInfoUsingLib(CGFunctionInfo &FI) {
CheckSimple(Target.getDirectAlign(), Res.getDirectAlign(), "DirectAlign");
CheckSimple(Target.getDirectOffset(), Res.getDirectOffset(),
"DirectOffset");
+ // Extend falls through to here, and only Direct carries the flag.
+ if (Res.isDirect())
+ CheckSimple(Target.getCanBeFlattened(), Res.getCanBeFlattened(),
+ "CanBeFlattened");
break;
case ABIArgInfo::Indirect:
CheckSimple(Target.getIndirectByVal(), Res.getIndirectByVal(),
@@ -1023,7 +1027,9 @@ ABIArgInfo CodeGenModule::convertABIArgInfo(const llvm::abi::ArgInfo &AbiInfo,
CoercedType = AbiReverseMapper->convertType(AbiInfo.getCoerceToType());
if (!CoercedType)
CoercedType = getTypes().ConvertType(Type);
- return ABIArgInfo::getDirect(CoercedType, AbiInfo.getDirectOffset());
+ return ABIArgInfo::getDirect(CoercedType, AbiInfo.getDirectOffset(),
+ /*Padding=*/nullptr,
+ AbiInfo.getCanBeFlattened());
}
case llvm::abi::ArgInfo::Extend: {
llvm::Type *CoercedType = nullptr;
diff --git a/llvm/include/llvm/ABI/FunctionInfo.h b/llvm/include/llvm/ABI/FunctionInfo.h
index caedafcbe9d22..d51df165c5d64 100644
--- a/llvm/include/llvm/ABI/FunctionInfo.h
+++ b/llvm/include/llvm/ABI/FunctionInfo.h
@@ -71,10 +71,11 @@ class ArgInfo {
bool ZeroExt : 1;
bool IndirectByVal : 1;
bool IndirectRealign : 1;
+ bool CanBeFlattened : 1;
ArgInfo(Kind K = Direct)
: TheKind(K), SignExt(false), ZeroExt(false), IndirectByVal(false),
- IndirectRealign(false) {}
+ IndirectRealign(false), CanBeFlattened(true) {}
public:
/// \param T The type to coerce to. If null, the argument's original type is
@@ -140,6 +141,13 @@ class ArgInfo {
return *this;
}
+ /// See getCanBeFlattened.
+ ArgInfo &setCanBeFlattened(bool Flatten) {
+ assert(isDirect() && "Invalid Kind!");
+ CanBeFlattened = Flatten;
+ return *this;
+ }
+
Kind getKind() const { return TheKind; }
bool isDirect() const { return TheKind == Direct; }
bool isIndirect() const { return TheKind == Indirect; }
@@ -178,6 +186,13 @@ class ArgInfo {
return IndirectRealign;
}
+ /// Whether a Direct record coercion may be split into one wire argument
+ /// per field. Mirrors clang::CodeGen::ABIArgInfo::CanBeFlattened.
+ bool getCanBeFlattened() const {
+ assert(isDirect() && "Invalid Kind!");
+ return CanBeFlattened;
+ }
+
bool isSignExt() const {
assert(isExtend() && "Invalid Kind!");
return SignExt;
diff --git a/llvm/unittests/ABI/CMakeLists.txt b/llvm/unittests/ABI/CMakeLists.txt
index fdc665e5a2c98..c47fc33bf193a 100644
--- a/llvm/unittests/ABI/CMakeLists.txt
+++ b/llvm/unittests/ABI/CMakeLists.txt
@@ -6,6 +6,7 @@ set(LLVM_LINK_COMPONENTS
add_llvm_unittest(ABITests
AArch64TargetInfoTest.cpp
+ FunctionInfoTest.cpp
X86TargetInfoTest.cpp
TypesTest.cpp
)
diff --git a/llvm/unittests/ABI/FunctionInfoTest.cpp b/llvm/unittests/ABI/FunctionInfoTest.cpp
new file mode 100644
index 0000000000000..e68f5020fb3f7
--- /dev/null
+++ b/llvm/unittests/ABI/FunctionInfoTest.cpp
@@ -0,0 +1,100 @@
+//===- FunctionInfoTest.cpp - ArgInfo and FunctionInfo 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/Types.h"
+#include "llvm/IR/CallingConv.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Allocator.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+using ABIType = llvm::abi::Type;
+using llvm::abi::ArgEntry;
+using llvm::abi::ArgInfo;
+using llvm::abi::FieldInfo;
+using llvm::abi::FunctionInfo;
+using llvm::abi::StructPacking;
+using llvm::abi::TypeBuilder;
+
+class FunctionInfoTest : public ::testing::Test {
+protected:
+ llvm::BumpPtrAllocator Alloc;
+ TypeBuilder TB;
+ const ABIType *I32;
+ const ABIType *I64;
+ /// A two-i64 record: the shape a classifier coerces a 16-byte struct to when
+ /// it lands in two registers, and so the shape a rewriter may flatten.
+ const ABIType *TwoI64;
+
+ FunctionInfoTest()
+ : TB(Alloc), I32(TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true)),
+ I64(TB.getIntegerType(64, llvm::Align(8), /*Signed=*/true)),
+ TwoI64(TB.getRecordType({FieldInfo(I64, 0), FieldInfo(I64, 64)},
+ llvm::TypeSize::getFixed(128), llvm::Align(8),
+ StructPacking::Default)) {}
+};
+
+TEST_F(FunctionInfoTest, DirectCanBeFlattenedByDefault) {
+ EXPECT_TRUE(ArgInfo::getDirect().getCanBeFlattened());
+ EXPECT_TRUE(ArgInfo::getDirect(TwoI64).getCanBeFlattened());
+ EXPECT_TRUE(ArgInfo::getDirect(I64, /*Offset=*/8).getCanBeFlattened());
+}
+
+TEST_F(FunctionInfoTest, SetCanBeFlattenedRoundTrips) {
+ ArgInfo Info = ArgInfo::getDirect(TwoI64);
+ EXPECT_EQ(&Info.setCanBeFlattened(false), &Info);
+ EXPECT_FALSE(Info.getCanBeFlattened());
+ // Clearing the flag leaves the rest of the classification alone.
+ EXPECT_TRUE(Info.isDirect());
+ EXPECT_EQ(Info.getCoerceToType(), TwoI64);
+ EXPECT_EQ(Info.getDirectOffset(), 0u);
+
+ Info.setCanBeFlattened(true);
+ EXPECT_TRUE(Info.getCanBeFlattened());
+}
+
+TEST_F(FunctionInfoTest, SetCanBeFlattenedChainsOffGetDirect) {
+ // The spelling a classifier uses to keep an aggregate in one piece.
+ ArgInfo Info = ArgInfo::getDirect(TwoI64).setCanBeFlattened(false);
+ EXPECT_TRUE(Info.isDirect());
+ EXPECT_FALSE(Info.getCanBeFlattened());
+}
+
+TEST_F(FunctionInfoTest, CanBeFlattenedSurvivesFunctionInfo) {
+ std::unique_ptr<FunctionInfo> FI =
+ FunctionInfo::create(llvm::CallingConv::C, TwoI64, {TwoI64, I32});
+ FI->getReturnInfo() = ArgInfo::getDirect(TwoI64).setCanBeFlattened(false);
+ FI->getArgInfo(0).Info = ArgInfo::getDirect(TwoI64).setCanBeFlattened(false);
+ FI->getArgInfo(1).Info = ArgInfo::getDirect(I32);
+
+ const FunctionInfo &ConstFI = *FI;
+ EXPECT_FALSE(ConstFI.getReturnInfo().getCanBeFlattened());
+ EXPECT_FALSE(ConstFI.arguments()[0].Info.getCanBeFlattened());
+ EXPECT_TRUE(ConstFI.arguments()[1].Info.getCanBeFlattened());
+
+ // The flag rides along with the rest of the classification on copy.
+ ArgEntry Copy = ConstFI.getArgInfo(0);
+ EXPECT_FALSE(Copy.Info.getCanBeFlattened());
+}
+
+#if GTEST_HAS_DEATH_TEST && !defined(NDEBUG)
+TEST_F(FunctionInfoTest, CanBeFlattenedIsDirectOnly) {
+ EXPECT_DEATH((void)ArgInfo::getIgnore().getCanBeFlattened(), "Invalid Kind");
+ EXPECT_DEATH((void)ArgInfo::getExtend(I32).getCanBeFlattened(),
+ "Invalid Kind");
+ EXPECT_DEATH((void)ArgInfo::getIndirect(llvm::Align(8), /*ByVal=*/true)
+ .getCanBeFlattened(),
+ "Invalid Kind");
+ EXPECT_DEATH((void)ArgInfo::getIgnore().setCanBeFlattened(false),
+ "Invalid Kind");
+}
+#endif
+
+} // namespace
diff --git a/llvm/utils/gn/secondary/llvm/unittests/ABI/BUILD.gn b/llvm/utils/gn/secondary/llvm/unittests/ABI/BUILD.gn
index 9945330f902a5..6ea7bea7d5cfd 100644
--- a/llvm/utils/gn/secondary/llvm/unittests/ABI/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/unittests/ABI/BUILD.gn
@@ -8,6 +8,7 @@ unittest("ABITests") {
]
sources = [
"AArch64TargetInfoTest.cpp",
+ "FunctionInfoTest.cpp",
"X86TargetInfoTest.cpp",
"TypesTest.cpp",
]
>From 9f1f78cc499007e3ac8d32f069be904356a1f0d7 Mon Sep 17 00:00:00 2001
From: AkshayK <iit.akshay at gmail.com>
Date: Wed, 9 Sep 2026 00:37:02 -0400
Subject: [PATCH 2/2] [LLVMABI] Set CanBeFlattened through getDirect
Start the bit false in the constructor like the other flags and add a
trailing CanBeFlattened parameter to getDirect, matching classic
ABIArgInfo. Keep the chainable setter for classifiers that clear the
flag after the fact.
---
llvm/include/llvm/ABI/FunctionInfo.h | 8 ++++++--
llvm/unittests/ABI/FunctionInfoTest.cpp | 16 +++++++++++++---
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/llvm/include/llvm/ABI/FunctionInfo.h b/llvm/include/llvm/ABI/FunctionInfo.h
index d51df165c5d64..d2e6c619a0434 100644
--- a/llvm/include/llvm/ABI/FunctionInfo.h
+++ b/llvm/include/llvm/ABI/FunctionInfo.h
@@ -75,7 +75,7 @@ class ArgInfo {
ArgInfo(Kind K = Direct)
: TheKind(K), SignExt(false), ZeroExt(false), IndirectByVal(false),
- IndirectRealign(false), CanBeFlattened(true) {}
+ IndirectRealign(false), CanBeFlattened(false) {}
public:
/// \param T The type to coerce to. If null, the argument's original type is
@@ -86,12 +86,16 @@ class ArgInfo {
/// return value on x86-64).
/// \param Align Override for the argument's alignment. If absent, the
/// default alignment for \p T is used.
+ /// \param CanBeFlattened Whether a record coercion may be split into one
+ /// wire argument per field. See getCanBeFlattened.
static ArgInfo getDirect(const Type *T = nullptr, unsigned Offset = 0,
- MaybeAlign Align = std::nullopt) {
+ MaybeAlign Align = std::nullopt,
+ bool CanBeFlattened = true) {
ArgInfo AI(Direct);
AI.CoercionType = T;
AI.Alignment = Align;
AI.DirectAttr.Offset = Offset;
+ AI.CanBeFlattened = CanBeFlattened;
return AI;
}
diff --git a/llvm/unittests/ABI/FunctionInfoTest.cpp b/llvm/unittests/ABI/FunctionInfoTest.cpp
index e68f5020fb3f7..04fafc1108f83 100644
--- a/llvm/unittests/ABI/FunctionInfoTest.cpp
+++ b/llvm/unittests/ABI/FunctionInfoTest.cpp
@@ -12,6 +12,7 @@
#include "llvm/Support/Alignment.h"
#include "llvm/Support/Allocator.h"
#include "gtest/gtest.h"
+#include <optional>
namespace {
@@ -60,17 +61,26 @@ TEST_F(FunctionInfoTest, SetCanBeFlattenedRoundTrips) {
EXPECT_TRUE(Info.getCanBeFlattened());
}
-TEST_F(FunctionInfoTest, SetCanBeFlattenedChainsOffGetDirect) {
+TEST_F(FunctionInfoTest, GetDirectTakesCanBeFlattened) {
// The spelling a classifier uses to keep an aggregate in one piece.
- ArgInfo Info = ArgInfo::getDirect(TwoI64).setCanBeFlattened(false);
+ ArgInfo Info = ArgInfo::getDirect(TwoI64, /*Offset=*/0, std::nullopt,
+ /*CanBeFlattened=*/false);
EXPECT_TRUE(Info.isDirect());
+ EXPECT_EQ(Info.getCoerceToType(), TwoI64);
+ EXPECT_EQ(Info.getDirectOffset(), 0u);
EXPECT_FALSE(Info.getCanBeFlattened());
+
+ EXPECT_TRUE(ArgInfo::getDirect(TwoI64, /*Offset=*/0, std::nullopt,
+ /*CanBeFlattened=*/true)
+ .getCanBeFlattened());
}
TEST_F(FunctionInfoTest, CanBeFlattenedSurvivesFunctionInfo) {
std::unique_ptr<FunctionInfo> FI =
FunctionInfo::create(llvm::CallingConv::C, TwoI64, {TwoI64, I32});
- FI->getReturnInfo() = ArgInfo::getDirect(TwoI64).setCanBeFlattened(false);
+ // Both spellings that clear the flag land in the same place.
+ FI->getReturnInfo() = ArgInfo::getDirect(TwoI64, /*Offset=*/0, std::nullopt,
+ /*CanBeFlattened=*/false);
FI->getArgInfo(0).Info = ArgInfo::getDirect(TwoI64).setCanBeFlattened(false);
FI->getArgInfo(1).Info = ArgInfo::getDirect(I32);
More information about the cfe-commits
mailing list