//===- VPIntrinsicTest.cpp - VPIntrinsic 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/ADT/SmallVector.h" #include "llvm/AsmParser/Parser.h" #include "llvm/CodeGen/ISDOpcodes.h" #include "llvm/IR/Constants.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/Verifier.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" #include using namespace llvm; namespace { class VPIntrinsicTest : public testing::Test { protected: LLVMContext Context; VPIntrinsicTest() : Context() {} LLVMContext C; SMDiagnostic Err; std::unique_ptr createVPDeclarationModule() { const char *BinaryIntOpcodes[] = {"add", "sub", "mul", "sdiv", "srem", "udiv", "urem", "and", "xor", "or", "ashr", "lshr", "shl"}; std::stringstream Str; for (const char *BinaryIntOpcode : BinaryIntOpcodes) Str << " declare <8 x i32> @llvm.vp." << BinaryIntOpcode << ".v8i32(<8 x i32>, <8 x i32>, <8 x i1>, i32) "; const char *BinaryFPOpcodes[] = {"fadd", "fsub", "fmul", "fdiv", "frem"}; for (const char *BinaryFPOpcode : BinaryFPOpcodes) Str << " declare <8 x float> @llvm.vp." << BinaryFPOpcode << ".v8f32(<8 x float>, <8 x float>, <8 x i1>, i32) "; Str << " declare void @llvm.vp.store.v8i32.p0v8i32(<8 x i32>, <8 x i32>*, " "<8 x i1>, i32) "; Str << " declare void @llvm.vp.scatter.v8i32.v8p0i32(<8 x i32>, <8 x " "i32*>, <8 x i1>, i32) "; Str << " declare <8 x i32> @llvm.vp.load.v8i32.p0v8i32(<8 x i32>*, <8 x " "i1>, i32) "; Str << " declare <8 x i32> @llvm.vp.gather.v8i32.v8p0i32(<8 x i32*>, <8 x " "i1>, i32) "; return parseAssemblyString(Str.str(), Err, C); } }; /// Check that the property scopes include/llvm/IR/VPIntrinsics.def are closed. TEST_F(VPIntrinsicTest, VPIntrinsicsDefScopes) { Optional ScopeVPID; #define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) \ ASSERT_FALSE(ScopeVPID.hasValue()); \ ScopeVPID = Intrinsic::VPID; #define END_REGISTER_VP_INTRINSIC(VPID) \ ASSERT_TRUE(ScopeVPID.hasValue()); \ ASSERT_EQ(ScopeVPID.getValue(), Intrinsic::VPID); \ ScopeVPID = None; Optional ScopeOPC; #define BEGIN_REGISTER_VP_SDNODE(SDOPC, ...) \ ASSERT_FALSE(ScopeOPC.hasValue()); \ ScopeOPC = ISD::SDOPC; #define END_REGISTER_VP_SDNODE(SDOPC) \ ASSERT_TRUE(ScopeOPC.hasValue()); \ ASSERT_EQ(ScopeOPC.getValue(), ISD::SDOPC); \ ScopeOPC = None; #include "llvm/IR/VPIntrinsics.def" ASSERT_FALSE(ScopeVPID.hasValue()); ASSERT_FALSE(ScopeOPC.hasValue()); } /// Check that every VP intrinsic in the test module is recognized as a VP /// intrinsic. TEST_F(VPIntrinsicTest, VPModuleComplete) { std::unique_ptr M = createVPDeclarationModule(); assert(M); // Check that all @llvm.vp.* functions in the module are recognized vp // intrinsics. std::set SeenIDs; for (const auto &VPDecl : *M) { ASSERT_TRUE(VPDecl.isIntrinsic()); ASSERT_TRUE(VPIntrinsic::isVPIntrinsic(VPDecl.getIntrinsicID())); SeenIDs.insert(VPDecl.getIntrinsicID()); } // Check that every registered VP intrinsic has an instance in the test // module. #define BEGIN_REGISTER_VP_INTRINSIC(VPID, ...) \ ASSERT_TRUE(SeenIDs.count(Intrinsic::VPID)); #include "llvm/IR/VPIntrinsics.def" } /// Check that VPIntrinsic:canIgnoreVectorLengthParam() returns true /// if the vector length parameter does not mask off any lanes. TEST_F(VPIntrinsicTest, CanIgnoreVectorLength) { LLVMContext C; SMDiagnostic Err; std::unique_ptr M = parseAssemblyString( "declare <256 x i64> @llvm.vp.mul.v256i64(<256 x i64>, <256 x i64>, <256 x i1>, i32)" "declare @llvm.vp.mul.nxv2i64(, , , i32)" "declare @llvm.vp.mul.nxv1i64(, , , i32)" "declare i32 @llvm.vscale.i32()" "define void @test_static_vlen( " " <256 x i64> %i0, %si0x2, %si0x1," " <256 x i64> %i1, %si1x2, %si1x1," " <256 x i1> %m, %smx2, %smx1, i32 %vl) { " " %r0 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 %vl)" " %r1 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 256)" " %r2 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 0)" " %r3 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 7)" " %r4 = call <256 x i64> @llvm.vp.mul.v256i64(<256 x i64> %i0, <256 x i64> %i1, <256 x i1> %m, i32 123)" " %vs = call i32 @llvm.vscale.i32()" " %vs.x2 = mul i32 %vs, 2" " %r5 = call @llvm.vp.mul.nxv2i64( %si0x2, %si1x2, %smx2, i32 %vs.x2)" " %r6 = call @llvm.vp.mul.nxv2i64( %si0x2, %si1x2, %smx2, i32 %vs)" " %r7 = call @llvm.vp.mul.nxv2i64( %si0x2, %si1x2, %smx2, i32 99999)" " %r8 = call @llvm.vp.mul.nxv1i64( %si0x1, %si1x1, %smx1, i32 %vs)" " %r9 = call @llvm.vp.mul.nxv1i64( %si0x1, %si1x1, %smx1, i32 1)" " %r10 = call @llvm.vp.mul.nxv1i64( %si0x1, %si1x1, %smx1, i32 %vs.x2)" " %vs.wat = add i32 %vs, 2" " %r11 = call @llvm.vp.mul.nxv2i64( %si0x2, %si1x2, %smx2, i32 %vs.wat)" " ret void " "}", Err, C); auto *F = M->getFunction("test_static_vlen"); assert(F); const bool Expected[] = {false, true, false, false, false, true, false, false, true, false, true, false}; const auto *ExpectedIt = std::begin(Expected); for (auto &I : F->getEntryBlock()) { VPIntrinsic *VPI = dyn_cast(&I); if (!VPI) continue; ASSERT_NE(ExpectedIt, std::end(Expected)); ASSERT_EQ(*ExpectedIt, VPI->canIgnoreVectorLengthParam()); ++ExpectedIt; } } /// Check that the argument returned by /// VPIntrinsic::getParamPos(Intrinsic::ID) has the expected type. TEST_F(VPIntrinsicTest, GetParamPos) { std::unique_ptr M = createVPDeclarationModule(); assert(M); for (Function &F : *M) { ASSERT_TRUE(F.isIntrinsic()); Optional MaskParamPos = VPIntrinsic::getMaskParamPos(F.getIntrinsicID()); if (MaskParamPos.hasValue()) { Type *MaskParamType = F.getArg(MaskParamPos.getValue())->getType(); ASSERT_TRUE(MaskParamType->isVectorTy()); ASSERT_TRUE( cast(MaskParamType)->getElementType()->isIntegerTy(1)); } Optional VecLenParamPos = VPIntrinsic::getVectorLengthParamPos(F.getIntrinsicID()); if (VecLenParamPos.hasValue()) { Type *VecLenParamType = F.getArg(VecLenParamPos.getValue())->getType(); ASSERT_TRUE(VecLenParamType->isIntegerTy(32)); } } } /// Check that going from Opcode to VP intrinsic and back results in the same /// Opcode. TEST_F(VPIntrinsicTest, OpcodeRoundTrip) { std::vector Opcodes; Opcodes.reserve(100); { #define HANDLE_INST(OCNum, OCName, Class) Opcodes.push_back(OCNum); #include "llvm/IR/Instruction.def" } unsigned FullTripCounts = 0; for (unsigned OC : Opcodes) { Intrinsic::ID VPID = VPIntrinsic::getForOpcode(OC); // No equivalent VP intrinsic available. if (VPID == Intrinsic::not_intrinsic) continue; Optional RoundTripOC = VPIntrinsic::getFunctionalOpcodeForVP(VPID); // No equivalent Opcode available. if (!RoundTripOC) continue; ASSERT_EQ(*RoundTripOC, OC); ++FullTripCounts; } ASSERT_NE(FullTripCounts, 0u); } /// Check that going from VP intrinsic to Opcode and back results in the same /// intrinsic id. TEST_F(VPIntrinsicTest, IntrinsicIDRoundTrip) { std::unique_ptr M = createVPDeclarationModule(); assert(M); unsigned FullTripCounts = 0; for (const auto &VPDecl : *M) { auto VPID = VPDecl.getIntrinsicID(); Optional OC = VPIntrinsic::getFunctionalOpcodeForVP(VPID); // no equivalent Opcode available if (!OC) continue; Intrinsic::ID RoundTripVPID = VPIntrinsic::getForOpcode(*OC); ASSERT_EQ(RoundTripVPID, VPID); ++FullTripCounts; } ASSERT_NE(FullTripCounts, 0u); } /// Check that VPIntrinsic::getDeclarationForParams works. TEST_F(VPIntrinsicTest, VPIntrinsicDeclarationForParams) { std::unique_ptr M = createVPDeclarationModule(); assert(M); auto OutM = std::make_unique("", M->getContext()); for (auto &F : *M) { auto *FuncTy = F.getFunctionType(); // Declare intrinsic anew with explicit types. std::vector Values; for (auto *ParamTy : FuncTy->params()) Values.push_back(UndefValue::get(ParamTy)); ASSERT_NE(F.getIntrinsicID(), Intrinsic::not_intrinsic); auto *NewDecl = VPIntrinsic::getDeclarationForParams( OutM.get(), F.getIntrinsicID(), Values); ASSERT_TRUE(NewDecl); // Check that 'old decl' == 'new decl'. ASSERT_EQ(F.getIntrinsicID(), NewDecl->getIntrinsicID()); FunctionType::param_iterator ItNewParams = NewDecl->getFunctionType()->param_begin(); FunctionType::param_iterator EndItNewParams = NewDecl->getFunctionType()->param_end(); for (auto *ParamTy : FuncTy->params()) { ASSERT_NE(ItNewParams, EndItNewParams); ASSERT_EQ(*ItNewParams, ParamTy); ++ItNewParams; } } } /// Check that the HANDLE_VP_TO_CONSTRAINEDFP maps to an existing intrinsic with /// the right amount of metadata args. TEST_F(VPIntrinsicTest, HandleToConstrainedFP) { #define HANDLE_VP_TO_CONSTRAINEDFP(HASROUND, HASEXCEPT, CFPID) \ { \ SmallVector T; \ Intrinsic::getIntrinsicInfoTableEntries(Intrinsic::CFPID, T); \ unsigned NumMetadataArgs = 0; \ for (auto TD : T) \ NumMetadataArgs += (TD.Kind == Intrinsic::IITDescriptor::Metadata); \ ASSERT_EQ(NumMetadataArgs, (unsigned)(HASROUND + HASEXCEPT)); \ } #include "llvm/IR/VPIntrinsics.def" } } // end anonymous namespace