[clang] [llvm] [LLVMABI] Add support for SVE types in the LLVM ABI library (PR #221375)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 10 09:53:41 PDT 2026
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/221375
>From a1c7cf7526225cc09ee40a62f95cc1b6f8c2e65a Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Fri, 4 Sep 2026 17:51:00 -0700
Subject: [PATCH 1/5] [LLVMABI] Add support for SVE types in the LLVM ABI
library
This change extends the LLVM ABI library's VectorType to be able to
describe SVE types and updates Clang's QualTypeMapper to map them.
The AArch64 target info class in the ABI library is still a work in
progress. It will continue to report "not yet implemented" for function
signatures involving SVE types. This change is a neceasary prerequisite
for correctly handling them or correctly deferring handling of these
specific types.
Assisted-by: Cursor / claude-opus-5
---
clang/lib/CodeGen/QualTypeMapper.cpp | 78 +++++-
clang/lib/CodeGen/QualTypeMapper.h | 1 +
clang/unittests/CodeGen/CMakeLists.txt | 3 +
.../unittests/CodeGen/QualTypeMapperTest.cpp | 258 ++++++++++++++++++
llvm/include/llvm/ABI/Types.h | 80 +++++-
llvm/lib/ABI/IRTypeMapper.cpp | 11 +-
llvm/unittests/ABI/TypesTest.cpp | 79 ++++++
.../clang/unittests/CodeGen/BUILD.gn | 3 +
8 files changed, 499 insertions(+), 14 deletions(-)
create mode 100644 clang/unittests/CodeGen/QualTypeMapperTest.cpp
diff --git a/clang/lib/CodeGen/QualTypeMapper.cpp b/clang/lib/CodeGen/QualTypeMapper.cpp
index c095b9394f8e5..fadb0d41a1756 100644
--- a/clang/lib/CodeGen/QualTypeMapper.cpp
+++ b/clang/lib/CodeGen/QualTypeMapper.cpp
@@ -34,6 +34,31 @@
namespace clang {
namespace CodeGen {
+/// Returns true if \p BT is one of the AArch64 SVE predicate types, i.e.
+/// svbool_t or one of its tuples.
+static bool isSVEPredicateBuiltinType(const BuiltinType *BT) {
+ switch (BT->getKind()) {
+#define SVE_PREDICATE_TYPE(Name, MangledName, Id, SingletonId) \
+ case BuiltinType::Id: \
+ return true;
+#include "clang/Basic/AArch64ACLETypes.def"
+ default:
+ return false;
+ }
+}
+
+/// Maps a Clang vector kind onto the ABI library's notion of a vector flavor.
+static llvm::abi::VectorKind getABIVectorKind(clang::VectorKind Kind) {
+ switch (Kind) {
+ case clang::VectorKind::SveFixedLengthData:
+ return llvm::abi::VectorKind::SVEData;
+ case clang::VectorKind::SveFixedLengthPredicate:
+ return llvm::abi::VectorKind::SVEPredicate;
+ default:
+ return llvm::abi::VectorKind::Generic;
+ }
+}
+
/// Main entry point for converting Clang QualType to LLVM ABI Type.
/// This method performs type canonicalization, caching, and dispatches
/// to specialized conversion methods based on the type kind.
@@ -242,11 +267,27 @@ QualTypeMapper::convertBuiltinType(const BuiltinType *BT) {
case BuiltinType::ObjCSel:
return createPointerTypeForPointee(QT);
- // Target-specific vector/matrix types — not yet implemented.
-#define SVE_TYPE(Name, Id, SingletonId) case BuiltinType::Id:
+ // AArch64 SVE data and predicate types, including the x2/x3/x4 tuples.
+#define SVE_VECTOR_TYPE(Name, MangledName, Id, SingletonId) \
+ case BuiltinType::Id:
+#define SVE_PREDICATE_TYPE(Name, MangledName, Id, SingletonId) \
+ case BuiltinType::Id:
#include "clang/Basic/AArch64ACLETypes.def"
+ return convertSVEBuiltinType(BT);
+
+ case BuiltinType::SveCount:
+ return Builder.getSVECountType(getTypeAlign(QT));
+
+ // TODO: __mfp8 has no floating-point semantics of its own, so representing
+ // it needs a decision about how the ABI library should model opaque
+ // floating-point data. As an mfloat8 vector element it is treated as an
+ // 8-bit integer, but that is not right for the scalar type, which is passed
+ // in a floating-point register.
+ case BuiltinType::MFloat8:
llvm::reportFatalInternalError(
- "AArch64 SVE types not yet supported in ABI lowering library");
+ "__mfp8 is not yet supported in the ABI lowering library");
+
+ // Target-specific vector/matrix types — not yet implemented.
#define PPC_VECTOR_TYPE(Name, Id, Size) case BuiltinType::Id:
#include "clang/Basic/PPCTypes.def"
llvm::reportFatalInternalError(
@@ -318,7 +359,36 @@ const llvm::abi::Type *QualTypeMapper::convertVectorType(const VectorType *VT) {
llvm::ElementCount NumElements = llvm::ElementCount::getFixed(NElems);
llvm::Align VectorAlign = getTypeAlign(VectorQualType);
- return Builder.getVectorType(ElementType, NumElements, VectorAlign);
+ return Builder.getVectorType(ElementType, NumElements, VectorAlign,
+ getABIVectorKind(VT->getVectorKind()));
+}
+
+/// Converts the sizeless AArch64 SVE data and predicate builtin types,
+/// including the x2/x3/x4 tuples, to scalable LLVM ABI vector types.
+///
+/// \param BT The SVE BuiltinType to convert
+/// \return LLVM ABI VectorType with a scalable element count
+const llvm::abi::Type *
+QualTypeMapper::convertSVEBuiltinType(const BuiltinType *BT) {
+ ASTContext::BuiltinVectorTypeInfo Info = ASTCtx.getBuiltinVectorTypeInfo(BT);
+ assert(Info.NumVectors > 0 && Info.NumVectors <= 4 &&
+ "Expected 1, 2, 3 or 4 vectors!");
+
+ // __mfp8 carries no floating-point semantics, so mfloat8 vectors use an
+ // 8-bit integer element type, which is also how they are represented in
+ // LLVM IR.
+ const llvm::abi::Type *ElementType =
+ Info.ElementType->isMFloat8Type()
+ ? Builder.getIntegerType(8, llvm::Align(1), /*Signed=*/false)
+ : convertType(Info.ElementType);
+
+ llvm::abi::VectorKind VecKind = isSVEPredicateBuiltinType(BT)
+ ? llvm::abi::VectorKind::SVEPredicate
+ : llvm::abi::VectorKind::SVEData;
+
+ return Builder.getVectorType(ElementType, Info.EC,
+ getTypeAlign(QualType(BT, 0)), VecKind,
+ Info.NumVectors);
}
/// Converts complex types to LLVM ABI complex representations.
diff --git a/clang/lib/CodeGen/QualTypeMapper.h b/clang/lib/CodeGen/QualTypeMapper.h
index 35876f44f3aba..f6bb43482c705 100644
--- a/clang/lib/CodeGen/QualTypeMapper.h
+++ b/clang/lib/CodeGen/QualTypeMapper.h
@@ -41,6 +41,7 @@ class QualTypeMapper {
const llvm::abi::Type *convertBuiltinType(const clang::BuiltinType *BT);
const llvm::abi::Type *convertArrayType(const clang::ArrayType *AT);
const llvm::abi::Type *convertVectorType(const clang::VectorType *VT);
+ const llvm::abi::Type *convertSVEBuiltinType(const clang::BuiltinType *BT);
const llvm::abi::Type *convertRecordType(const clang::RecordType *RT);
const llvm::abi::Type *convertEnumType(const clang::EnumType *ET);
const llvm::abi::Type *convertComplexType(const ComplexType *CT);
diff --git a/clang/unittests/CodeGen/CMakeLists.txt b/clang/unittests/CodeGen/CMakeLists.txt
index d4efb2230a054..7e2782e09117c 100644
--- a/clang/unittests/CodeGen/CMakeLists.txt
+++ b/clang/unittests/CodeGen/CMakeLists.txt
@@ -4,6 +4,7 @@ add_clang_unittest(ClangCodeGenTests
DemangleTrapReasonInDebugInfo.cpp
TBAAMetadataTest.cpp
CheckTargetFeaturesTest.cpp
+ QualTypeMapperTest.cpp
CLANG_LIBS
clangAST
clangBasic
@@ -12,7 +13,9 @@ add_clang_unittest(ClangCodeGenTests
clangLex
clangParse
clangSerialization
+ clangTesting
LLVM_COMPONENTS
+ ABI
Core
Support
TargetParser
diff --git a/clang/unittests/CodeGen/QualTypeMapperTest.cpp b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
new file mode 100644
index 0000000000000..4e354ab641e05
--- /dev/null
+++ b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
@@ -0,0 +1,258 @@
+//===- QualTypeMapperTest.cpp - Tests for QualType to ABI type mapping ----===//
+//
+// 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
+/// Tests that QualTypeMapper maps the AArch64 SVE types onto the expected
+/// LLVM ABI type representations.
+///
+//===----------------------------------------------------------------------===//
+
+#include "../../lib/CodeGen/QualTypeMapper.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/Type.h"
+#include "clang/Testing/CommandLineArgs.h"
+#include "clang/Testing/TestAST.h"
+#include "llvm/ABI/Types.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/TypeSize.h"
+#include "gtest/gtest.h"
+
+using namespace clang;
+
+namespace {
+
+/// Parses a translation unit for an AArch64 target, which makes the SVE
+/// builtin types available on the ASTContext, and exposes a QualTypeMapper
+/// over the resulting ASTContext.
+class QualTypeMapperSVETest : public ::testing::Test {
+protected:
+ QualTypeMapperSVETest()
+ : AST(makeInputs()), Mapper(AST.context(), DL, Alloc) {}
+
+ ASTContext &context() { return AST.context(); }
+
+ /// Maps \p QT and returns it as an ABI vector type, or null if it did not
+ /// map to a vector.
+ const llvm::abi::VectorType *mapToVector(QualType QT) {
+ return dyn_cast<llvm::abi::VectorType>(Mapper.convertType(QT));
+ }
+
+ /// Returns the underlying type of the file-scope typedef named \p Name.
+ QualType lookupTypedef(StringRef Name) {
+ for (Decl *D : context().getTranslationUnitDecl()->decls())
+ if (const auto *TD = dyn_cast<TypedefNameDecl>(D))
+ if (TD->getName() == Name)
+ return TD->getUnderlyingType();
+ ADD_FAILURE() << "no typedef named " << Name;
+ return QualType();
+ }
+
+ /// Checks an SVE data vector of \p NF vectors of \p NumEls elements, each
+ /// \p ElBits wide. \p IsFP selects whether the element is expected to be a
+ /// floating-point or an integer type.
+ void checkDataVector(StringRef Name, QualType QT, unsigned NumEls,
+ unsigned ElBits, unsigned NF, bool IsFP) {
+ SCOPED_TRACE(Name);
+ const llvm::abi::VectorType *VT = mapToVector(QT);
+ ASSERT_NE(VT, nullptr) << "did not map to a vector type";
+
+ EXPECT_EQ(VT->getVectorKind(), llvm::abi::VectorKind::SVEData);
+ EXPECT_TRUE(VT->isScalable());
+ EXPECT_EQ(VT->getNumElements(), llvm::ElementCount::getScalable(NumEls));
+ EXPECT_EQ(VT->getNumVectors(), NF);
+ EXPECT_EQ(VT->isTuple(), NF > 1);
+ EXPECT_EQ(VT->getSizeInBits(),
+ llvm::TypeSize::getScalable(NumEls * ElBits * NF));
+ // AAPCS64 aligns every SVE data vector, including the tuples, to 16 bytes.
+ EXPECT_EQ(VT->getAlignment(), llvm::Align(16));
+
+ const llvm::abi::Type *Elt = VT->getElementType();
+ EXPECT_EQ(Elt->getSizeInBits(), llvm::TypeSize::getFixed(ElBits));
+ EXPECT_EQ(Elt->isFloat(), IsFP);
+ EXPECT_EQ(Elt->isInteger(), !IsFP);
+ }
+
+ /// Checks an SVE predicate vector of \p NF vectors of \p NumEls elements.
+ void checkPredicateVector(StringRef Name, QualType QT, unsigned NumEls,
+ unsigned NF) {
+ SCOPED_TRACE(Name);
+ const llvm::abi::VectorType *VT = mapToVector(QT);
+ ASSERT_NE(VT, nullptr) << "did not map to a vector type";
+
+ EXPECT_EQ(VT->getVectorKind(), llvm::abi::VectorKind::SVEPredicate);
+ EXPECT_TRUE(VT->isScalable());
+ EXPECT_EQ(VT->getNumElements(), llvm::ElementCount::getScalable(NumEls));
+ EXPECT_EQ(VT->getNumVectors(), NF);
+ EXPECT_EQ(VT->getSizeInBits(), llvm::TypeSize::getScalable(NumEls * NF));
+ EXPECT_EQ(VT->getAlignment(), llvm::Align(2));
+
+ const llvm::abi::Type *Elt = VT->getElementType();
+ ASSERT_TRUE(Elt->isInteger());
+ EXPECT_EQ(Elt->getSizeInBits(), llvm::TypeSize::getFixed(1));
+ }
+
+private:
+ static TestInputs makeInputs() {
+ TestInputs Inputs(R"c(
+typedef __SVInt32_t fixed_int32_t __attribute__((arm_sve_vector_bits(256)));
+typedef __SVBool_t fixed_bool_t __attribute__((arm_sve_vector_bits(256)));
+typedef int generic_int32x4_t __attribute__((vector_size(16)));
+)c");
+ Inputs.Language = TestLanguage::Lang_C99;
+ // The fixed-length SVE typedefs require a known vector length, which must
+ // agree with the width in the attribute.
+ Inputs.ExtraArgs = {"-triple", "aarch64-unknown-linux-gnu",
+ "-target-feature", "+sve",
+ "-mvscale-min=2", "-mvscale-max=2"};
+ return Inputs;
+ }
+
+ TestAST AST;
+ llvm::DataLayout DL;
+ llvm::BumpPtrAllocator Alloc;
+ CodeGen::QualTypeMapper Mapper;
+};
+
+// Every SVE data vector, including the x2/x3/x4 tuples, maps to a scalable
+// vector tagged as SVE data.
+TEST_F(QualTypeMapperSVETest, DataVectors) {
+#define SVE_VECTOR_TYPE_INT(Name, MangledName, Id, SingletonId, NumEls, \
+ ElBits, NF, IsSigned) \
+ checkDataVector(#Name, context().SingletonId, NumEls, ElBits, NF, \
+ /*IsFP=*/false);
+#define SVE_VECTOR_TYPE_FLOAT(Name, MangledName, Id, SingletonId, NumEls, \
+ ElBits, NF) \
+ checkDataVector(#Name, context().SingletonId, NumEls, ElBits, NF, \
+ /*IsFP=*/true);
+#define SVE_VECTOR_TYPE_BFLOAT(Name, MangledName, Id, SingletonId, NumEls, \
+ ElBits, NF) \
+ checkDataVector(#Name, context().SingletonId, NumEls, ElBits, NF, \
+ /*IsFP=*/true);
+ // mfloat8 vectors use an integer element, since __mfp8 has no
+ // floating-point semantics of its own.
+#define SVE_VECTOR_TYPE_MFLOAT(Name, MangledName, Id, SingletonId, NumEls, \
+ ElBits, NF) \
+ checkDataVector(#Name, context().SingletonId, NumEls, ElBits, NF, \
+ /*IsFP=*/false);
+#include "clang/Basic/AArch64ACLETypes.def"
+}
+
+// Every SVE predicate, including the x2/x4 tuples, maps to a scalable vector
+// of one-bit elements tagged as an SVE predicate.
+TEST_F(QualTypeMapperSVETest, PredicateVectors) {
+#define SVE_PREDICATE_TYPE_ALL(Name, MangledName, Id, SingletonId, NumEls, NF) \
+ checkPredicateVector(#Name, context().SingletonId, NumEls, NF);
+#include "clang/Basic/AArch64ACLETypes.def"
+}
+
+// Spot-check a few representative types against their IR spellings, so that
+// the macro-driven tests above are anchored to concrete expectations.
+TEST_F(QualTypeMapperSVETest, RepresentativeTypes) {
+ // svint8_t is <vscale x 16 x i8>.
+ const llvm::abi::VectorType *SVInt8 = mapToVector(context().SveInt8Ty);
+ ASSERT_NE(SVInt8, nullptr);
+ EXPECT_EQ(SVInt8->getNumElements(), llvm::ElementCount::getScalable(16));
+ EXPECT_EQ(SVInt8->getSizeInBits(), llvm::TypeSize::getScalable(128));
+ EXPECT_TRUE(
+ cast<llvm::abi::IntegerType>(SVInt8->getElementType())->isSigned());
+
+ // svuint8_t has the same shape but an unsigned element.
+ const llvm::abi::VectorType *SVUint8 = mapToVector(context().SveUint8Ty);
+ ASSERT_NE(SVUint8, nullptr);
+ EXPECT_FALSE(
+ cast<llvm::abi::IntegerType>(SVUint8->getElementType())->isSigned());
+
+ // svfloat64x2_t is two <vscale x 2 x double> vectors.
+ const llvm::abi::VectorType *SVFloat64x2 =
+ mapToVector(context().SveFloat64x2Ty);
+ ASSERT_NE(SVFloat64x2, nullptr);
+ EXPECT_EQ(SVFloat64x2->getNumVectors(), 2u);
+ EXPECT_EQ(SVFloat64x2->getNumElements(), llvm::ElementCount::getScalable(2));
+ EXPECT_EQ(SVFloat64x2->getSizeInBits(), llvm::TypeSize::getScalable(256));
+
+ // svbool_t is <vscale x 16 x i1>.
+ const llvm::abi::VectorType *SVBool = mapToVector(context().SveBoolTy);
+ ASSERT_NE(SVBool, nullptr);
+ EXPECT_EQ(SVBool->getNumElements(), llvm::ElementCount::getScalable(16));
+ EXPECT_EQ(SVBool->getSizeInBits(), llvm::TypeSize::getScalable(16));
+}
+
+// __mfp8 has no floating-point semantics, so mfloat8 vectors use an 8-bit
+// integer element. That makes svmfloat8_t share a representation with
+// svuint8_t, which is also how the two are represented in LLVM IR.
+TEST_F(QualTypeMapperSVETest, MFloat8VectorUsesIntegerElement) {
+ const llvm::abi::VectorType *SVMFloat8 = mapToVector(context().SveMFloat8Ty);
+ ASSERT_NE(SVMFloat8, nullptr);
+
+ EXPECT_EQ(SVMFloat8->getVectorKind(), llvm::abi::VectorKind::SVEData);
+ EXPECT_EQ(SVMFloat8->getNumElements(), llvm::ElementCount::getScalable(16));
+
+ const auto *Elt =
+ dyn_cast<llvm::abi::IntegerType>(SVMFloat8->getElementType());
+ ASSERT_NE(Elt, nullptr);
+ EXPECT_EQ(Elt->getSizeInBits(), llvm::TypeSize::getFixed(8));
+ EXPECT_FALSE(Elt->isSigned());
+}
+
+// __SVCount_t is opaque, and is given the shape of svbool_t because it
+// occupies a predicate register.
+TEST_F(QualTypeMapperSVETest, SVECount) {
+ const llvm::abi::VectorType *SVCount = mapToVector(context().SveCountTy);
+ ASSERT_NE(SVCount, nullptr);
+
+ EXPECT_EQ(SVCount->getVectorKind(), llvm::abi::VectorKind::SVECount);
+ EXPECT_TRUE(SVCount->isSVEType());
+ EXPECT_FALSE(SVCount->isSVEPredicate());
+ EXPECT_TRUE(SVCount->isScalable());
+ EXPECT_FALSE(SVCount->isTuple());
+ EXPECT_EQ(SVCount->getNumElements(), llvm::ElementCount::getScalable(16));
+ EXPECT_EQ(SVCount->getSizeInBits(), llvm::TypeSize::getScalable(16));
+ EXPECT_EQ(SVCount->getAlignment(), llvm::Align(2));
+}
+
+// The arm_sve_vector_bits types are fixed-length, but they still have to be
+// distinguishable from an ordinary vector of the same shape.
+TEST_F(QualTypeMapperSVETest, FixedLengthSVEVectors) {
+ const llvm::abi::VectorType *FixedInt32 =
+ mapToVector(lookupTypedef("fixed_int32_t"));
+ ASSERT_NE(FixedInt32, nullptr);
+ EXPECT_EQ(FixedInt32->getVectorKind(), llvm::abi::VectorKind::SVEData);
+ EXPECT_FALSE(FixedInt32->isScalable());
+ EXPECT_FALSE(FixedInt32->isTuple());
+ EXPECT_EQ(FixedInt32->getNumElements(), llvm::ElementCount::getFixed(8));
+ EXPECT_EQ(FixedInt32->getSizeInBits(), llvm::TypeSize::getFixed(256));
+
+ // Clang derives the element count of a fixed-length predicate by dividing
+ // the vector length in bits by the square of the char width, so a 256-bit
+ // vector length gives 4 elements.
+ const llvm::abi::VectorType *FixedBool =
+ mapToVector(lookupTypedef("fixed_bool_t"));
+ ASSERT_NE(FixedBool, nullptr);
+ EXPECT_EQ(FixedBool->getVectorKind(), llvm::abi::VectorKind::SVEPredicate);
+ EXPECT_FALSE(FixedBool->isScalable());
+ EXPECT_EQ(FixedBool->getNumElements(), llvm::ElementCount::getFixed(4));
+}
+
+// An ordinary vector must not be mistaken for an SVE type.
+TEST_F(QualTypeMapperSVETest, PlainVectorIsGeneric) {
+ const llvm::abi::VectorType *Int32x4 =
+ mapToVector(lookupTypedef("generic_int32x4_t"));
+ ASSERT_NE(Int32x4, nullptr);
+
+ EXPECT_EQ(Int32x4->getVectorKind(), llvm::abi::VectorKind::Generic);
+ EXPECT_FALSE(Int32x4->isSVEType());
+ EXPECT_FALSE(Int32x4->isScalable());
+ EXPECT_EQ(Int32x4->getNumElements(), llvm::ElementCount::getFixed(4));
+ EXPECT_EQ(Int32x4->getSizeInBits(), llvm::TypeSize::getFixed(128));
+}
+
+} // namespace
diff --git a/llvm/include/llvm/ABI/Types.h b/llvm/include/llvm/ABI/Types.h
index 88b99e9e095d3..d04bd765ab5e6 100644
--- a/llvm/include/llvm/ABI/Types.h
+++ b/llvm/include/llvm/ABI/Types.h
@@ -81,7 +81,7 @@ class Type {
bool isRecord() const { return Kind == TypeKind::Record; }
bool isMemberPointer() const { return Kind == TypeKind::MemberPointer; }
bool isComplex() const { return Kind == TypeKind::Complex; }
- bool isZeroSize() const { return getSizeInBits().getFixedValue() == 0; }
+ bool isZeroSize() const { return getSizeInBits().isZero(); }
};
class VoidType : public Type {
@@ -209,23 +209,73 @@ class ArrayType : public Type {
static bool classof(const Type *T) { return T->getKind() == TypeKind::Array; }
};
+/// Distinguishes the vector flavors that ABIs have to treat differently.
+/// Scalability is not part of the kind. It is tracked by the vector's
+/// ElementCount, because some flavors have both a scalable and a
+/// fixed-length spelling.
+enum class VectorKind {
+ /// A plain vector, such as a Neon vector or a GCC vector_size vector.
+ Generic,
+
+ /// An AArch64 SVE data vector, such as svint32_t or svfloat64x2_t. Data
+ /// vectors are passed in Z registers.
+ SVEData,
+
+ /// An AArch64 SVE predicate vector, such as svbool_t or svboolx4_t.
+ /// Predicate vectors have one-bit elements and are passed in P registers.
+ SVEPredicate,
+
+ /// The AArch64 __SVCount_t type. It is opaque rather than a real vector,
+ /// but it occupies a predicate register, so it is given the same shape as
+ /// svbool_t.
+ SVECount,
+};
+
class VectorType : public Type {
private:
const Type *ElementType;
ElementCount NumElements;
+ VectorKind VecKind;
+ unsigned NumVectors;
+
+ static TypeSize computeSizeInBits(const Type *ElementType,
+ ElementCount NumElements,
+ unsigned NumVectors) {
+ return TypeSize(ElementType->getSizeInBits().getFixedValue() *
+ NumElements.getKnownMinValue() * NumVectors,
+ NumElements.isScalable());
+ }
public:
- VectorType(const Type *ElementType, ElementCount NumElements, Align ABIAlign)
+ VectorType(const Type *ElementType, ElementCount NumElements, Align ABIAlign,
+ VectorKind VecKind = VectorKind::Generic, unsigned NumVectors = 1)
: Type(TypeKind::Vector,
- TypeSize(ElementType->getSizeInBits().getFixedValue() *
- NumElements.getKnownMinValue(),
- NumElements.isScalable()),
- ABIAlign),
- ElementType(ElementType), NumElements(NumElements) {}
+ computeSizeInBits(ElementType, NumElements, NumVectors), ABIAlign),
+ ElementType(ElementType), NumElements(NumElements), VecKind(VecKind),
+ NumVectors(NumVectors) {}
const Type *getElementType() const { return ElementType; }
+
+ /// Returns the element count of a single vector. A tuple type holds
+ /// getNumVectors() vectors of this shape.
ElementCount getNumElements() const { return NumElements; }
+ VectorKind getVectorKind() const { return VecKind; }
+
+ /// Returns the number of vectors making up an SVE tuple type, which is 1,
+ /// 2, 3, or 4. Every other vector type holds exactly one.
+ unsigned getNumVectors() const { return NumVectors; }
+ bool isTuple() const { return NumVectors > 1; }
+
+ bool isScalable() const { return NumElements.isScalable(); }
+
+ bool isSVEData() const { return VecKind == VectorKind::SVEData; }
+ bool isSVEPredicate() const { return VecKind == VectorKind::SVEPredicate; }
+ bool isSVECount() const { return VecKind == VectorKind::SVECount; }
+
+ /// Returns true for any of the AArch64 SVE flavors.
+ bool isSVEType() const { return VecKind != VectorKind::Generic; }
+
static bool classof(const Type *T) {
return T->getKind() == TypeKind::Vector;
}
@@ -369,9 +419,21 @@ class TypeBuilder {
}
const VectorType *getVectorType(const Type *ElementType,
- ElementCount NumElements, Align Align) {
+ ElementCount NumElements, Align Align,
+ VectorKind VecKind = VectorKind::Generic,
+ unsigned NumVectors = 1) {
return new (Allocator.Allocate<VectorType>())
- VectorType(ElementType, NumElements, Align);
+ VectorType(ElementType, NumElements, Align, VecKind, NumVectors);
+ }
+
+ /// Creates the AArch64 __SVCount_t type. The type is opaque, so it is
+ /// modeled with the shape of svbool_t: a scalable vector of 16 one-bit
+ /// elements.
+ const VectorType *getSVECountType(Align ABIAlign) {
+ const Type *PredicateBit =
+ getIntegerType(1, Align(1), /*Signed=*/false, /*IsBitInt=*/false);
+ return getVectorType(PredicateBit, ElementCount::getScalable(16), ABIAlign,
+ VectorKind::SVECount);
}
const RecordType *getRecordType(ArrayRef<FieldInfo> Fields, TypeSize Size,
diff --git a/llvm/lib/ABI/IRTypeMapper.cpp b/llvm/lib/ABI/IRTypeMapper.cpp
index bcd133ae30c41..4b1185ce3e2f2 100644
--- a/llvm/lib/ABI/IRTypeMapper.cpp
+++ b/llvm/lib/ABI/IRTypeMapper.cpp
@@ -77,8 +77,17 @@ llvm::Type *IRTypeMapper::convertArrayType(const abi::ArrayType *AT) {
}
llvm::Type *IRTypeMapper::convertVectorType(const abi::VectorType *VT) {
+ if (VT->isSVECount())
+ return llvm::TargetExtType::get(Context, "aarch64.svcount");
+
llvm::Type *ElementType = convertType(VT->getElementType());
- return llvm::VectorType::get(ElementType, VT->getNumElements());
+ llvm::Type *VecTy = llvm::VectorType::get(ElementType, VT->getNumElements());
+ if (!VT->isTuple())
+ return VecTy;
+
+ // SVE tuples are a struct with one element per vector.
+ SmallVector<llvm::Type *, 4> Elements(VT->getNumVectors(), VecTy);
+ return llvm::StructType::get(Context, Elements);
}
llvm::Type *IRTypeMapper::convertRecordType(const abi::RecordType *RT) {
diff --git a/llvm/unittests/ABI/TypesTest.cpp b/llvm/unittests/ABI/TypesTest.cpp
index 85a9d368b1e25..ef5cabb98525a 100644
--- a/llvm/unittests/ABI/TypesTest.cpp
+++ b/llvm/unittests/ABI/TypesTest.cpp
@@ -14,12 +14,15 @@
#include "gtest/gtest.h"
using llvm::Align;
+using llvm::ElementCount;
using llvm::TypeSize;
using llvm::abi::FieldInfo;
using llvm::abi::RecordFlags;
using llvm::abi::RecordType;
using llvm::abi::StructPacking;
using llvm::abi::TypeBuilder;
+using llvm::abi::VectorKind;
+using llvm::abi::VectorType;
namespace {
@@ -125,4 +128,80 @@ TEST_F(ABITypesTest, DirectVirtualBasesAndVTablePointer) {
->isEmpty());
}
+TEST_F(ABITypesTest, GenericVector) {
+ const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), /*Signed=*/true);
+ const VectorType *V4I32 =
+ TB.getVectorType(I32, ElementCount::getFixed(4), Align(16));
+
+ EXPECT_EQ(V4I32->getVectorKind(), VectorKind::Generic);
+ EXPECT_FALSE(V4I32->isSVEType());
+ EXPECT_FALSE(V4I32->isScalable());
+ EXPECT_FALSE(V4I32->isTuple());
+ EXPECT_EQ(V4I32->getNumVectors(), 1u);
+ EXPECT_EQ(V4I32->getSizeInBits(), TypeSize::getFixed(128));
+}
+
+// svint32_t is <vscale x 4 x i32>.
+TEST_F(ABITypesTest, SVEDataVector) {
+ const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), /*Signed=*/true);
+ const VectorType *SVInt32 = TB.getVectorType(
+ I32, ElementCount::getScalable(4), Align(16), VectorKind::SVEData);
+
+ EXPECT_TRUE(SVInt32->isSVEData());
+ EXPECT_TRUE(SVInt32->isSVEType());
+ EXPECT_TRUE(SVInt32->isScalable());
+ EXPECT_FALSE(SVInt32->isTuple());
+ EXPECT_EQ(SVInt32->getSizeInBits(), TypeSize::getScalable(128));
+ EXPECT_EQ(SVInt32->getAlignment(), Align(16));
+}
+
+// svint32x3_t is three <vscale x 4 x i32> vectors.
+TEST_F(ABITypesTest, SVEDataVectorTuple) {
+ const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), /*Signed=*/true);
+ const VectorType *SVInt32x3 =
+ TB.getVectorType(I32, ElementCount::getScalable(4), Align(16),
+ VectorKind::SVEData, /*NumVectors=*/3);
+
+ EXPECT_TRUE(SVInt32x3->isSVEData());
+ EXPECT_TRUE(SVInt32x3->isTuple());
+ EXPECT_EQ(SVInt32x3->getNumVectors(), 3u);
+ // getNumElements() describes one vector of the tuple, while the size covers
+ // all of them.
+ EXPECT_EQ(SVInt32x3->getNumElements(), ElementCount::getScalable(4));
+ EXPECT_EQ(SVInt32x3->getSizeInBits(), TypeSize::getScalable(384));
+}
+
+// svbool_t is <vscale x 16 x i1>.
+TEST_F(ABITypesTest, SVEPredicateVector) {
+ const llvm::abi::Type *I1 = TB.getIntegerType(1, Align(1), /*Signed=*/false);
+ const VectorType *SVBool = TB.getVectorType(
+ I1, ElementCount::getScalable(16), Align(2), VectorKind::SVEPredicate);
+
+ EXPECT_TRUE(SVBool->isSVEPredicate());
+ EXPECT_FALSE(SVBool->isSVEData());
+ EXPECT_TRUE(SVBool->isScalable());
+ EXPECT_EQ(SVBool->getSizeInBits(), TypeSize::getScalable(16));
+ EXPECT_EQ(SVBool->getAlignment(), Align(2));
+}
+
+TEST_F(ABITypesTest, SVECount) {
+ const VectorType *SVCount = TB.getSVECountType(Align(2));
+
+ EXPECT_TRUE(SVCount->isSVECount());
+ EXPECT_TRUE(SVCount->isSVEType());
+ EXPECT_FALSE(SVCount->isSVEPredicate());
+ EXPECT_TRUE(SVCount->isScalable());
+ EXPECT_FALSE(SVCount->isTuple());
+ EXPECT_EQ(SVCount->getSizeInBits(), TypeSize::getScalable(16));
+}
+
+// Scalable vectors have no fixed size, so isZeroSize() must not query one.
+TEST_F(ABITypesTest, ScalableVectorIsNotZeroSized) {
+ const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), /*Signed=*/true);
+ const VectorType *SVInt32 = TB.getVectorType(
+ I32, ElementCount::getScalable(4), Align(16), VectorKind::SVEData);
+
+ EXPECT_FALSE(SVInt32->isZeroSize());
+}
+
} // namespace
diff --git a/llvm/utils/gn/secondary/clang/unittests/CodeGen/BUILD.gn b/llvm/utils/gn/secondary/clang/unittests/CodeGen/BUILD.gn
index bd8d9610c2a4a..faca44f0f5d95 100644
--- a/llvm/utils/gn/secondary/clang/unittests/CodeGen/BUILD.gn
+++ b/llvm/utils/gn/secondary/clang/unittests/CodeGen/BUILD.gn
@@ -9,6 +9,8 @@ unittest("ClangCodeGenTests") {
"//clang/lib/Frontend",
"//clang/lib/Lex",
"//clang/lib/Parse",
+ "//clang/lib/Testing",
+ "//llvm/lib/ABI",
"//llvm/lib/IR",
"//llvm/lib/Support",
"//llvm/lib/TargetParser",
@@ -18,6 +20,7 @@ unittest("ClangCodeGenTests") {
"CheckTargetFeaturesTest.cpp",
"CodeGenExternalTest.cpp",
"DemangleTrapReasonInDebugInfo.cpp",
+ "QualTypeMapperTest.cpp",
"TBAAMetadataTest.cpp",
]
}
>From 4bbd51419c863d326e0d351eb15cf263718454ac Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 9 Sep 2026 10:21:09 -0700
Subject: [PATCH 2/5] Introduce TupleType for a tuple of vectors
---
clang/lib/CodeGen/QualTypeMapper.cpp | 15 ++--
.../unittests/CodeGen/QualTypeMapperTest.cpp | 72 ++++++++++++-----
llvm/include/llvm/ABI/IRTypeMapper.h | 1 +
llvm/include/llvm/ABI/Types.h | 77 +++++++++++++------
llvm/lib/ABI/IRTypeMapper.cpp | 13 ++--
llvm/unittests/ABI/TypesTest.cpp | 18 +++--
6 files changed, 132 insertions(+), 64 deletions(-)
diff --git a/clang/lib/CodeGen/QualTypeMapper.cpp b/clang/lib/CodeGen/QualTypeMapper.cpp
index fadb0d41a1756..fff11f9f00bb7 100644
--- a/clang/lib/CodeGen/QualTypeMapper.cpp
+++ b/clang/lib/CodeGen/QualTypeMapper.cpp
@@ -363,11 +363,12 @@ const llvm::abi::Type *QualTypeMapper::convertVectorType(const VectorType *VT) {
getABIVectorKind(VT->getVectorKind()));
}
-/// Converts the sizeless AArch64 SVE data and predicate builtin types,
-/// including the x2/x3/x4 tuples, to scalable LLVM ABI vector types.
+/// Converts the sizeless AArch64 SVE data and predicate builtin types.
+/// Single vectors become a scalable LLVM ABI VectorType. The x2/x3/x4
+/// forms become a TupleType of that vector.
///
/// \param BT The SVE BuiltinType to convert
-/// \return LLVM ABI VectorType with a scalable element count
+/// \return LLVM ABI VectorType or TupleType
const llvm::abi::Type *
QualTypeMapper::convertSVEBuiltinType(const BuiltinType *BT) {
ASTContext::BuiltinVectorTypeInfo Info = ASTCtx.getBuiltinVectorTypeInfo(BT);
@@ -386,9 +387,11 @@ QualTypeMapper::convertSVEBuiltinType(const BuiltinType *BT) {
? llvm::abi::VectorKind::SVEPredicate
: llvm::abi::VectorKind::SVEData;
- return Builder.getVectorType(ElementType, Info.EC,
- getTypeAlign(QualType(BT, 0)), VecKind,
- Info.NumVectors);
+ const llvm::abi::VectorType *VecTy = Builder.getVectorType(
+ ElementType, Info.EC, getTypeAlign(QualType(BT, 0)), VecKind);
+ if (Info.NumVectors == 1)
+ return VecTy;
+ return Builder.getTupleType(VecTy, Info.NumVectors);
}
/// Converts complex types to LLVM ABI complex representations.
diff --git a/clang/unittests/CodeGen/QualTypeMapperTest.cpp b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
index 4e354ab641e05..69a7a38ccf341 100644
--- a/clang/unittests/CodeGen/QualTypeMapperTest.cpp
+++ b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
@@ -46,6 +46,12 @@ class QualTypeMapperSVETest : public ::testing::Test {
return dyn_cast<llvm::abi::VectorType>(Mapper.convertType(QT));
}
+ /// Maps \p QT and returns it as an ABI tuple type, or null if it did not
+ /// map to a tuple.
+ const llvm::abi::TupleType *mapToTuple(QualType QT) {
+ return dyn_cast<llvm::abi::TupleType>(Mapper.convertType(QT));
+ }
+
/// Returns the underlying type of the file-scope typedef named \p Name.
QualType lookupTypedef(StringRef Name) {
for (Decl *D : context().getTranslationUnitDecl()->decls())
@@ -58,22 +64,34 @@ class QualTypeMapperSVETest : public ::testing::Test {
/// Checks an SVE data vector of \p NF vectors of \p NumEls elements, each
/// \p ElBits wide. \p IsFP selects whether the element is expected to be a
- /// floating-point or an integer type.
+ /// floating-point or an integer type. NF == 1 is a VectorType; NF > 1 is
+ /// a TupleType of that vector.
void checkDataVector(StringRef Name, QualType QT, unsigned NumEls,
unsigned ElBits, unsigned NF, bool IsFP) {
SCOPED_TRACE(Name);
- const llvm::abi::VectorType *VT = mapToVector(QT);
- ASSERT_NE(VT, nullptr) << "did not map to a vector type";
+ const llvm::abi::VectorType *VT = nullptr;
+ if (NF == 1) {
+ VT = mapToVector(QT);
+ ASSERT_NE(VT, nullptr) << "did not map to a vector type";
+ EXPECT_EQ(VT->getSizeInBits(),
+ llvm::TypeSize::getScalable(NumEls * ElBits));
+ EXPECT_EQ(VT->getAlignment(), llvm::Align(16));
+ } else {
+ const llvm::abi::TupleType *TT = mapToTuple(QT);
+ ASSERT_NE(TT, nullptr) << "did not map to a tuple type";
+ EXPECT_EQ(TT->getNumVectors(), NF);
+ EXPECT_EQ(TT->getSizeInBits(),
+ llvm::TypeSize::getScalable(NumEls * ElBits * NF));
+ EXPECT_EQ(TT->getAlignment(), llvm::Align(16));
+ VT = TT->getVectorType();
+ ASSERT_NE(VT, nullptr);
+ EXPECT_EQ(VT->getSizeInBits(),
+ llvm::TypeSize::getScalable(NumEls * ElBits));
+ }
EXPECT_EQ(VT->getVectorKind(), llvm::abi::VectorKind::SVEData);
EXPECT_TRUE(VT->isScalable());
EXPECT_EQ(VT->getNumElements(), llvm::ElementCount::getScalable(NumEls));
- EXPECT_EQ(VT->getNumVectors(), NF);
- EXPECT_EQ(VT->isTuple(), NF > 1);
- EXPECT_EQ(VT->getSizeInBits(),
- llvm::TypeSize::getScalable(NumEls * ElBits * NF));
- // AAPCS64 aligns every SVE data vector, including the tuples, to 16 bytes.
- EXPECT_EQ(VT->getAlignment(), llvm::Align(16));
const llvm::abi::Type *Elt = VT->getElementType();
EXPECT_EQ(Elt->getSizeInBits(), llvm::TypeSize::getFixed(ElBits));
@@ -85,15 +103,26 @@ class QualTypeMapperSVETest : public ::testing::Test {
void checkPredicateVector(StringRef Name, QualType QT, unsigned NumEls,
unsigned NF) {
SCOPED_TRACE(Name);
- const llvm::abi::VectorType *VT = mapToVector(QT);
- ASSERT_NE(VT, nullptr) << "did not map to a vector type";
+ const llvm::abi::VectorType *VT = nullptr;
+ if (NF == 1) {
+ VT = mapToVector(QT);
+ ASSERT_NE(VT, nullptr) << "did not map to a vector type";
+ EXPECT_EQ(VT->getSizeInBits(), llvm::TypeSize::getScalable(NumEls));
+ EXPECT_EQ(VT->getAlignment(), llvm::Align(2));
+ } else {
+ const llvm::abi::TupleType *TT = mapToTuple(QT);
+ ASSERT_NE(TT, nullptr) << "did not map to a tuple type";
+ EXPECT_EQ(TT->getNumVectors(), NF);
+ EXPECT_EQ(TT->getSizeInBits(), llvm::TypeSize::getScalable(NumEls * NF));
+ EXPECT_EQ(TT->getAlignment(), llvm::Align(2));
+ VT = TT->getVectorType();
+ ASSERT_NE(VT, nullptr);
+ EXPECT_EQ(VT->getSizeInBits(), llvm::TypeSize::getScalable(NumEls));
+ }
EXPECT_EQ(VT->getVectorKind(), llvm::abi::VectorKind::SVEPredicate);
EXPECT_TRUE(VT->isScalable());
EXPECT_EQ(VT->getNumElements(), llvm::ElementCount::getScalable(NumEls));
- EXPECT_EQ(VT->getNumVectors(), NF);
- EXPECT_EQ(VT->getSizeInBits(), llvm::TypeSize::getScalable(NumEls * NF));
- EXPECT_EQ(VT->getAlignment(), llvm::Align(2));
const llvm::abi::Type *Elt = VT->getElementType();
ASSERT_TRUE(Elt->isInteger());
@@ -122,8 +151,8 @@ typedef int generic_int32x4_t __attribute__((vector_size(16)));
CodeGen::QualTypeMapper Mapper;
};
-// Every SVE data vector, including the x2/x3/x4 tuples, maps to a scalable
-// vector tagged as SVE data.
+// Every SVE data vector maps to a scalable vector tagged as SVE data.
+// The x2/x3/x4 forms map to a TupleType of that vector.
TEST_F(QualTypeMapperSVETest, DataVectors) {
#define SVE_VECTOR_TYPE_INT(Name, MangledName, Id, SingletonId, NumEls, \
ElBits, NF, IsSigned) \
@@ -146,8 +175,8 @@ TEST_F(QualTypeMapperSVETest, DataVectors) {
#include "clang/Basic/AArch64ACLETypes.def"
}
-// Every SVE predicate, including the x2/x4 tuples, maps to a scalable vector
-// of one-bit elements tagged as an SVE predicate.
+// Every SVE predicate maps to a scalable vector of one-bit elements tagged
+// as an SVE predicate. The x2/x4 forms map to a TupleType of that vector.
TEST_F(QualTypeMapperSVETest, PredicateVectors) {
#define SVE_PREDICATE_TYPE_ALL(Name, MangledName, Id, SingletonId, NumEls, NF) \
checkPredicateVector(#Name, context().SingletonId, NumEls, NF);
@@ -172,11 +201,12 @@ TEST_F(QualTypeMapperSVETest, RepresentativeTypes) {
cast<llvm::abi::IntegerType>(SVUint8->getElementType())->isSigned());
// svfloat64x2_t is two <vscale x 2 x double> vectors.
- const llvm::abi::VectorType *SVFloat64x2 =
- mapToVector(context().SveFloat64x2Ty);
+ const llvm::abi::TupleType *SVFloat64x2 =
+ mapToTuple(context().SveFloat64x2Ty);
ASSERT_NE(SVFloat64x2, nullptr);
EXPECT_EQ(SVFloat64x2->getNumVectors(), 2u);
- EXPECT_EQ(SVFloat64x2->getNumElements(), llvm::ElementCount::getScalable(2));
+ EXPECT_EQ(SVFloat64x2->getVectorType()->getNumElements(),
+ llvm::ElementCount::getScalable(2));
EXPECT_EQ(SVFloat64x2->getSizeInBits(), llvm::TypeSize::getScalable(256));
// svbool_t is <vscale x 16 x i1>.
diff --git a/llvm/include/llvm/ABI/IRTypeMapper.h b/llvm/include/llvm/ABI/IRTypeMapper.h
index 0fbb9a550348b..cd8160489c705 100644
--- a/llvm/include/llvm/ABI/IRTypeMapper.h
+++ b/llvm/include/llvm/ABI/IRTypeMapper.h
@@ -44,6 +44,7 @@ class IRTypeMapper {
llvm::Type *convertArrayType(const abi::ArrayType *AT);
llvm::Type *convertVectorType(const abi::VectorType *VT);
+ llvm::Type *convertTupleType(const abi::TupleType *TT);
llvm::Type *convertRecordType(const abi::RecordType *RT);
llvm::Type *convertComplexType(const abi::ComplexType *CT);
llvm::Type *convertMemberPointerType(const abi::MemberPointerType *MPT);
diff --git a/llvm/include/llvm/ABI/Types.h b/llvm/include/llvm/ABI/Types.h
index d04bd765ab5e6..5cfb23edd502a 100644
--- a/llvm/include/llvm/ABI/Types.h
+++ b/llvm/include/llvm/ABI/Types.h
@@ -34,6 +34,7 @@ enum class TypeKind {
Pointer,
Array,
Vector,
+ Tuple,
Record,
};
@@ -78,6 +79,7 @@ class Type {
bool isPointer() const { return Kind == TypeKind::Pointer; }
bool isArray() const { return Kind == TypeKind::Array; }
bool isVector() const { return Kind == TypeKind::Vector; }
+ bool isTuple() const { return Kind == TypeKind::Tuple; }
bool isRecord() const { return Kind == TypeKind::Record; }
bool isMemberPointer() const { return Kind == TypeKind::MemberPointer; }
bool isComplex() const { return Kind == TypeKind::Complex; }
@@ -217,12 +219,13 @@ enum class VectorKind {
/// A plain vector, such as a Neon vector or a GCC vector_size vector.
Generic,
- /// An AArch64 SVE data vector, such as svint32_t or svfloat64x2_t. Data
- /// vectors are passed in Z registers.
+ /// An AArch64 SVE data vector, such as svint32_t. Data vectors are
+ /// passed in Z registers. Tuples of these vectors use TupleType.
SVEData,
- /// An AArch64 SVE predicate vector, such as svbool_t or svboolx4_t.
- /// Predicate vectors have one-bit elements and are passed in P registers.
+ /// An AArch64 SVE predicate vector, such as svbool_t. Predicate
+ /// vectors have one-bit elements and are passed in P registers.
+ /// Tuples of these vectors use TupleType.
SVEPredicate,
/// The AArch64 __SVCount_t type. It is opaque rather than a real vector,
@@ -236,37 +239,26 @@ class VectorType : public Type {
const Type *ElementType;
ElementCount NumElements;
VectorKind VecKind;
- unsigned NumVectors;
static TypeSize computeSizeInBits(const Type *ElementType,
- ElementCount NumElements,
- unsigned NumVectors) {
+ ElementCount NumElements) {
return TypeSize(ElementType->getSizeInBits().getFixedValue() *
- NumElements.getKnownMinValue() * NumVectors,
+ NumElements.getKnownMinValue(),
NumElements.isScalable());
}
public:
VectorType(const Type *ElementType, ElementCount NumElements, Align ABIAlign,
- VectorKind VecKind = VectorKind::Generic, unsigned NumVectors = 1)
- : Type(TypeKind::Vector,
- computeSizeInBits(ElementType, NumElements, NumVectors), ABIAlign),
- ElementType(ElementType), NumElements(NumElements), VecKind(VecKind),
- NumVectors(NumVectors) {}
+ VectorKind VecKind = VectorKind::Generic)
+ : Type(TypeKind::Vector, computeSizeInBits(ElementType, NumElements),
+ ABIAlign),
+ ElementType(ElementType), NumElements(NumElements), VecKind(VecKind) {}
const Type *getElementType() const { return ElementType; }
-
- /// Returns the element count of a single vector. A tuple type holds
- /// getNumVectors() vectors of this shape.
ElementCount getNumElements() const { return NumElements; }
VectorKind getVectorKind() const { return VecKind; }
- /// Returns the number of vectors making up an SVE tuple type, which is 1,
- /// 2, 3, or 4. Every other vector type holds exactly one.
- unsigned getNumVectors() const { return NumVectors; }
- bool isTuple() const { return NumVectors > 1; }
-
bool isScalable() const { return NumElements.isScalable(); }
bool isSVEData() const { return VecKind == VectorKind::SVEData; }
@@ -281,6 +273,36 @@ class VectorType : public Type {
}
};
+/// A homogeneous tuple of 2, 3, or 4 identical vectors, such as the
+/// AArch64 SVE types svint32x3_t and svboolx2_t.
+///
+/// The contained vector describes one register-shaped member. Size and
+/// alignment of the tuple cover the whole group: size is NumVectors times
+/// the vector size, and alignment matches the contained vector.
+class TupleType : public Type {
+private:
+ const VectorType *Vec;
+ unsigned NumVectors;
+
+ static TypeSize computeSizeInBits(const VectorType *Vec,
+ unsigned NumVectors) {
+ TypeSize VecSize = Vec->getSizeInBits();
+ return TypeSize(VecSize.getKnownMinValue() * NumVectors,
+ VecSize.isScalable());
+ }
+
+public:
+ TupleType(const VectorType *Vec, unsigned NumVectors)
+ : Type(TypeKind::Tuple, computeSizeInBits(Vec, NumVectors),
+ Vec->getAlignment()),
+ Vec(Vec), NumVectors(NumVectors) {}
+
+ const VectorType *getVectorType() const { return Vec; }
+ unsigned getNumVectors() const { return NumVectors; }
+
+ static bool classof(const Type *T) { return T->getKind() == TypeKind::Tuple; }
+};
+
struct FieldInfo {
const Type *FieldType;
uint64_t OffsetInBits;
@@ -420,10 +442,17 @@ class TypeBuilder {
const VectorType *getVectorType(const Type *ElementType,
ElementCount NumElements, Align Align,
- VectorKind VecKind = VectorKind::Generic,
- unsigned NumVectors = 1) {
+ VectorKind VecKind = VectorKind::Generic) {
return new (Allocator.Allocate<VectorType>())
- VectorType(ElementType, NumElements, Align, VecKind, NumVectors);
+ VectorType(ElementType, NumElements, Align, VecKind);
+ }
+
+ /// Creates a homogeneous tuple of \p NumVectors copies of \p Vec.
+ /// \p NumVectors must be 2, 3, or 4.
+ const TupleType *getTupleType(const VectorType *Vec, unsigned NumVectors) {
+ assert(NumVectors >= 2 && NumVectors <= 4 &&
+ "tuple types hold 2, 3, or 4 vectors");
+ return new (Allocator.Allocate<TupleType>()) TupleType(Vec, NumVectors);
}
/// Creates the AArch64 __SVCount_t type. The type is opaque, so it is
diff --git a/llvm/lib/ABI/IRTypeMapper.cpp b/llvm/lib/ABI/IRTypeMapper.cpp
index 4b1185ce3e2f2..adb7ed8ea7ceb 100644
--- a/llvm/lib/ABI/IRTypeMapper.cpp
+++ b/llvm/lib/ABI/IRTypeMapper.cpp
@@ -52,6 +52,9 @@ llvm::Type *IRTypeMapper::convertType(const abi::Type *ABIType) {
case abi::TypeKind::Vector:
Result = convertVectorType(cast<abi::VectorType>(ABIType));
break;
+ case abi::TypeKind::Tuple:
+ Result = convertTupleType(cast<abi::TupleType>(ABIType));
+ break;
case abi::TypeKind::Record:
Result = convertRecordType(cast<abi::RecordType>(ABIType));
break;
@@ -81,12 +84,12 @@ llvm::Type *IRTypeMapper::convertVectorType(const abi::VectorType *VT) {
return llvm::TargetExtType::get(Context, "aarch64.svcount");
llvm::Type *ElementType = convertType(VT->getElementType());
- llvm::Type *VecTy = llvm::VectorType::get(ElementType, VT->getNumElements());
- if (!VT->isTuple())
- return VecTy;
+ return llvm::VectorType::get(ElementType, VT->getNumElements());
+}
- // SVE tuples are a struct with one element per vector.
- SmallVector<llvm::Type *, 4> Elements(VT->getNumVectors(), VecTy);
+llvm::Type *IRTypeMapper::convertTupleType(const abi::TupleType *TT) {
+ llvm::Type *VecTy = convertType(TT->getVectorType());
+ SmallVector<llvm::Type *, 4> Elements(TT->getNumVectors(), VecTy);
return llvm::StructType::get(Context, Elements);
}
diff --git a/llvm/unittests/ABI/TypesTest.cpp b/llvm/unittests/ABI/TypesTest.cpp
index ef5cabb98525a..f56d41329ad03 100644
--- a/llvm/unittests/ABI/TypesTest.cpp
+++ b/llvm/unittests/ABI/TypesTest.cpp
@@ -20,6 +20,7 @@ using llvm::abi::FieldInfo;
using llvm::abi::RecordFlags;
using llvm::abi::RecordType;
using llvm::abi::StructPacking;
+using llvm::abi::TupleType;
using llvm::abi::TypeBuilder;
using llvm::abi::VectorKind;
using llvm::abi::VectorType;
@@ -137,7 +138,6 @@ TEST_F(ABITypesTest, GenericVector) {
EXPECT_FALSE(V4I32->isSVEType());
EXPECT_FALSE(V4I32->isScalable());
EXPECT_FALSE(V4I32->isTuple());
- EXPECT_EQ(V4I32->getNumVectors(), 1u);
EXPECT_EQ(V4I32->getSizeInBits(), TypeSize::getFixed(128));
}
@@ -158,16 +158,18 @@ TEST_F(ABITypesTest, SVEDataVector) {
// svint32x3_t is three <vscale x 4 x i32> vectors.
TEST_F(ABITypesTest, SVEDataVectorTuple) {
const llvm::abi::Type *I32 = TB.getIntegerType(32, Align(4), /*Signed=*/true);
- const VectorType *SVInt32x3 =
- TB.getVectorType(I32, ElementCount::getScalable(4), Align(16),
- VectorKind::SVEData, /*NumVectors=*/3);
+ const VectorType *SVInt32 = TB.getVectorType(
+ I32, ElementCount::getScalable(4), Align(16), VectorKind::SVEData);
+ const TupleType *SVInt32x3 = TB.getTupleType(SVInt32, /*NumVectors=*/3);
- EXPECT_TRUE(SVInt32x3->isSVEData());
EXPECT_TRUE(SVInt32x3->isTuple());
EXPECT_EQ(SVInt32x3->getNumVectors(), 3u);
- // getNumElements() describes one vector of the tuple, while the size covers
- // all of them.
- EXPECT_EQ(SVInt32x3->getNumElements(), ElementCount::getScalable(4));
+ EXPECT_EQ(SVInt32x3->getVectorType(), SVInt32);
+ EXPECT_EQ(SVInt32x3->getAlignment(), Align(16));
+ // The contained vector keeps a per-vector element count; the tuple size
+ // covers all of the vectors.
+ EXPECT_EQ(SVInt32->getNumElements(), ElementCount::getScalable(4));
+ EXPECT_EQ(SVInt32->getSizeInBits(), TypeSize::getScalable(128));
EXPECT_EQ(SVInt32x3->getSizeInBits(), TypeSize::getScalable(384));
}
>From 904c019bd3ec72a2cdfc88d3f39265fd68502392 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 9 Sep 2026 10:39:22 -0700
Subject: [PATCH 3/5] Add IRTypeMapperTest
---
llvm/unittests/ABI/CMakeLists.txt | 1 +
llvm/unittests/ABI/IRTypeMapperTest.cpp | 100 ++++++++++++++++++
.../gn/secondary/llvm/unittests/ABI/BUILD.gn | 1 +
3 files changed, 102 insertions(+)
create mode 100644 llvm/unittests/ABI/IRTypeMapperTest.cpp
diff --git a/llvm/unittests/ABI/CMakeLists.txt b/llvm/unittests/ABI/CMakeLists.txt
index fdc665e5a2c98..40cdd42ea6c61 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
+ IRTypeMapperTest.cpp
X86TargetInfoTest.cpp
TypesTest.cpp
)
diff --git a/llvm/unittests/ABI/IRTypeMapperTest.cpp b/llvm/unittests/ABI/IRTypeMapperTest.cpp
new file mode 100644
index 0000000000000..4a4723324d952
--- /dev/null
+++ b/llvm/unittests/ABI/IRTypeMapperTest.cpp
@@ -0,0 +1,100 @@
+//===- IRTypeMapperTest.cpp - ABI to LLVM IR type mapping 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/IRTypeMapper.h"
+#include "llvm/ABI/Types.h"
+#include "llvm/ADT/APFloat.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/DerivedTypes.h"
+#include "llvm/IR/LLVMContext.h"
+#include "llvm/Support/Alignment.h"
+#include "llvm/Support/Allocator.h"
+#include "llvm/Support/Casting.h"
+#include "llvm/Support/TypeSize.h"
+#include "gtest/gtest.h"
+
+namespace {
+
+class IRTypeMapperTest : public ::testing::Test {
+protected:
+ llvm::LLVMContext Context;
+ llvm::DataLayout DL{""};
+ llvm::BumpPtrAllocator Alloc;
+ llvm::abi::TypeBuilder TB{Alloc};
+ llvm::abi::IRTypeMapper Mapper{Context, DL};
+};
+
+TEST_F(IRTypeMapperTest, GenericVectorMapsToFixedVector) {
+ const llvm::abi::Type *I32 =
+ TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true);
+ const llvm::abi::VectorType *V4I32 =
+ TB.getVectorType(I32, llvm::ElementCount::getFixed(4), llvm::Align(16));
+
+ auto *Vec = llvm::dyn_cast<llvm::VectorType>(Mapper.convertType(V4I32));
+ ASSERT_NE(Vec, nullptr);
+ EXPECT_FALSE(Vec->isScalableTy());
+ EXPECT_EQ(Vec->getElementCount(), llvm::ElementCount::getFixed(4));
+ EXPECT_TRUE(Vec->getElementType()->isIntegerTy(32));
+}
+
+TEST_F(IRTypeMapperTest, SVEDataVectorMapsToScalableVector) {
+ const llvm::abi::Type *I32 =
+ TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true);
+ const llvm::abi::VectorType *SVInt32 =
+ TB.getVectorType(I32, llvm::ElementCount::getScalable(4), llvm::Align(16),
+ llvm::abi::VectorKind::SVEData);
+
+ auto *Vec = llvm::dyn_cast<llvm::VectorType>(Mapper.convertType(SVInt32));
+ ASSERT_NE(Vec, nullptr);
+ EXPECT_TRUE(Vec->isScalableTy());
+ EXPECT_EQ(Vec->getElementCount(), llvm::ElementCount::getScalable(4));
+ EXPECT_TRUE(Vec->getElementType()->isIntegerTy(32));
+}
+
+TEST_F(IRTypeMapperTest, SVEPredicateVectorMapsToScalableI1Vector) {
+ const llvm::abi::Type *I1 =
+ TB.getIntegerType(1, llvm::Align(1), /*Signed=*/false);
+ const llvm::abi::VectorType *SVBool =
+ TB.getVectorType(I1, llvm::ElementCount::getScalable(16), llvm::Align(2),
+ llvm::abi::VectorKind::SVEPredicate);
+
+ auto *Vec = llvm::dyn_cast<llvm::VectorType>(Mapper.convertType(SVBool));
+ ASSERT_NE(Vec, nullptr);
+ EXPECT_TRUE(Vec->isScalableTy());
+ EXPECT_EQ(Vec->getElementCount(), llvm::ElementCount::getScalable(16));
+ EXPECT_TRUE(Vec->getElementType()->isIntegerTy(1));
+}
+
+TEST_F(IRTypeMapperTest, SVECountMapsToAArch64SVCount) {
+ const llvm::abi::VectorType *SVCount = TB.getSVECountType(llvm::Align(2));
+
+ auto *TET = llvm::dyn_cast<llvm::TargetExtType>(Mapper.convertType(SVCount));
+ ASSERT_NE(TET, nullptr);
+ EXPECT_EQ(TET->getName(), "aarch64.svcount");
+}
+
+TEST_F(IRTypeMapperTest, SVEDataTupleMapsToStructOfVectors) {
+ const llvm::abi::Type *I32 =
+ TB.getIntegerType(32, llvm::Align(4), /*Signed=*/true);
+ const llvm::abi::VectorType *SVInt32 =
+ TB.getVectorType(I32, llvm::ElementCount::getScalable(4), llvm::Align(16),
+ llvm::abi::VectorKind::SVEData);
+ const llvm::abi::TupleType *SVInt32x3 =
+ TB.getTupleType(SVInt32, /*NumVectors=*/3);
+
+ auto *Struct =
+ llvm::dyn_cast<llvm::StructType>(Mapper.convertType(SVInt32x3));
+ ASSERT_NE(Struct, nullptr);
+ ASSERT_EQ(Struct->getNumElements(), 3u);
+
+ llvm::Type *ExpectedVec = Mapper.convertType(SVInt32);
+ for (unsigned I = 0; I < 3; ++I)
+ EXPECT_EQ(Struct->getElementType(I), ExpectedVec);
+}
+
+} // 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..43b98bccc447e 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",
+ "IRTypeMapperTest.cpp",
"X86TargetInfoTest.cpp",
"TypesTest.cpp",
]
>From 5940974b59b221e7005cfed05199e020e211c9c8 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 9 Sep 2026 10:56:31 -0700
Subject: [PATCH 4/5] Address review concerns about predicate element types
---
clang/lib/CodeGen/QualTypeMapper.cpp | 4 ++++
clang/unittests/CodeGen/QualTypeMapperTest.cpp | 9 ++++++++-
llvm/include/llvm/ABI/Types.h | 8 +++++---
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/QualTypeMapper.cpp b/clang/lib/CodeGen/QualTypeMapper.cpp
index fff11f9f00bb7..dfcc3f97430df 100644
--- a/clang/lib/CodeGen/QualTypeMapper.cpp
+++ b/clang/lib/CodeGen/QualTypeMapper.cpp
@@ -359,6 +359,10 @@ const llvm::abi::Type *QualTypeMapper::convertVectorType(const VectorType *VT) {
llvm::ElementCount NumElements = llvm::ElementCount::getFixed(NElems);
llvm::Align VectorAlign = getTypeAlign(VectorQualType);
+ // SveFixedLengthPredicate is tagged SVEPredicate, like sizeless svbool_t.
+ // The element type is left as the AST unsigned char (i8). The builtin path
+ // below maps sizeless predicates to i1. Both match the Clang AST, but
+ // consumers that key only off VectorKind cannot assume a 1-bit element.
return Builder.getVectorType(ElementType, NumElements, VectorAlign,
getABIVectorKind(VT->getVectorKind()));
}
diff --git a/clang/unittests/CodeGen/QualTypeMapperTest.cpp b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
index 69a7a38ccf341..8fab3354c5c29 100644
--- a/clang/unittests/CodeGen/QualTypeMapperTest.cpp
+++ b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
@@ -263,13 +263,20 @@ TEST_F(QualTypeMapperSVETest, FixedLengthSVEVectors) {
// Clang derives the element count of a fixed-length predicate by dividing
// the vector length in bits by the square of the char width, so a 256-bit
- // vector length gives 4 elements.
+ // vector length gives 4 unsigned char (i8) elements. That is still tagged
+ // SVEPredicate, unlike sizeless svbool_t, which uses i1 elements.
const llvm::abi::VectorType *FixedBool =
mapToVector(lookupTypedef("fixed_bool_t"));
ASSERT_NE(FixedBool, nullptr);
EXPECT_EQ(FixedBool->getVectorKind(), llvm::abi::VectorKind::SVEPredicate);
EXPECT_FALSE(FixedBool->isScalable());
EXPECT_EQ(FixedBool->getNumElements(), llvm::ElementCount::getFixed(4));
+
+ const auto *FixedBoolElt =
+ dyn_cast<llvm::abi::IntegerType>(FixedBool->getElementType());
+ ASSERT_NE(FixedBoolElt, nullptr);
+ EXPECT_EQ(FixedBoolElt->getSizeInBits(), llvm::TypeSize::getFixed(8));
+ EXPECT_FALSE(FixedBoolElt->isSigned());
}
// An ordinary vector must not be mistaken for an SVE type.
diff --git a/llvm/include/llvm/ABI/Types.h b/llvm/include/llvm/ABI/Types.h
index 5cfb23edd502a..6f0c3f83bd5a1 100644
--- a/llvm/include/llvm/ABI/Types.h
+++ b/llvm/include/llvm/ABI/Types.h
@@ -223,9 +223,11 @@ enum class VectorKind {
/// passed in Z registers. Tuples of these vectors use TupleType.
SVEData,
- /// An AArch64 SVE predicate vector, such as svbool_t. Predicate
- /// vectors have one-bit elements and are passed in P registers.
- /// Tuples of these vectors use TupleType.
+ /// An AArch64 SVE predicate vector, such as svbool_t. These are passed
+ /// in P registers. Sizeless predicates have one-bit elements; the
+ /// fixed-length arm_sve_vector_bits form keeps unsigned char (i8)
+ /// elements, matching the Clang AST. Both use this kind. Tuples of
+ /// these vectors use TupleType.
SVEPredicate,
/// The AArch64 __SVCount_t type. It is opaque rather than a real vector,
>From f78be460b7044e9cf5a4f9cebc703b692ae558fe Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Thu, 10 Sep 2026 09:52:58 -0700
Subject: [PATCH 5/5] Address review feedback
---
clang/unittests/CodeGen/QualTypeMapperTest.cpp | 2 +-
llvm/include/llvm/ABI/Types.h | 10 ++--------
llvm/unittests/ABI/TypesTest.cpp | 2 +-
3 files changed, 4 insertions(+), 10 deletions(-)
diff --git a/clang/unittests/CodeGen/QualTypeMapperTest.cpp b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
index 8fab3354c5c29..6fdd4318bf4f0 100644
--- a/clang/unittests/CodeGen/QualTypeMapperTest.cpp
+++ b/clang/unittests/CodeGen/QualTypeMapperTest.cpp
@@ -262,7 +262,7 @@ TEST_F(QualTypeMapperSVETest, FixedLengthSVEVectors) {
EXPECT_EQ(FixedInt32->getSizeInBits(), llvm::TypeSize::getFixed(256));
// Clang derives the element count of a fixed-length predicate by dividing
- // the vector length in bits by the square of the char width, so a 256-bit
+ // the vector length in bytes by the the char bit width, so a 256-bit
// vector length gives 4 unsigned char (i8) elements. That is still tagged
// SVEPredicate, unlike sizeless svbool_t, which uses i1 elements.
const llvm::abi::VectorType *FixedBool =
diff --git a/llvm/include/llvm/ABI/Types.h b/llvm/include/llvm/ABI/Types.h
index 6f0c3f83bd5a1..a766e3e7ebb0f 100644
--- a/llvm/include/llvm/ABI/Types.h
+++ b/llvm/include/llvm/ABI/Types.h
@@ -262,6 +262,7 @@ class VectorType : public Type {
VectorKind getVectorKind() const { return VecKind; }
bool isScalable() const { return NumElements.isScalable(); }
+ bool isFixedLength() const { return !NumElements.isScalable(); }
bool isSVEData() const { return VecKind == VectorKind::SVEData; }
bool isSVEPredicate() const { return VecKind == VectorKind::SVEPredicate; }
@@ -286,16 +287,9 @@ class TupleType : public Type {
const VectorType *Vec;
unsigned NumVectors;
- static TypeSize computeSizeInBits(const VectorType *Vec,
- unsigned NumVectors) {
- TypeSize VecSize = Vec->getSizeInBits();
- return TypeSize(VecSize.getKnownMinValue() * NumVectors,
- VecSize.isScalable());
- }
-
public:
TupleType(const VectorType *Vec, unsigned NumVectors)
- : Type(TypeKind::Tuple, computeSizeInBits(Vec, NumVectors),
+ : Type(TypeKind::Tuple, (Vec->getSizeInBits() * NumVectors),
Vec->getAlignment()),
Vec(Vec), NumVectors(NumVectors) {}
diff --git a/llvm/unittests/ABI/TypesTest.cpp b/llvm/unittests/ABI/TypesTest.cpp
index f56d41329ad03..57acdbb97dfc1 100644
--- a/llvm/unittests/ABI/TypesTest.cpp
+++ b/llvm/unittests/ABI/TypesTest.cpp
@@ -136,7 +136,7 @@ TEST_F(ABITypesTest, GenericVector) {
EXPECT_EQ(V4I32->getVectorKind(), VectorKind::Generic);
EXPECT_FALSE(V4I32->isSVEType());
- EXPECT_FALSE(V4I32->isScalable());
+ EXPECT_TRUE(V4I32->isFixedLength());
EXPECT_FALSE(V4I32->isTuple());
EXPECT_EQ(V4I32->getSizeInBits(), TypeSize::getFixed(128));
}
More information about the cfe-commits
mailing list