//===- unittests/IR/PassBuilderCallbacksTest.cpp - PB Callback 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/Testing/Support/Error.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace llvm; namespace { using testing::AnyNumber; using testing::AtLeast; using testing::DoDefault; using testing::Not; using testing::Return; using testing::Expectation; using testing::Invoke; using testing::WithArgs; using testing::_; /// A CRTP base for analysis mock handles /// /// This class reconciles mocking with the value semantics implementation of the /// AnalysisManager. Analysis mock handles should derive from this class and /// call \c setDefault() in their constroctur for wiring up the defaults defined /// by this base with their mock run() and invalidate() implementations. template , typename... ExtraArgTs> class MockAnalysisHandleBase { public: class Analysis : public AnalysisInfoMixin { friend AnalysisInfoMixin; friend MockAnalysisHandleBase; static AnalysisKey Key; DerivedT *Handle; Analysis(DerivedT &Handle) : Handle(&Handle) { static_assert(std::is_base_of::value, "Must pass the derived type to this template!"); } public: class Result { friend MockAnalysisHandleBase; DerivedT *Handle; Result(DerivedT &Handle) : Handle(&Handle) {} public: // Forward invalidation events to the mock handle. bool invalidate(IRUnitT &IR, const PreservedAnalyses &PA, typename AnalysisManagerT::Invalidator &Inv) { return Handle->invalidate(IR, PA, Inv); } }; Result run(IRUnitT &IR, AnalysisManagerT &AM, ExtraArgTs... ExtraArgs) { return Handle->run(IR, AM, ExtraArgs...); } }; Analysis getAnalysis() { return Analysis(static_cast(*this)); } typename Analysis::Result getResult() { return typename Analysis::Result(static_cast(*this)); } static StringRef getName() { return llvm::getTypeName(); } protected: // FIXME: MSVC seems unable to handle a lambda argument to Invoke from within // the template, so we use a boring static function. static bool invalidateCallback(IRUnitT &IR, const PreservedAnalyses &PA, typename AnalysisManagerT::Invalidator &Inv) { auto PAC = PA.template getChecker(); return !PAC.preserved() && !PAC.template preservedSet>(); } /// Derived classes should call this in their constructor to set up default /// mock actions. (We can't do this in our constructor because this has to /// run after the DerivedT is constructed.) void setDefaults() { ON_CALL(static_cast(*this), run(_, _, testing::Matcher(_)...)) .WillByDefault(Return(this->getResult())); ON_CALL(static_cast(*this), invalidate(_, _, _)) .WillByDefault(Invoke(&invalidateCallback)); } }; /// A CRTP base for pass mock handles /// /// This class reconciles mocking with the value semantics implementation of the /// PassManager. Pass mock handles should derive from this class and /// call \c setDefault() in their constroctur for wiring up the defaults defined /// by this base with their mock run() and invalidate() implementations. template AnalysisKey MockAnalysisHandleBase::Analysis::Key; template , typename... ExtraArgTs> class MockPassHandleBase { public: class Pass : public PassInfoMixin { friend MockPassHandleBase; DerivedT *Handle; Pass(DerivedT &Handle) : Handle(&Handle) { static_assert(std::is_base_of::value, "Must pass the derived type to this template!"); } public: PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM, ExtraArgTs... ExtraArgs) { return Handle->run(IR, AM, ExtraArgs...); } }; static StringRef getName() { return llvm::getTypeName(); } Pass getPass() { return Pass(static_cast(*this)); } protected: /// Derived classes should call this in their constructor to set up default /// mock actions. (We can't do this in our constructor because this has to /// run after the DerivedT is constructed.) void setDefaults() { ON_CALL(static_cast(*this), run(_, _, testing::Matcher(_)...)) .WillByDefault(Return(PreservedAnalyses::all())); } }; /// Mock handles for passes for the IRUnits Module, CGSCC, Function, Loop. /// These handles define the appropriate run() mock interface for the respective /// IRUnit type. template struct MockPassHandle; template <> struct MockPassHandle : MockPassHandleBase, Loop, LoopAnalysisManager, LoopStandardAnalysisResults &, LPMUpdater &> { MOCK_METHOD4(run, PreservedAnalyses(Loop &, LoopAnalysisManager &, LoopStandardAnalysisResults &, LPMUpdater &)); static void invalidateLoop(Loop &L, LoopAnalysisManager &, LoopStandardAnalysisResults &, LPMUpdater &Updater) { Updater.markLoopAsDeleted(L, L.getName()); } MockPassHandle() { setDefaults(); } }; template <> struct MockPassHandle : MockPassHandleBase, Function> { MOCK_METHOD2(run, PreservedAnalyses(Function &, FunctionAnalysisManager &)); MockPassHandle() { setDefaults(); } }; template <> struct MockPassHandle : MockPassHandleBase, LazyCallGraph::SCC, CGSCCAnalysisManager, LazyCallGraph &, CGSCCUpdateResult &> { MOCK_METHOD4(run, PreservedAnalyses(LazyCallGraph::SCC &, CGSCCAnalysisManager &, LazyCallGraph &G, CGSCCUpdateResult &UR)); static void invalidateSCC(LazyCallGraph::SCC &C, CGSCCAnalysisManager &, LazyCallGraph &, CGSCCUpdateResult &UR) { UR.InvalidatedSCCs.insert(&C); } MockPassHandle() { setDefaults(); } }; template <> struct MockPassHandle : MockPassHandleBase, Module> { MOCK_METHOD2(run, PreservedAnalyses(Module &, ModuleAnalysisManager &)); MockPassHandle() { setDefaults(); } }; /// Mock handles for analyses for the IRUnits Module, CGSCC, Function, Loop. /// These handles define the appropriate run() and invalidate() mock interfaces /// for the respective IRUnit type. template struct MockAnalysisHandle; template <> struct MockAnalysisHandle : MockAnalysisHandleBase, Loop, LoopAnalysisManager, LoopStandardAnalysisResults &> { MOCK_METHOD3_T(run, typename Analysis::Result(Loop &, LoopAnalysisManager &, LoopStandardAnalysisResults &)); MOCK_METHOD3_T(invalidate, bool(Loop &, const PreservedAnalyses &, LoopAnalysisManager::Invalidator &)); MockAnalysisHandle() { this->setDefaults(); } }; template <> struct MockAnalysisHandle : MockAnalysisHandleBase, Function> { MOCK_METHOD2(run, Analysis::Result(Function &, FunctionAnalysisManager &)); MOCK_METHOD3(invalidate, bool(Function &, const PreservedAnalyses &, FunctionAnalysisManager::Invalidator &)); MockAnalysisHandle() { setDefaults(); } }; template <> struct MockAnalysisHandle : MockAnalysisHandleBase, LazyCallGraph::SCC, CGSCCAnalysisManager, LazyCallGraph &> { MOCK_METHOD3(run, Analysis::Result(LazyCallGraph::SCC &, CGSCCAnalysisManager &, LazyCallGraph &)); MOCK_METHOD3(invalidate, bool(LazyCallGraph::SCC &, const PreservedAnalyses &, CGSCCAnalysisManager::Invalidator &)); MockAnalysisHandle() { setDefaults(); } }; template <> struct MockAnalysisHandle : MockAnalysisHandleBase, Module> { MOCK_METHOD2(run, Analysis::Result(Module &, ModuleAnalysisManager &)); MOCK_METHOD3(invalidate, bool(Module &, const PreservedAnalyses &, ModuleAnalysisManager::Invalidator &)); MockAnalysisHandle() { setDefaults(); } }; static std::unique_ptr parseIR(LLVMContext &C, const char *IR) { SMDiagnostic Err; return parseAssemblyString(IR, Err, C); } /// Helper for HasName matcher that returns getName both for IRUnit and /// for IRUnit pointer wrapper into llvm::Any (wrapped by PassInstrumentation). template std::string getName(const IRUnitT &IR) { return std::string(IR.getName()); } template <> std::string getName(const StringRef &name) { return std::string(name); } template <> std::string getName(const llvm::Any &WrappedIR) { if (any_isa(WrappedIR)) return any_cast(WrappedIR)->getName().str(); if (any_isa(WrappedIR)) return any_cast(WrappedIR)->getName().str(); if (any_isa(WrappedIR)) return any_cast(WrappedIR)->getName().str(); if (any_isa(WrappedIR)) return any_cast(WrappedIR)->getName(); return ""; } /// Define a custom matcher for objects which support a 'getName' method. /// /// LLVM often has IR objects or analysis objects which expose a name /// and in tests it is convenient to match these by name for readability. /// Usually, this name is either a StringRef or a plain std::string. This /// matcher supports any type exposing a getName() method of this form whose /// return value is compatible with an std::ostream. For StringRef, this uses /// the shift operator defined above. /// /// It should be used as: /// /// HasName("my_function") /// /// No namespace or other qualification is required. MATCHER_P(HasName, Name, "") { *result_listener << "has name '" << getName(arg) << "'"; return Name == getName(arg); } MATCHER_P(HasNameRegex, Name, "") { *result_listener << "has name '" << getName(arg) << "'"; llvm::Regex r(Name); return r.match(getName(arg)); } struct MockPassInstrumentationCallbacks { PassInstrumentationCallbacks Callbacks; MockPassInstrumentationCallbacks() { ON_CALL(*this, runBeforePass(_, _)).WillByDefault(Return(true)); } MOCK_METHOD2(runBeforePass, bool(StringRef PassID, llvm::Any)); MOCK_METHOD2(runBeforeSkippedPass, void(StringRef PassID, llvm::Any)); MOCK_METHOD2(runBeforeNonSkippedPass, void(StringRef PassID, llvm::Any)); MOCK_METHOD3(runAfterPass, void(StringRef PassID, llvm::Any, const PreservedAnalyses &PA)); MOCK_METHOD2(runAfterPassInvalidated, void(StringRef PassID, const PreservedAnalyses &PA)); MOCK_METHOD2(runBeforeAnalysis, void(StringRef PassID, llvm::Any)); MOCK_METHOD2(runAfterAnalysis, void(StringRef PassID, llvm::Any)); void registerPassInstrumentation() { Callbacks.registerShouldRunOptionalPassCallback( [this](StringRef P, llvm::Any IR) { return this->runBeforePass(P, IR); }); Callbacks.registerBeforeSkippedPassCallback( [this](StringRef P, llvm::Any IR) { this->runBeforeSkippedPass(P, IR); }); Callbacks.registerBeforeNonSkippedPassCallback( [this](StringRef P, llvm::Any IR) { this->runBeforeNonSkippedPass(P, IR); }); Callbacks.registerAfterPassCallback( [this](StringRef P, llvm::Any IR, const PreservedAnalyses &PA) { this->runAfterPass(P, IR, PA); }); Callbacks.registerAfterPassInvalidatedCallback( [this](StringRef P, const PreservedAnalyses &PA) { this->runAfterPassInvalidated(P, PA); }); Callbacks.registerBeforeAnalysisCallback([this](StringRef P, llvm::Any IR) { return this->runBeforeAnalysis(P, IR); }); Callbacks.registerAfterAnalysisCallback( [this](StringRef P, llvm::Any IR) { this->runAfterAnalysis(P, IR); }); } void ignoreNonMockPassInstrumentation(StringRef IRName) { // Generic EXPECT_CALLs are needed to match instrumentation on unimportant // parts of a pipeline that we do not care about (e.g. various passes added // by default by PassBuilder - Verifier pass etc). // Make sure to avoid ignoring Mock passes/analysis, we definitely want // to check these explicitly. EXPECT_CALL(*this, runBeforePass(Not(HasNameRegex("Mock")), HasName(IRName))) .Times(AnyNumber()); EXPECT_CALL( *this, runBeforeSkippedPass(Not(HasNameRegex("Mock")), HasName(IRName))) .Times(AnyNumber()); EXPECT_CALL(*this, runBeforeNonSkippedPass(Not(HasNameRegex("Mock")), HasName(IRName))) .Times(AnyNumber()); EXPECT_CALL(*this, runAfterPass(Not(HasNameRegex("Mock")), HasName(IRName), _)) .Times(AnyNumber()); EXPECT_CALL(*this, runBeforeAnalysis(Not(HasNameRegex("Mock")), HasName(IRName))) .Times(AnyNumber()); EXPECT_CALL(*this, runAfterAnalysis(Not(HasNameRegex("Mock")), HasName(IRName))) .Times(AnyNumber()); } }; template class PassBuilderCallbacksTest; /// This test fixture is shared between all the actual tests below and /// takes care of setting up appropriate defaults. /// /// The template specialization serves to extract the IRUnit and AM types from /// the given PassManagerT. template class PassBuilderCallbacksTest, ExtraPassArgTs...>> : public testing::Test { protected: using IRUnitT = TestIRUnitT; using AnalysisManagerT = AnalysisManager; using PassManagerT = PassManager; using AnalysisT = typename MockAnalysisHandle::Analysis; LLVMContext Context; std::unique_ptr M; MockPassInstrumentationCallbacks CallbacksHandle; PassBuilder PB; ModulePassManager PM; LoopAnalysisManager LAM; FunctionAnalysisManager FAM; CGSCCAnalysisManager CGAM; ModuleAnalysisManager AM; MockPassHandle PassHandle; MockAnalysisHandle AnalysisHandle; static PreservedAnalyses getAnalysisResult(IRUnitT &U, AnalysisManagerT &AM, ExtraAnalysisArgTs &&... Args) { (void)AM.template getResult( U, std::forward(Args)...); return PreservedAnalyses::all(); } PassBuilderCallbacksTest() : M(parseIR(Context, "declare void @bar()\n" "define void @foo(i32 %n) {\n" "entry:\n" " br label %loop\n" "loop:\n" " %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]\n" " %iv.next = add i32 %iv, 1\n" " tail call void @bar()\n" " %cmp = icmp eq i32 %iv, %n\n" " br i1 %cmp, label %exit, label %loop\n" "exit:\n" " ret void\n" "}\n")), CallbacksHandle(), PB(false, nullptr, PipelineTuningOptions(), None, &CallbacksHandle.Callbacks), PM(true), LAM(true), FAM(true), CGAM(true), AM(true) { EXPECT_TRUE(&CallbacksHandle.Callbacks == PB.getPassInstrumentationCallbacks()); /// Register a callback for analysis registration. /// /// The callback is a function taking a reference to an AnalyisManager /// object. When called, the callee gets to register its own analyses with /// this PassBuilder instance. PB.registerAnalysisRegistrationCallback([this](AnalysisManagerT &AM) { // Register our mock analysis AM.registerPass([this] { return AnalysisHandle.getAnalysis(); }); }); /// Register a callback for pipeline parsing. /// /// During parsing of a textual pipeline, the PassBuilder will call these /// callbacks for each encountered pass name that it does not know. This /// includes both simple pass names as well as names of sub-pipelines. In /// the latter case, the InnerPipeline is not empty. PB.registerPipelineParsingCallback( [this](StringRef Name, PassManagerT &PM, ArrayRef InnerPipeline) { /// Handle parsing of the names of analysis utilities such as /// require and invalidate for our /// analysis mock handle if (parseAnalysisUtilityPasses("test-analysis", Name, PM)) return true; /// Parse the name of our pass mock handle if (Name == "test-transform") { PM.addPass(PassHandle.getPass()); return true; } return false; }); /// Register builtin analyses and cross-register the analysis proxies PB.registerModuleAnalyses(AM); PB.registerCGSCCAnalyses(CGAM); PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, AM); } }; using ModuleCallbacksTest = PassBuilderCallbacksTest; using CGSCCCallbacksTest = PassBuilderCallbacksTest; using FunctionCallbacksTest = PassBuilderCallbacksTest; using LoopCallbacksTest = PassBuilderCallbacksTest; /// Test parsing of the name of our mock pass for all IRUnits. /// /// The pass should by default run our mock analysis and then preserve it. TEST_F(ModuleCallbacksTest, Passes) { EXPECT_CALL(AnalysisHandle, run(HasName(""), _)); EXPECT_CALL(PassHandle, run(HasName(""), _)) .WillOnce(Invoke(getAnalysisResult)); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(ModuleCallbacksTest, InstrumentedPasses) { EXPECT_CALL(AnalysisHandle, run(HasName(""), _)); EXPECT_CALL(PassHandle, run(HasName(""), _)) .WillOnce(Invoke(getAnalysisResult)); CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation not specifically mentioned below can be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); // PassInstrumentation calls should happen in-sequence, in the same order // as passes/analyses are scheduled. ::testing::Sequence PISequence; EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName(""))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), HasName(""))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), HasName(""))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), HasName(""))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), HasName(""), _)) .InSequence(PISequence); // No passes are skipped, so there should be no calls to // runBeforeSkippedPass(). EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName(""))) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(ModuleCallbacksTest, InstrumentedSkippedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation run here can safely be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); // Skip all passes by returning false. Pass managers and adaptor passes are // also passes that observed by the callbacks. EXPECT_CALL(CallbacksHandle, runBeforePass(_, _)) .WillRepeatedly(Return(false)); EXPECT_CALL(CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), _)) .Times(3); EXPECT_CALL(AnalysisHandle, run(HasName(""), _)).Times(0); EXPECT_CALL(PassHandle, run(HasName(""), _)).Times(0); // As the pass is skipped there is no nonskippedpass/afterPass, // beforeAnalysis/afterAnalysis as well. EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), _, _)) .Times(0); EXPECT_CALL(CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); // Order is important here. `Adaptor` expectations should be checked first // because the its argument contains 'PassManager' (for example: // ModuleToFunctionPassAdaptor{{.*}}PassManager{{.*}}). Check // `runBeforeNonSkippedPass` and `runAfterPass` to show that they are not // skipped. // // Pass managers are not ignored. // 5 = (1) ModulePassManager + (2) FunctionPassMangers + (1) LoopPassManager + // (1) CGSCCPassManager EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("PassManager"), _)) .Times(5); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("ModuleToFunctionPassAdaptor"), _)) .Times(1); EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass( HasNameRegex("ModuleToPostOrderCGSCCPassAdaptor"), _)) .Times(1); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("CGSCCToFunctionPassAdaptor"), _)) .Times(1); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("FunctionToLoopPassAdaptor"), _)) .Times(1); // The `runAfterPass` checks are the same as these of // `runBeforeNonSkippedPass`. EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("PassManager"), _, _)) .Times(5); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("ModuleToFunctionPassAdaptor"), _, _)) .Times(1); EXPECT_CALL( CallbacksHandle, runAfterPass(HasNameRegex("ModuleToPostOrderCGSCCPassAdaptor"), _, _)) .Times(1); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("CGSCCToFunctionPassAdaptor"), _, _)) .Times(1); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("FunctionToLoopPassAdaptor"), _, _)) .Times(1); // Ignore analyses introduced by adaptor passes. EXPECT_CALL(CallbacksHandle, runBeforeAnalysis(Not(HasNameRegex("MockAnalysisHandle")), _)) .Times(AnyNumber()); EXPECT_CALL(CallbacksHandle, runAfterAnalysis(Not(HasNameRegex("MockAnalysisHandle")), _)) .Times(AnyNumber()); // Register Funtion and Loop version of "test-transform" for testing PB.registerPipelineParsingCallback( [](StringRef Name, FunctionPassManager &FPM, ArrayRef) { if (Name == "test-transform") { FPM.addPass(MockPassHandle().getPass()); return true; } return false; }); PB.registerPipelineParsingCallback( [](StringRef Name, LoopPassManager &LPM, ArrayRef) { if (Name == "test-transform") { LPM.addPass(MockPassHandle().getPass()); return true; } return false; }); StringRef PipelineText = "test-transform,function(test-transform),cgscc(" "function(loop(test-transform)))"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(FunctionCallbacksTest, Passes) { EXPECT_CALL(AnalysisHandle, run(HasName("foo"), _)); EXPECT_CALL(PassHandle, run(HasName("foo"), _)) .WillOnce(Invoke(getAnalysisResult)); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(FunctionCallbacksTest, InstrumentedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation not specifically mentioned below can be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); EXPECT_CALL(AnalysisHandle, run(HasName("foo"), _)); EXPECT_CALL(PassHandle, run(HasName("foo"), _)) .WillOnce(Invoke(getAnalysisResult)); // PassInstrumentation calls should happen in-sequence, in the same order // as passes/analyses are scheduled. ::testing::Sequence PISequence; EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("foo"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), HasName("foo"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("foo"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("foo"))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), HasName("foo"), _)) .InSequence(PISequence); // No passes are skipped, so there should be no calls to // runBeforeSkippedPass(). EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName("foo"))) .Times(0); // Our mock pass does not invalidate IR. EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(FunctionCallbacksTest, InstrumentedSkippedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation run here can safely be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); // Skip the pass by returning false. EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("foo"))) .WillOnce(Return(false)); EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName("foo"))) .Times(1); EXPECT_CALL(AnalysisHandle, run(HasName("foo"), _)).Times(0); EXPECT_CALL(PassHandle, run(HasName("foo"), _)).Times(0); // As the pass is skipped there is no afterPass, beforeAnalysis/afterAnalysis // as well. EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), _, _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), _, _)) .Times(0); EXPECT_CALL(CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(LoopCallbacksTest, Passes) { EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _)); EXPECT_CALL(PassHandle, run(HasName("loop"), _, _, _)) .WillOnce(WithArgs<0, 1, 2>(Invoke(getAnalysisResult))); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(LoopCallbacksTest, InstrumentedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation not specifically mentioned below can be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); CallbacksHandle.ignoreNonMockPassInstrumentation("loop"); EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _)); EXPECT_CALL(PassHandle, run(HasName("loop"), _, _, _)) .WillOnce(WithArgs<0, 1, 2>(Invoke(getAnalysisResult))); // PassInstrumentation calls should happen in-sequence, in the same order // as passes/analyses are scheduled. ::testing::Sequence PISequence; EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), HasName("loop"), _)) .InSequence(PISequence); // Our mock pass does not invalidate IR. EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .Times(0); // No passes are skipped, so there should be no calls to // runBeforeSkippedPass(). EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName("loop"))) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(LoopCallbacksTest, InstrumentedInvalidatingPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation not specifically mentioned below can be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); CallbacksHandle.ignoreNonMockPassInstrumentation("loop"); EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _)); EXPECT_CALL(PassHandle, run(HasName("loop"), _, _, _)) .WillOnce(DoAll(WithArgs<0, 1, 2, 3>(Invoke(PassHandle.invalidateLoop)), WithArgs<0, 1, 2>(Invoke(getAnalysisResult)))); // PassInstrumentation calls should happen in-sequence, in the same order // as passes/analyses are scheduled. ::testing::Sequence PISequence; EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("loop"))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("^PassManager"), _)) .InSequence(PISequence); // Our mock pass invalidates IR, thus normal runAfterPass is never called. EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), HasName("loop"), _)) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(LoopCallbacksTest, InstrumentedSkippedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation run here can safely be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); CallbacksHandle.ignoreNonMockPassInstrumentation("loop"); // Skip the pass by returning false. EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("loop"))) .WillOnce(Return(false)); EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName("loop"))) .Times(1); EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _)).Times(0); EXPECT_CALL(PassHandle, run(HasName("loop"), _, _, _)).Times(0); // As the pass is skipped there is no afterPass, beforeAnalysis/afterAnalysis // as well. EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), _, _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(CGSCCCallbacksTest, Passes) { EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _)); EXPECT_CALL(PassHandle, run(HasName("(foo)"), _, _, _)) .WillOnce(WithArgs<0, 1, 2>(Invoke(getAnalysisResult))); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(CGSCCCallbacksTest, InstrumentedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation not specifically mentioned below can be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); CallbacksHandle.ignoreNonMockPassInstrumentation("(foo)"); EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _)); EXPECT_CALL(PassHandle, run(HasName("(foo)"), _, _, _)) .WillOnce(WithArgs<0, 1, 2>(Invoke(getAnalysisResult))); // PassInstrumentation calls should happen in-sequence, in the same order // as passes/analyses are scheduled. ::testing::Sequence PISequence; EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), HasName("(foo)"), _)) .InSequence(PISequence); // Our mock pass does not invalidate IR. EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .Times(0); // No passes are skipped, so there should be no calls to // runBeforeSkippedPass(). EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(CGSCCCallbacksTest, InstrumentedInvalidatingPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation not specifically mentioned below can be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); CallbacksHandle.ignoreNonMockPassInstrumentation("(foo)"); EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _)); EXPECT_CALL(PassHandle, run(HasName("(foo)"), _, _, _)) .WillOnce(DoAll(WithArgs<0, 1, 2, 3>(Invoke(PassHandle.invalidateSCC)), WithArgs<0, 1, 2>(Invoke(getAnalysisResult)))); // PassInstrumentation calls should happen in-sequence, in the same order // as passes/analyses are scheduled. ::testing::Sequence PISequence; EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL( CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), HasName("(foo)"))) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .InSequence(PISequence); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("^PassManager"), _)) .InSequence(PISequence); // Our mock pass does invalidate IR, thus normal runAfterPass is never called. EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), HasName("(foo)"), _)) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(CGSCCCallbacksTest, InstrumentedSkippedPasses) { CallbacksHandle.registerPassInstrumentation(); // Non-mock instrumentation run here can safely be ignored. CallbacksHandle.ignoreNonMockPassInstrumentation(""); CallbacksHandle.ignoreNonMockPassInstrumentation("foo"); CallbacksHandle.ignoreNonMockPassInstrumentation("(foo)"); // Skip the pass by returning false. EXPECT_CALL(CallbacksHandle, runBeforePass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .WillOnce(Return(false)); EXPECT_CALL( CallbacksHandle, runBeforeSkippedPass(HasNameRegex("MockPassHandle"), HasName("(foo)"))) .Times(1); // neither Analysis nor Pass are called. EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _)).Times(0); EXPECT_CALL(PassHandle, run(HasName("(foo)"), _, _, _)).Times(0); // As the pass is skipped there is no afterPass, beforeAnalysis/afterAnalysis // as well. EXPECT_CALL(CallbacksHandle, runBeforeNonSkippedPass(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPass(HasNameRegex("MockPassHandle"), _, _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterPassInvalidated(HasNameRegex("MockPassHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runBeforeAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); EXPECT_CALL(CallbacksHandle, runAfterAnalysis(HasNameRegex("MockAnalysisHandle"), _)) .Times(0); StringRef PipelineText = "test-transform"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } /// Test parsing of the names of analysis utilities for our mock analysis /// for all IRUnits. /// /// We first require<>, then invalidate<> it, expecting the analysis to be run /// once and subsequently invalidated. TEST_F(ModuleCallbacksTest, AnalysisUtilities) { EXPECT_CALL(AnalysisHandle, run(HasName(""), _)); EXPECT_CALL(AnalysisHandle, invalidate(HasName(""), _, _)); StringRef PipelineText = "require,invalidate"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(CGSCCCallbacksTest, PassUtilities) { EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _)); EXPECT_CALL(AnalysisHandle, invalidate(HasName("(foo)"), _, _)); StringRef PipelineText = "require,invalidate"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(FunctionCallbacksTest, AnalysisUtilities) { EXPECT_CALL(AnalysisHandle, run(HasName("foo"), _)); EXPECT_CALL(AnalysisHandle, invalidate(HasName("foo"), _, _)); StringRef PipelineText = "require,invalidate"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } TEST_F(LoopCallbacksTest, PassUtilities) { EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _)); EXPECT_CALL(AnalysisHandle, invalidate(HasName("loop"), _, _)); StringRef PipelineText = "require,invalidate"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); } /// Test parsing of the top-level pipeline. /// /// The ParseTopLevelPipeline callback takes over parsing of the entire pipeline /// from PassBuilder if it encounters an unknown pipeline entry at the top level /// (i.e., the first entry on the pipeline). /// This test parses a pipeline named 'another-pipeline', whose only elements /// may be the test-transform pass or the analysis utilities TEST_F(ModuleCallbacksTest, ParseTopLevelPipeline) { PB.registerParseTopLevelPipelineCallback( [this](ModulePassManager &MPM, ArrayRef Pipeline, bool DebugLogging) { auto &FirstName = Pipeline.front().Name; auto &InnerPipeline = Pipeline.front().InnerPipeline; if (FirstName == "another-pipeline") { for (auto &E : InnerPipeline) { if (parseAnalysisUtilityPasses("test-analysis", E.Name, PM)) continue; if (E.Name == "test-transform") { PM.addPass(PassHandle.getPass()); continue; } return false; } } return true; }); EXPECT_CALL(AnalysisHandle, run(HasName(""), _)); EXPECT_CALL(PassHandle, run(HasName(""), _)) .WillOnce(Invoke(getAnalysisResult)); EXPECT_CALL(AnalysisHandle, invalidate(HasName(""), _, _)); StringRef PipelineText = "another-pipeline(test-transform,invalidate)"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Succeeded()) << "Pipeline was: " << PipelineText; PM.run(*M, AM); /// Test the negative case PipelineText = "another-pipeline(instcombine)"; ASSERT_THAT_ERROR(PB.parsePassPipeline(PM, PipelineText), Failed()) << "Pipeline was: " << PipelineText; } } // end anonymous namespace