//===------- VFABIDemanglerTest.cpp - VFABI 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/IR/VFABIDemangler.h" #include "llvm/ADT/StringRef.h" #include "llvm/Analysis/VectorUtils.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Module.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" #include using namespace llvm; namespace { static LLVMContext Ctx; /// Perform tests against VFABI Rules. `invokeParser` creates a VFInfo object /// and a scalar FunctionType, which are used by tests to check that: /// 1. The scalar and vector names are correct. /// 2. The number of parameters from the parsed mangled name matches the number /// of arguments in the scalar function passed as FunctionType string. /// 3. The number of vector parameters and their types match the values /// specified in the test. /// On masked functions it also checks that the last parameter is a mask (ie, /// GlobalPredicate). /// 4. The vector function is correctly found to have a mask. /// class VFABIParserTest : public ::testing::Test { private: // Parser output. VFInfo Info; /// Reset the data needed for the test. void reset(const StringRef ScalarFTyStr) { M = parseAssemblyString("declare void @dummy()", Err, Ctx); EXPECT_NE(M.get(), nullptr) << "Loading an invalid module.\n " << Err.getMessage() << "\n"; Type *Ty = parseType(ScalarFTyStr, Err, *(M.get())); ScalarFTy = dyn_cast(Ty); EXPECT_NE(ScalarFTy, nullptr) << "Invalid function type string: " << ScalarFTyStr << "\n" << Err.getMessage() << "\n"; // Reset the VFInfo Info = VFInfo(); } // Data needed to load the optional IR passed to invokeParser SMDiagnostic Err; std::unique_ptr M; FunctionType *ScalarFTy = nullptr; protected: // References to the parser output field. ElementCount &VF = Info.Shape.VF; VFISAKind &ISA = Info.ISA; /// Parameters for the vectorized function SmallVector &Parameters = Info.Shape.Parameters; std::string &ScalarName = Info.ScalarName; std::string &VectorName = Info.VectorName; /// Invoke the parser. Every time this method is invoked the state of the test /// is reset. /// /// \p MangledName string the parser has to demangle. /// /// \p ScalarFTyStr FunctionType string to get the signature of the scalar /// function, which is used by `tryDemangleForVFABI` to check for the number /// of arguments on scalable vectors, and by `matchParameters` to perform some /// additional checking in the tests in this file. bool invokeParser(const StringRef MangledName, const StringRef ScalarFTyStr = "void()") { // Reset the VFInfo to be able to call `invokeParser` multiple times in // the same test. reset(ScalarFTyStr); const auto OptInfo = VFABI::tryDemangleForVFABI(MangledName, ScalarFTy); if (OptInfo) Info = *OptInfo; return OptInfo.has_value(); } /// Returns whether the parsed function contains a mask. bool isMasked() const { return Info.isMasked(); } FunctionType *getFunctionType() { return VFABI::createFunctionType(Info, ScalarFTy); } }; } // unnamed namespace // Function Types commonly used in tests FunctionType *FTyMaskVLen2_i32 = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getFixed(2)), VectorType::get(Type::getInt1Ty(Ctx), ElementCount::getFixed(2)), }, false); FunctionType *FTyNoMaskVLen2_i32 = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getFixed(2)), }, false); FunctionType *FTyMaskedVLA_i32 = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getScalable(4)), VectorType::get(Type::getInt1Ty(Ctx), ElementCount::getScalable(4)), }, false); // This test makes sure that the demangling method succeeds only on // valid values of the string. TEST_F(VFABIParserTest, OnlyValidNames) { // Incomplete string. EXPECT_FALSE(invokeParser("")); EXPECT_FALSE(invokeParser("_ZGV")); EXPECT_FALSE(invokeParser("_ZGVn")); EXPECT_FALSE(invokeParser("_ZGVnN")); EXPECT_FALSE(invokeParser("_ZGVnN2")); EXPECT_FALSE(invokeParser("_ZGVnN2v")); EXPECT_FALSE(invokeParser("_ZGVnN2v_")); // Missing parameters. EXPECT_FALSE(invokeParser("_ZGVnN2_foo")); // Missing _ZGV prefix. EXPECT_FALSE(invokeParser("_ZVnN2v_foo")); // Missing . EXPECT_FALSE(invokeParser("_ZGVN2v_foo")); // Missing . EXPECT_FALSE(invokeParser("_ZGVn2v_foo")); // Missing . EXPECT_FALSE(invokeParser("_ZGVnNv_foo")); // Missing . EXPECT_FALSE(invokeParser("_ZGVnN2v_")); // Missing _ separator. EXPECT_FALSE(invokeParser("_ZGVnN2vfoo")); // Missing . EXPECT_FALSE(invokeParser("_ZGVnN2v_foo()")); // Unterminated name. EXPECT_FALSE(invokeParser("_ZGVnN2v_foo(bar")); } TEST_F(VFABIParserTest, ParamListParsing) { EXPECT_TRUE( invokeParser("_ZGVnN2vl16Ls32R3l_foo", "void(i32, i32, i32, ptr, i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_EQ(false, isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), {VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getFixed(2)), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo(), Type::getInt32Ty(Ctx)}, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(Parameters.size(), (unsigned)5); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector, 0})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::OMP_Linear, 16})); EXPECT_EQ(Parameters[2], VFParameter({2, VFParamKind::OMP_LinearValPos, 32})); EXPECT_EQ(Parameters[3], VFParameter({3, VFParamKind::OMP_LinearRef, 3})); EXPECT_EQ(Parameters[4], VFParameter({4, VFParamKind::OMP_Linear, 1})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "_ZGVnN2vl16Ls32R3l_foo"); } TEST_F(VFABIParserTest, ScalarNameAndVectorName_01) { EXPECT_TRUE(invokeParser("_ZGVnM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_EQ(true, isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ScalarNameAndVectorName_02) { EXPECT_TRUE(invokeParser("_ZGVnM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_EQ(true, isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ScalarNameAndVectorName_03) { EXPECT_TRUE( invokeParser("_ZGVnM2v___foo_bar_abc(fooBarAbcVec)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_EQ(true, isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(ScalarName, "__foo_bar_abc"); EXPECT_EQ(VectorName, "fooBarAbcVec"); } TEST_F(VFABIParserTest, ScalarNameOnly) { EXPECT_TRUE(invokeParser("_ZGVnM2v___foo_bar_abc", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_EQ(true, isMasked()); EXPECT_EQ(ScalarName, "__foo_bar_abc"); // no vector name specified (as it's optional), so it should have the entire // mangled name. EXPECT_EQ(VectorName, "_ZGVnM2v___foo_bar_abc"); } TEST_F(VFABIParserTest, Parse) { EXPECT_TRUE( invokeParser("_ZGVnN2vls2Ls27Us4Rs5l1L10U100R1000_foo", "void(i32, i32, i32, i32, ptr, i32, i32, i32, ptr)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_FALSE(isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getFixed(2)), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo(), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo(), }, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)9); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector, 0})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::OMP_LinearPos, 2})); EXPECT_EQ(Parameters[2], VFParameter({2, VFParamKind::OMP_LinearValPos, 27})); EXPECT_EQ(Parameters[3], VFParameter({3, VFParamKind::OMP_LinearUValPos, 4})); EXPECT_EQ(Parameters[4], VFParameter({4, VFParamKind::OMP_LinearRefPos, 5})); EXPECT_EQ(Parameters[5], VFParameter({5, VFParamKind::OMP_Linear, 1})); EXPECT_EQ(Parameters[6], VFParameter({6, VFParamKind::OMP_LinearVal, 10})); EXPECT_EQ(Parameters[7], VFParameter({7, VFParamKind::OMP_LinearUVal, 100})); EXPECT_EQ(Parameters[8], VFParameter({8, VFParamKind::OMP_LinearRef, 1000})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "_ZGVnN2vls2Ls27Us4Rs5l1L10U100R1000_foo"); } TEST_F(VFABIParserTest, ParseVectorName) { EXPECT_TRUE(invokeParser("_ZGVnN2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_FALSE(isMasked()); EXPECT_EQ(getFunctionType(), FTyNoMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)1); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector, 0})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, LinearWithCompileTimeNegativeStep) { EXPECT_TRUE(invokeParser("_ZGVnN2ln1Ln10Un100Rn1000_foo(vector_foo)", "void(i32, i32, i32, ptr)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_FALSE(isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo()}, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)4); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::OMP_Linear, -1})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::OMP_LinearVal, -10})); EXPECT_EQ(Parameters[2], VFParameter({2, VFParamKind::OMP_LinearUVal, -100})); EXPECT_EQ(Parameters[3], VFParameter({3, VFParamKind::OMP_LinearRef, -1000})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseScalableSVE) { EXPECT_TRUE(invokeParser("_ZGVsMxv_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskedVLA_i32); EXPECT_EQ(VF, ElementCount::getScalable(4)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseFixedWidthSVE) { EXPECT_TRUE(invokeParser("_ZGVsM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, NotAVectorFunctionABIName) { // Vector names should start with `_ZGV`. EXPECT_FALSE(invokeParser("ZGVnN2v_foo")); } TEST_F(VFABIParserTest, LinearWithRuntimeStep) { EXPECT_FALSE(invokeParser("_ZGVnN2ls_foo")) << "A number should be present after \"ls\"."; EXPECT_TRUE(invokeParser("_ZGVnN2ls2_foo", "void(i32)")); EXPECT_FALSE(invokeParser("_ZGVnN2Rs_foo")) << "A number should be present after \"Rs\"."; EXPECT_TRUE(invokeParser("_ZGVnN2Rs4_foo", "void(i32)")); EXPECT_FALSE(invokeParser("_ZGVnN2Ls_foo")) << "A number should be present after \"Ls\"."; EXPECT_TRUE(invokeParser("_ZGVnN2Ls6_foo", "void(i32)")); EXPECT_FALSE(invokeParser("_ZGVnN2Us_foo")) << "A number should be present after \"Us\"."; EXPECT_TRUE(invokeParser("_ZGVnN2Us8_foo", "void(i32)")); } TEST_F(VFABIParserTest, LinearWithoutCompileTime) { EXPECT_TRUE(invokeParser("_ZGVnN3lLRUlnLnRnUn_foo(vector_foo)", "void(i32, i32, ptr, i32, i32, i32, ptr, i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_FALSE(isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo(), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo(), Type::getInt32Ty(Ctx)}, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(Parameters.size(), (unsigned)8); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::OMP_Linear, 1})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::OMP_LinearVal, 1})); EXPECT_EQ(Parameters[2], VFParameter({2, VFParamKind::OMP_LinearRef, 1})); EXPECT_EQ(Parameters[3], VFParameter({3, VFParamKind::OMP_LinearUVal, 1})); EXPECT_EQ(Parameters[4], VFParameter({4, VFParamKind::OMP_Linear, -1})); EXPECT_EQ(Parameters[5], VFParameter({5, VFParamKind::OMP_LinearVal, -1})); EXPECT_EQ(Parameters[6], VFParameter({6, VFParamKind::OMP_LinearRef, -1})); EXPECT_EQ(Parameters[7], VFParameter({7, VFParamKind::OMP_LinearUVal, -1})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, LLVM_ISA) { EXPECT_FALSE(invokeParser("_ZGV_LLVM_N2v_foo")); EXPECT_TRUE(invokeParser("_ZGV_LLVM_N2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::LLVM); EXPECT_FALSE(isMasked()); EXPECT_EQ(getFunctionType(), FTyNoMaskVLen2_i32); EXPECT_EQ(Parameters.size(), (unsigned)1); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, InvalidMask) { EXPECT_FALSE(invokeParser("_ZGVsK2v_foo")); } TEST_F(VFABIParserTest, InvalidParameter) { EXPECT_FALSE(invokeParser("_ZGVsM2vX_foo")); } TEST_F(VFABIParserTest, Align) { EXPECT_TRUE(invokeParser("_ZGVsN2l2a2_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_FALSE(isMasked()); EXPECT_EQ(Parameters.size(), (unsigned)1); EXPECT_EQ(Parameters[0].Alignment, Align(2)); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); FunctionType *FTy = FunctionType::get(Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx)}, false); EXPECT_EQ(getFunctionType(), FTy); // Missing alignment value. EXPECT_FALSE(invokeParser("_ZGVsM2l2a_foo")); // Invalid alignment token "x". EXPECT_FALSE(invokeParser("_ZGVsM2l2ax_foo")); // Alignment MUST be associated to a paramater. EXPECT_FALSE(invokeParser("_ZGVsM2a2_foo")); // Alignment must be a power of 2. EXPECT_FALSE(invokeParser("_ZGVsN2l2a0_foo")); EXPECT_TRUE(invokeParser("_ZGVsN2l2a1_foo", "void(i32)")); EXPECT_FALSE(invokeParser("_ZGVsN2l2a3_foo")); EXPECT_FALSE(invokeParser("_ZGVsN2l2a6_foo")); } TEST_F(VFABIParserTest, ParseUniform) { EXPECT_TRUE(invokeParser("_ZGVnN2u_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_FALSE(isMasked()); FunctionType *FTy = FunctionType::get(Type::getVoidTy(Ctx), {Type::getInt32Ty(Ctx)}, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)1); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::OMP_Uniform, 0})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); // Uniform doesn't expect extra data. EXPECT_FALSE(invokeParser("_ZGVnN2u0_foo")); } TEST_F(VFABIParserTest, ISAIndependentMangling) { // This test makes sure that the mangling of the parameters in // independent on the token. const StringRef IRTy = "void(i32, i32, i32, i32, ptr, i32, i32, i32, i32, i32)"; FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), {VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getFixed(2)), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)->getPointerTo(), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx), Type::getInt32Ty(Ctx)}, false); const SmallVector ExpectedParams = { VFParameter({0, VFParamKind::Vector, 0}), VFParameter({1, VFParamKind::OMP_LinearPos, 2}), VFParameter({2, VFParamKind::OMP_LinearValPos, 27}), VFParameter({3, VFParamKind::OMP_LinearUValPos, 4}), VFParameter({4, VFParamKind::OMP_LinearRefPos, 5}), VFParameter({5, VFParamKind::OMP_Linear, 1}), VFParameter({6, VFParamKind::OMP_LinearVal, 10}), VFParameter({7, VFParamKind::OMP_LinearUVal, 100}), VFParameter({8, VFParamKind::OMP_LinearRef, 1000}), VFParameter({9, VFParamKind::OMP_Uniform, 0}), }; #define __COMMON_CHECKS \ do { \ EXPECT_EQ(VF, ElementCount::getFixed(2)); \ EXPECT_FALSE(isMasked()); \ EXPECT_EQ(getFunctionType(), FTy); \ EXPECT_EQ(Parameters.size(), (unsigned)10); \ EXPECT_EQ(Parameters, ExpectedParams); \ EXPECT_EQ(ScalarName, "foo"); \ EXPECT_EQ(VectorName, "vector_foo"); \ } while (0) // Advanced SIMD: = "n" EXPECT_TRUE(invokeParser( "_ZGVnN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); __COMMON_CHECKS; // SVE: = "s" EXPECT_TRUE(invokeParser( "_ZGVsN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::SVE); __COMMON_CHECKS; // SSE: = "b" EXPECT_TRUE(invokeParser( "_ZGVbN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::SSE); __COMMON_CHECKS; // AVX: = "c" EXPECT_TRUE(invokeParser( "_ZGVcN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::AVX); __COMMON_CHECKS; // AVX2: = "d" EXPECT_TRUE(invokeParser( "_ZGVdN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::AVX2); __COMMON_CHECKS; // AVX512: = "e" EXPECT_TRUE(invokeParser( "_ZGVeN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::AVX512); __COMMON_CHECKS; // LLVM: = "_LLVM_" internal vector function. EXPECT_TRUE(invokeParser( "_ZGV_LLVM_N2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::LLVM); __COMMON_CHECKS; // Unknown ISA (randomly using "q"). This test will need update if // some targets decide to use "q" as their ISA token. EXPECT_TRUE(invokeParser( "_ZGVqN2vls2Ls27Us4Rs5l1L10U100R1000u_foo(vector_foo)", IRTy)); EXPECT_EQ(ISA, VFISAKind::Unknown); __COMMON_CHECKS; #undef __COMMON_CHECKS } TEST_F(VFABIParserTest, MissingScalarName) { EXPECT_FALSE(invokeParser("_ZGVnN2v_")); } TEST_F(VFABIParserTest, MissingVectorName) { EXPECT_FALSE(invokeParser("_ZGVnN2v_foo()")); } TEST_F(VFABIParserTest, MissingVectorNameTermination) { EXPECT_FALSE(invokeParser("_ZGVnN2v_foo(bar")); } TEST_F(VFABIParserTest, ParseMaskingNEON) { EXPECT_TRUE(invokeParser("_ZGVnM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AdvancedSIMD); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseMaskingSVE) { EXPECT_TRUE(invokeParser("_ZGVsM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseMaskingSSE) { EXPECT_TRUE(invokeParser("_ZGVbM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::SSE); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseMaskingAVX) { EXPECT_TRUE(invokeParser("_ZGVcM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AVX); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseMaskingAVX2) { EXPECT_TRUE(invokeParser("_ZGVdM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AVX2); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseMaskingAVX512) { EXPECT_TRUE(invokeParser("_ZGVeM2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::AVX512); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseMaskingLLVM) { EXPECT_TRUE(invokeParser("_ZGV_LLVM_M2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::LLVM); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskVLen2_i32); EXPECT_EQ(VF, ElementCount::getFixed(2)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseScalableMaskingLLVM) { EXPECT_FALSE(invokeParser("_ZGV_LLVM_Mxv_foo(vector_foo)")); } TEST_F(VFABIParserTest, LLVM_InternalISA) { EXPECT_FALSE(invokeParser("_ZGV_LLVM_N2v_foo")); EXPECT_TRUE(invokeParser("_ZGV_LLVM_N2v_foo(vector_foo)", "void(i32)")); EXPECT_EQ(ISA, VFISAKind::LLVM); EXPECT_FALSE(isMasked()); EXPECT_EQ(getFunctionType(), FTyNoMaskVLen2_i32); EXPECT_EQ(Parameters.size(), (unsigned)1); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, LLVM_Intrinsics) { EXPECT_TRUE(invokeParser("_ZGV_LLVM_N4vv_llvm.pow.f32(__svml_powf4)", "void(float, float)")); EXPECT_EQ(ISA, VFISAKind::LLVM); EXPECT_FALSE(isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getFloatTy(Ctx), ElementCount::getFixed(4)), VectorType::get(Type::getFloatTy(Ctx), ElementCount::getFixed(4)), }, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(VF, ElementCount::getFixed(4)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::Vector})); EXPECT_EQ(ScalarName, "llvm.pow.f32"); EXPECT_EQ(VectorName, "__svml_powf4"); } TEST_F(VFABIParserTest, ParseScalableRequiresDeclaration) { const char *MangledName = "_ZGVsMxv_sin(custom_vg)"; EXPECT_FALSE(invokeParser(MangledName)); EXPECT_TRUE(invokeParser(MangledName, "void(i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); EXPECT_EQ(getFunctionType(), FTyMaskedVLA_i32); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "sin"); EXPECT_EQ(VectorName, "custom_vg"); } TEST_F(VFABIParserTest, ZeroIsInvalidVLEN) { EXPECT_FALSE(invokeParser("_ZGVeM0v_foo")); EXPECT_FALSE(invokeParser("_ZGVeN0v_foo")); EXPECT_FALSE(invokeParser("_ZGVsM0v_foo")); EXPECT_FALSE(invokeParser("_ZGVsN0v_foo")); } TEST_F(VFABIParserTest, ParseScalableMaskingSVE) { EXPECT_TRUE(invokeParser("_ZGVsMxv_foo(vector_foo)", "i32(i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); FunctionType *FTy = FunctionType::get( VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getScalable(4)), {VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getScalable(4)), VectorType::get(Type::getInt1Ty(Ctx), ElementCount::getScalable(4))}, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(VF, ElementCount::getScalable(4)); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } TEST_F(VFABIParserTest, ParseScalableMaskingSVESincos) { EXPECT_TRUE(invokeParser("_ZGVsMxvl8l8_sincos(custom_vector_sincos)", "void(double, ptr, ptr)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getDoubleTy(Ctx), ElementCount::getScalable(2)), Type::getInt32Ty(Ctx)->getPointerTo(), Type::getInt32Ty(Ctx)->getPointerTo(), VectorType::get(Type::getInt1Ty(Ctx), ElementCount::getScalable(2)), }, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(VF, ElementCount::getScalable(2)); EXPECT_EQ(Parameters.size(), (unsigned)4); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::OMP_Linear, 8})); EXPECT_EQ(Parameters[2], VFParameter({2, VFParamKind::OMP_Linear, 8})); EXPECT_EQ(Parameters[3], VFParameter({3, VFParamKind::GlobalPredicate})); EXPECT_EQ(ScalarName, "sincos"); EXPECT_EQ(VectorName, "custom_vector_sincos"); } // Make sure that we get the correct VF if the return type is wider than any // parameter type. TEST_F(VFABIParserTest, ParseWiderReturnTypeSVE) { EXPECT_TRUE(invokeParser("_ZGVsMxvv_foo(vector_foo)", "i64(i32, i32)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); FunctionType *FTy = FunctionType::get( VectorType::get(Type::getInt64Ty(Ctx), ElementCount::getScalable(2)), { VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getScalable(2)), VectorType::get(Type::getInt32Ty(Ctx), ElementCount::getScalable(2)), VectorType::get(Type::getInt1Ty(Ctx), ElementCount::getScalable(2)), }, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(Parameters.size(), (unsigned)3); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::Vector})); EXPECT_EQ(Parameters[2], VFParameter({2, VFParamKind::GlobalPredicate})); EXPECT_EQ(VF, ElementCount::getScalable(2)); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } // Make sure we handle void return types. TEST_F(VFABIParserTest, ParseVoidReturnTypeSVE) { EXPECT_TRUE(invokeParser("_ZGVsMxv_foo(vector_foo)", "void(i16)")); EXPECT_EQ(ISA, VFISAKind::SVE); EXPECT_TRUE(isMasked()); FunctionType *FTy = FunctionType::get( Type::getVoidTy(Ctx), { VectorType::get(Type::getInt16Ty(Ctx), ElementCount::getScalable(8)), VectorType::get(Type::getInt1Ty(Ctx), ElementCount::getScalable(8)), }, false); EXPECT_EQ(getFunctionType(), FTy); EXPECT_EQ(Parameters.size(), (unsigned)2); EXPECT_EQ(Parameters[0], VFParameter({0, VFParamKind::Vector})); EXPECT_EQ(Parameters[1], VFParameter({1, VFParamKind::GlobalPredicate})); EXPECT_EQ(VF, ElementCount::getScalable(8)); EXPECT_EQ(ScalarName, "foo"); EXPECT_EQ(VectorName, "vector_foo"); } // Make sure we reject unsupported parameter types. TEST_F(VFABIParserTest, ParseUnsupportedElementTypeSVE) { EXPECT_FALSE(invokeParser("_ZGVsMxv_foo(vector_foo)", "void(i128)")); } // Make sure we reject unsupported return types TEST_F(VFABIParserTest, ParseUnsupportedReturnTypeSVE) { EXPECT_FALSE(invokeParser("_ZGVsMxv_foo(vector_foo)", "fp128(float)")); } class VFABIAttrTest : public testing::Test { protected: void SetUp() override { M = parseAssemblyString(IR, Err, Ctx); // Get the only call instruction in the block, which is the first // instruction. CI = dyn_cast(&*(instructions(M->getFunction("f")).begin())); } const char *IR = "define i32 @f(i32 %a) {\n" " %1 = call i32 @g(i32 %a) #0\n" " ret i32 %1\n" "}\n" "declare i32 @g(i32)\n" "declare <2 x i32> @custom_vg(<2 x i32>)" "declare <4 x i32> @_ZGVnN4v_g(<4 x i32>)" "declare <8 x i32> @_ZGVnN8v_g(<8 x i32>)" "attributes #0 = { " "\"vector-function-abi-variant\"=\"" "_ZGVnN2v_g(custom_vg),_ZGVnN4v_g\" }"; LLVMContext Ctx; SMDiagnostic Err; std::unique_ptr M; CallInst *CI; SmallVector Mappings; }; TEST_F(VFABIAttrTest, Read) { VFABI::getVectorVariantNames(*CI, Mappings); SmallVector Exp; Exp.push_back("_ZGVnN2v_g(custom_vg)"); Exp.push_back("_ZGVnN4v_g"); EXPECT_EQ(Mappings, Exp); } TEST_F(VFABIAttrTest, Write) { Mappings.push_back("_ZGVnN8v_g"); Mappings.push_back("_ZGVnN2v_g(custom_vg)"); VFABI::setVectorVariantNames(CI, Mappings); const StringRef S = CI->getFnAttr("vector-function-abi-variant").getValueAsString(); EXPECT_EQ(S, "_ZGVnN8v_g,_ZGVnN2v_g(custom_vg)"); } static std::unique_ptr parseIR(LLVMContext &C, const char *IR) { SMDiagnostic Err; std::unique_ptr Mod = parseAssemblyString(IR, Err, C); if (!Mod) Err.print("VectorFunctionABITests", errs()); return Mod; } TEST(VFABIGetMappingsTest, IndirectCallInst) { LLVMContext C; std::unique_ptr M = parseIR(C, R"IR( define void @call(void () * %f) { entry: call void %f() ret void } )IR"); auto *F = dyn_cast_or_null(M->getNamedValue("call")); ASSERT_TRUE(F); auto *CI = dyn_cast(&F->front().front()); ASSERT_TRUE(CI); ASSERT_TRUE(CI->isIndirectCall()); auto Mappings = VFDatabase::getMappings(*CI); EXPECT_EQ(Mappings.size(), (unsigned)0); }