[clang] [llvm] [LLVMABI] Add support for SVE types in the LLVM ABI library (PR #221375)
Paul Walker via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 10 05:11:09 PDT 2026
================
@@ -0,0 +1,295 @@
+//===- 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));
+ }
+
+ /// 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())
+ 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. 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 = 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));
+
+ 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 = 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));
+
+ 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 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) \
+ 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 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);
+#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::TupleType *SVFloat64x2 =
+ mapToTuple(context().SveFloat64x2Ty);
+ ASSERT_NE(SVFloat64x2, nullptr);
+ EXPECT_EQ(SVFloat64x2->getNumVectors(), 2u);
+ 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>.
+ 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
----------------
paulwalker-arm wrote:
```suggestion
// Clang derives the element count of a fixed-length predicate by dividing
// the vector length in bytes by the char bit width, so a 256-bit
```
Up to you but the current "square of" description seems odd to my eyes.
https://github.com/llvm/llvm-project/pull/221375
More information about the cfe-commits
mailing list