//===- CGSCCPassManagerTest.cpp -------------------------------------------===// // // 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/Analysis/CGSCCPassManager.h" #include "llvm/Analysis/LazyCallGraph.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/Function.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassManager.h" #include "llvm/IR/Verifier.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Transforms/Utils/CallGraphUpdater.h" #include "gtest/gtest.h" using namespace llvm; namespace { class TestModuleAnalysis : public AnalysisInfoMixin { public: struct Result { Result(int Count) : FunctionCount(Count) {} int FunctionCount; bool invalidate(Module &, const PreservedAnalyses &PA, ModuleAnalysisManager::Invalidator &) { // Check whether the analysis or all analyses on modules have been // preserved. auto PAC = PA.getChecker(); return !(PAC.preserved() || PAC.preservedSet>()); } }; TestModuleAnalysis(int &Runs) : Runs(Runs) {} Result run(Module &M, ModuleAnalysisManager &AM) { ++Runs; return Result(M.size()); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; }; AnalysisKey TestModuleAnalysis::Key; class TestSCCAnalysis : public AnalysisInfoMixin { public: struct Result { Result(int Count) : FunctionCount(Count) {} int FunctionCount; bool invalidate(LazyCallGraph::SCC &, const PreservedAnalyses &PA, CGSCCAnalysisManager::Invalidator &) { // Check whether the analysis or all analyses on SCCs have been // preserved. auto PAC = PA.getChecker(); return !(PAC.preserved() || PAC.preservedSet>()); } }; TestSCCAnalysis(int &Runs) : Runs(Runs) {} Result run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &) { ++Runs; return Result(C.size()); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; }; AnalysisKey TestSCCAnalysis::Key; class TestFunctionAnalysis : public AnalysisInfoMixin { public: struct Result { Result(int Count) : InstructionCount(Count) {} int InstructionCount; bool invalidate(Function &, const PreservedAnalyses &PA, FunctionAnalysisManager::Invalidator &) { // Check whether the analysis or all analyses on functions have been // preserved. auto PAC = PA.getChecker(); return !(PAC.preserved() || PAC.preservedSet>()); } }; TestFunctionAnalysis(int &Runs) : Runs(Runs) {} Result run(Function &F, FunctionAnalysisManager &AM) { ++Runs; int Count = 0; for (Instruction &I : instructions(F)) { (void)I; ++Count; } return Result(Count); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; }; AnalysisKey TestFunctionAnalysis::Key; class TestImmutableFunctionAnalysis : public AnalysisInfoMixin { public: struct Result { bool invalidate(Function &, const PreservedAnalyses &, FunctionAnalysisManager::Invalidator &) { return false; } }; TestImmutableFunctionAnalysis(int &Runs) : Runs(Runs) {} Result run(Function &F, FunctionAnalysisManager &AM) { ++Runs; return Result(); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; }; AnalysisKey TestImmutableFunctionAnalysis::Key; struct LambdaModulePass : public PassInfoMixin { template LambdaModulePass(T &&Arg) : Func(std::forward(Arg)) {} PreservedAnalyses run(Module &F, ModuleAnalysisManager &AM) { return Func(F, AM); } std::function Func; }; struct LambdaSCCPass : public PassInfoMixin { template LambdaSCCPass(T &&Arg) : Func(std::forward(Arg)) {} PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { return Func(C, AM, CG, UR); } std::function Func; }; struct LambdaFunctionPass : public PassInfoMixin { template LambdaFunctionPass(T &&Arg) : Func(std::forward(Arg)) {} PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) { return Func(F, AM); } std::function Func; }; std::unique_ptr parseIR(const char *IR) { // We just use a static context here. This is never called from multiple // threads so it is harmless no matter how it is implemented. We just need // the context to outlive the module which it does. static LLVMContext C; SMDiagnostic Err; return parseAssemblyString(IR, Err, C); } class CGSCCPassManagerTest : public ::testing::Test { protected: LLVMContext Context; FunctionAnalysisManager FAM; CGSCCAnalysisManager CGAM; ModuleAnalysisManager MAM; std::unique_ptr M; public: CGSCCPassManagerTest() : FAM(), CGAM(), MAM(), M(parseIR( // Define a module with the following call graph, where calls go // out the bottom of nodes and enter the top: // // f // |\ _ // | \ / | // g h1 | // | | | // | h2 | // | | | // | h3 | // | / \_/ // |/ // x // "define void @x() {\n" "entry:\n" " ret void\n" "}\n" "define void @h3() {\n" "entry:\n" " call void @h1()\n" " ret void\n" "}\n" "define void @h2() {\n" "entry:\n" " call void @h3()\n" " call void @x()\n" " ret void\n" "}\n" "define void @h1() {\n" "entry:\n" " call void @h2()\n" " ret void\n" "}\n" "define void @g() {\n" "entry:\n" " call void @g()\n" " call void @x()\n" " ret void\n" "}\n" "define void @f() {\n" "entry:\n" " call void @g()\n" " call void @h1()\n" " ret void\n" "}\n")) { FAM.registerPass([&] { return TargetLibraryAnalysis(); }); MAM.registerPass([&] { return LazyCallGraphAnalysis(); }); MAM.registerPass([&] { return FunctionAnalysisManagerModuleProxy(FAM); }); // Register required pass instrumentation analysis. MAM.registerPass([&] { return PassInstrumentationAnalysis(); }); CGAM.registerPass([&] { return PassInstrumentationAnalysis(); }); FAM.registerPass([&] { return PassInstrumentationAnalysis(); }); // Cross-register proxies. MAM.registerPass([&] { return CGSCCAnalysisManagerModuleProxy(CGAM); }); CGAM.registerPass([&] { return FunctionAnalysisManagerCGSCCProxy(); }); CGAM.registerPass([&] { return ModuleAnalysisManagerCGSCCProxy(MAM); }); FAM.registerPass([&] { return CGSCCAnalysisManagerFunctionProxy(CGAM); }); FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); }); } }; TEST_F(CGSCCPassManagerTest, Basic) { int FunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); int ImmutableFunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestImmutableFunctionAnalysis(ImmutableFunctionAnalysisRuns); }); int SCCAnalysisRuns = 0; CGAM.registerPass([&] { return TestSCCAnalysis(SCCAnalysisRuns); }); int ModuleAnalysisRuns = 0; MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); }); ModulePassManager MPM; MPM.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM1; FunctionPassManager FPM1; int FunctionPassRunCount1 = 0; FPM1.addPass(LambdaFunctionPass([&](Function &, FunctionAnalysisManager &) { ++FunctionPassRunCount1; return PreservedAnalyses::none(); })); CGPM1.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); int SCCPassRunCount1 = 0; int AnalyzedInstrCount1 = 0; int AnalyzedSCCFunctionCount1 = 0; int AnalyzedModuleFunctionCount1 = 0; CGPM1.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { ++SCCPassRunCount1; // Note: The proper way to get to a module pass from a CGSCC pass is // through the ModuleAnalysisManagerCGSCCProxy: // ``` // const auto &MAMProxy = // AM.getResult(C, CG); // ``` // However getting a stateful analysis is incorrect usage, and the call // to getCachedResult below asserts: // ``` // if (TestModuleAnalysis::Result *TMA = // MAMProxy.getCachedResult( // *C.begin()->getFunction().getParent())) // AnalyzedModuleFunctionCount1 += TMA->FunctionCount; // ``` // For the purposes of this unittest, use the above MAM directly. if (TestModuleAnalysis::Result *TMA = MAM.getCachedResult( *C.begin()->getFunction().getParent())) AnalyzedModuleFunctionCount1 += TMA->FunctionCount; FunctionAnalysisManager &FAM = AM.getResult(C, CG).getManager(); TestSCCAnalysis::Result &AR = AM.getResult(C, CG); AnalyzedSCCFunctionCount1 += AR.FunctionCount; for (LazyCallGraph::Node &N : C) { TestFunctionAnalysis::Result &FAR = FAM.getResult(N.getFunction()); AnalyzedInstrCount1 += FAR.InstructionCount; // Just ensure we get the immutable results. (void)FAM.getResult(N.getFunction()); } return PreservedAnalyses::all(); })); FunctionPassManager FPM2; int FunctionPassRunCount2 = 0; FPM2.addPass(LambdaFunctionPass([&](Function &, FunctionAnalysisManager &) { ++FunctionPassRunCount2; return PreservedAnalyses::none(); })); CGPM1.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); FunctionPassManager FPM3; int FunctionPassRunCount3 = 0; FPM3.addPass(LambdaFunctionPass([&](Function &, FunctionAnalysisManager &) { ++FunctionPassRunCount3; return PreservedAnalyses::none(); })); MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM3))); MPM.run(*M, MAM); EXPECT_EQ(4, SCCPassRunCount1); EXPECT_EQ(6, FunctionPassRunCount1); EXPECT_EQ(6, FunctionPassRunCount2); EXPECT_EQ(6, FunctionPassRunCount3); EXPECT_EQ(1, ModuleAnalysisRuns); EXPECT_EQ(4, SCCAnalysisRuns); EXPECT_EQ(6, FunctionAnalysisRuns); EXPECT_EQ(6, ImmutableFunctionAnalysisRuns); EXPECT_EQ(14, AnalyzedInstrCount1); EXPECT_EQ(6, AnalyzedSCCFunctionCount1); EXPECT_EQ(4 * 6, AnalyzedModuleFunctionCount1); } // Test that an SCC pass which fails to preserve a module analysis does in fact // invalidate that module analysis. TEST_F(CGSCCPassManagerTest, TestSCCPassInvalidatesModuleAnalysis) { int ModuleAnalysisRuns = 0; MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); }); ModulePassManager MPM; MPM.addPass(RequireAnalysisPass()); // The first CGSCC run we preserve everything and make sure that works and // the module analysis is available in the second CGSCC run from the one // required module pass above. CGSCCPassManager CGPM1; int CountFoundModuleAnalysis1 = 0; CGPM1.addPass(LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { const auto &MAMProxy = AM.getResult(C, CG); if (MAMProxy.cachedResultExists( *C.begin()->getFunction().getParent())) ++CountFoundModuleAnalysis1; return PreservedAnalyses::all(); })); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // The second CGSCC run checks that the module analysis got preserved the // previous time and in one SCC fails to preserve it. CGSCCPassManager CGPM2; int CountFoundModuleAnalysis2 = 0; CGPM2.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { const auto &MAMProxy = AM.getResult(C, CG); if (MAMProxy.cachedResultExists( *C.begin()->getFunction().getParent())) ++CountFoundModuleAnalysis2; // Only fail to preserve analyses on one SCC and make sure that gets // propagated. return C.getName() == "(g)" ? PreservedAnalyses::none() : PreservedAnalyses::all(); })); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); // The third CGSCC run should fail to find a cached module analysis as it // should have been invalidated by the above CGSCC run. CGSCCPassManager CGPM3; int CountFoundModuleAnalysis3 = 0; CGPM3.addPass(LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { const auto &MAMProxy = AM.getResult(C, CG); if (MAMProxy.cachedResultExists( *C.begin()->getFunction().getParent())) ++CountFoundModuleAnalysis3; return PreservedAnalyses::none(); })); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM3))); MPM.run(*M, MAM); EXPECT_EQ(1, ModuleAnalysisRuns); EXPECT_EQ(4, CountFoundModuleAnalysis1); EXPECT_EQ(4, CountFoundModuleAnalysis2); EXPECT_EQ(0, CountFoundModuleAnalysis3); } // Similar to the above, but test that this works for function passes embedded // *within* a CGSCC layer. TEST_F(CGSCCPassManagerTest, TestFunctionPassInsideCGSCCInvalidatesModuleAnalysis) { int ModuleAnalysisRuns = 0; MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); }); ModulePassManager MPM; MPM.addPass(RequireAnalysisPass()); // The first run we preserve everything and make sure that works and the // module analysis is available in the second run from the one required // module pass above. FunctionPassManager FPM1; // Start true and mark false if we ever failed to find a module analysis // because we expect this to succeed for each SCC. bool FoundModuleAnalysis1 = true; FPM1.addPass(LambdaFunctionPass([&](Function &F, FunctionAnalysisManager &AM) { const auto &MAMProxy = AM.getResult(F); if (!MAMProxy.cachedResultExists(*F.getParent())) FoundModuleAnalysis1 = false; return PreservedAnalyses::all(); })); CGSCCPassManager CGPM1; CGPM1.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // The second run checks that the module analysis got preserved the previous // time and in one function fails to preserve it. FunctionPassManager FPM2; // Again, start true and mark false if we ever failed to find a module analysis // because we expect this to succeed for each SCC. bool FoundModuleAnalysis2 = true; FPM2.addPass(LambdaFunctionPass([&](Function &F, FunctionAnalysisManager &AM) { const auto &MAMProxy = AM.getResult(F); if (!MAMProxy.cachedResultExists(*F.getParent())) FoundModuleAnalysis2 = false; // Only fail to preserve analyses on one SCC and make sure that gets // propagated. return F.getName() == "h2" ? PreservedAnalyses::none() : PreservedAnalyses::all(); })); CGSCCPassManager CGPM2; CGPM2.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); // The third run should fail to find a cached module analysis as it should // have been invalidated by the above run. FunctionPassManager FPM3; // Start false and mark true if we ever *succeeded* to find a module // analysis, as we expect this to fail for every function. bool FoundModuleAnalysis3 = false; FPM3.addPass(LambdaFunctionPass([&](Function &F, FunctionAnalysisManager &AM) { const auto &MAMProxy = AM.getResult(F); if (MAMProxy.cachedResultExists(*F.getParent())) FoundModuleAnalysis3 = true; return PreservedAnalyses::none(); })); CGSCCPassManager CGPM3; CGPM3.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM3))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM3))); MPM.run(*M, MAM); EXPECT_EQ(1, ModuleAnalysisRuns); EXPECT_TRUE(FoundModuleAnalysis1); EXPECT_TRUE(FoundModuleAnalysis2); EXPECT_FALSE(FoundModuleAnalysis3); } // Test that a Module pass which fails to preserve an SCC analysis in fact // invalidates that analysis. TEST_F(CGSCCPassManagerTest, TestModulePassInvalidatesSCCAnalysis) { int SCCAnalysisRuns = 0; CGAM.registerPass([&] { return TestSCCAnalysis(SCCAnalysisRuns); }); ModulePassManager MPM; // First force the analysis to be run. CGSCCPassManager CGPM1; CGPM1.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // Now run a module pass that preserves the LazyCallGraph and the proxy but // not the SCC analysis. MPM.addPass(LambdaModulePass([&](Module &M, ModuleAnalysisManager &) { PreservedAnalyses PA; PA.preserve(); PA.preserve(); PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again. This // will trigger re-running it. CGSCCPassManager CGPM2; CGPM2.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // Two runs and four SCCs. EXPECT_EQ(2 * 4, SCCAnalysisRuns); } // Check that marking the SCC analysis preserved is sufficient to avoid // invaliadtion. This should only run the analysis once for each SCC. TEST_F(CGSCCPassManagerTest, TestModulePassCanPreserveSCCAnalysis) { int SCCAnalysisRuns = 0; CGAM.registerPass([&] { return TestSCCAnalysis(SCCAnalysisRuns); }); ModulePassManager MPM; // First force the analysis to be run. CGSCCPassManager CGPM1; CGPM1.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // Now run a module pass that preserves each of the necessary components // (but not everything). MPM.addPass(LambdaModulePass([&](Module &M, ModuleAnalysisManager &) { PreservedAnalyses PA; PA.preserve(); PA.preserve(); PA.preserve(); PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again but find // it in the cache. CGSCCPassManager CGPM2; CGPM2.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // Four SCCs EXPECT_EQ(4, SCCAnalysisRuns); } // Check that even when the analysis is preserved, if the SCC information isn't // we still nuke things because the SCC keys could change. TEST_F(CGSCCPassManagerTest, TestModulePassInvalidatesSCCAnalysisOnCGChange) { int SCCAnalysisRuns = 0; CGAM.registerPass([&] { return TestSCCAnalysis(SCCAnalysisRuns); }); ModulePassManager MPM; // First force the analysis to be run. CGSCCPassManager CGPM1; CGPM1.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // Now run a module pass that preserves the analysis but not the call // graph or proxy. MPM.addPass(LambdaModulePass([&](Module &M, ModuleAnalysisManager &) { PreservedAnalyses PA; PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again. CGSCCPassManager CGPM2; CGPM2.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // Two runs and four SCCs. EXPECT_EQ(2 * 4, SCCAnalysisRuns); } // Test that an SCC pass which fails to preserve a Function analysis in fact // invalidates that analysis. TEST_F(CGSCCPassManagerTest, TestSCCPassInvalidatesFunctionAnalysis) { int FunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); // Create a very simple module with a single function and SCC to make testing // these issues much easier. std::unique_ptr M = parseIR("declare void @g()\n" "declare void @h()\n" "define void @f() {\n" "entry:\n" " call void @g()\n" " call void @h()\n" " ret void\n" "}\n"); CGSCCPassManager CGPM; // First force the analysis to be run. FunctionPassManager FPM1; FPM1.addPass(RequireAnalysisPass()); CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); // Now run a module pass that preserves the LazyCallGraph and proxy but not // the SCC analysis. CGPM.addPass(LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &, LazyCallGraph &, CGSCCUpdateResult &) { PreservedAnalyses PA; PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again. This // will trigger re-running it. FunctionPassManager FPM2; FPM2.addPass(RequireAnalysisPass()); CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); EXPECT_EQ(2, FunctionAnalysisRuns); } // Check that marking the SCC analysis preserved is sufficient. This should // only run the analysis once the SCC. TEST_F(CGSCCPassManagerTest, TestSCCPassCanPreserveFunctionAnalysis) { int FunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); // Create a very simple module with a single function and SCC to make testing // these issues much easier. std::unique_ptr M = parseIR("declare void @g()\n" "declare void @h()\n" "define void @f() {\n" "entry:\n" " call void @g()\n" " call void @h()\n" " ret void\n" "}\n"); CGSCCPassManager CGPM; // First force the analysis to be run. FunctionPassManager FPM1; FPM1.addPass(RequireAnalysisPass()); CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); // Now run a module pass that preserves each of the necessary components // (but // not everything). CGPM.addPass(LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &, LazyCallGraph &, CGSCCUpdateResult &) { PreservedAnalyses PA; PA.preserve(); PA.preserve(); PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again but find // it in the cache. FunctionPassManager FPM2; FPM2.addPass(RequireAnalysisPass()); CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); EXPECT_EQ(1, FunctionAnalysisRuns); } // Note that there is no test for invalidating the call graph or other // structure with an SCC pass because there is no mechanism to do that from // withinsuch a pass. Instead, such a pass has to directly update the call // graph structure. // Test that a madule pass invalidates function analyses when the CGSCC proxies // and pass manager. TEST_F(CGSCCPassManagerTest, TestModulePassInvalidatesFunctionAnalysisNestedInCGSCC) { MAM.registerPass([&] { return LazyCallGraphAnalysis(); }); int FunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); ModulePassManager MPM; // First force the analysis to be run. FunctionPassManager FPM1; FPM1.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM1; CGPM1.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // Now run a module pass that preserves the LazyCallGraph and proxies but not // the Function analysis. MPM.addPass(LambdaModulePass([&](Module &M, ModuleAnalysisManager &) { PreservedAnalyses PA; PA.preserve(); PA.preserve(); PA.preserve(); PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again. This // will trigger re-running it. FunctionPassManager FPM2; FPM2.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM2; CGPM2.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // Two runs and 6 functions. EXPECT_EQ(2 * 6, FunctionAnalysisRuns); } // Check that by marking the function pass and proxies as preserved, this // propagates all the way through. TEST_F(CGSCCPassManagerTest, TestModulePassCanPreserveFunctionAnalysisNestedInCGSCC) { MAM.registerPass([&] { return LazyCallGraphAnalysis(); }); int FunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); ModulePassManager MPM; // First force the analysis to be run. FunctionPassManager FPM1; FPM1.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM1; CGPM1.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // Now run a module pass that preserves the LazyCallGraph, the proxy, and // the Function analysis. MPM.addPass(LambdaModulePass([&](Module &M, ModuleAnalysisManager &) { PreservedAnalyses PA; PA.preserve(); PA.preserve(); PA.preserve(); PA.preserve(); PA.preserve(); return PA; })); // And now a second CGSCC run which requires the SCC analysis again. This // will trigger re-running it. FunctionPassManager FPM2; FPM2.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM2; CGPM2.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // One run and 6 functions. EXPECT_EQ(6, FunctionAnalysisRuns); } // Check that if the lazy call graph itself isn't preserved we still manage to // invalidate everything. TEST_F(CGSCCPassManagerTest, TestModulePassInvalidatesFunctionAnalysisNestedInCGSCCOnCGChange) { MAM.registerPass([&] { return LazyCallGraphAnalysis(); }); int FunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); ModulePassManager MPM; // First force the analysis to be run. FunctionPassManager FPM1; FPM1.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM1; CGPM1.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM1))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM1))); // Now run a module pass that preserves the LazyCallGraph but not the // Function analysis. MPM.addPass(LambdaModulePass([&](Module &M, ModuleAnalysisManager &) { PreservedAnalyses PA; return PA; })); // And now a second CGSCC run which requires the SCC analysis again. This // will trigger re-running it. FunctionPassManager FPM2; FPM2.addPass(RequireAnalysisPass()); CGSCCPassManager CGPM2; CGPM2.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // Two runs and 6 functions. EXPECT_EQ(2 * 6, FunctionAnalysisRuns); } /// A test CGSCC-level analysis pass which caches in its result another /// analysis pass and uses it to serve queries. This requires the result to /// invalidate itself when its dependency is invalidated. /// /// FIXME: Currently this doesn't also depend on a function analysis, and if it /// did we would fail to invalidate it correctly. struct TestIndirectSCCAnalysis : public AnalysisInfoMixin { struct Result { Result(TestSCCAnalysis::Result &SCCDep, TestModuleAnalysis::Result &MDep) : SCCDep(SCCDep), MDep(MDep) {} TestSCCAnalysis::Result &SCCDep; TestModuleAnalysis::Result &MDep; bool invalidate(LazyCallGraph::SCC &C, const PreservedAnalyses &PA, CGSCCAnalysisManager::Invalidator &Inv) { auto PAC = PA.getChecker(); return !(PAC.preserved() || PAC.preservedSet>()) || Inv.invalidate(C, PA); } }; TestIndirectSCCAnalysis(int &Runs, ModuleAnalysisManager &MAM) : Runs(Runs), MAM(MAM) {} /// Run the analysis pass over the function and return a result. Result run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG) { ++Runs; auto &SCCDep = AM.getResult(C, CG); auto &ModuleProxy = AM.getResult(C, CG); // For the test, we insist that the module analysis starts off in the // cache. Getting a cached result that isn't stateless triggers an assert. // auto &MDep = *ModuleProxy.getCachedResult( // *C.begin()->getFunction().getParent()); // Use MAM, for the purposes of this unittest. auto &MDep = *MAM.getCachedResult( *C.begin()->getFunction().getParent()); // Register the dependency as module analysis dependencies have to be // pre-registered on the proxy. ModuleProxy.registerOuterAnalysisInvalidation(); return Result(SCCDep, MDep); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; ModuleAnalysisManager &MAM; }; AnalysisKey TestIndirectSCCAnalysis::Key; /// A test analysis pass which caches in its result the result from the above /// indirect analysis pass. /// /// This allows us to ensure that whenever an analysis pass is invalidated due /// to dependencies (especially dependencies across IR units that trigger /// asynchronous invalidation) we correctly detect that this may in turn cause /// other analysis to be invalidated. struct TestDoublyIndirectSCCAnalysis : public AnalysisInfoMixin { struct Result { Result(TestIndirectSCCAnalysis::Result &IDep) : IDep(IDep) {} TestIndirectSCCAnalysis::Result &IDep; bool invalidate(LazyCallGraph::SCC &C, const PreservedAnalyses &PA, CGSCCAnalysisManager::Invalidator &Inv) { auto PAC = PA.getChecker(); return !(PAC.preserved() || PAC.preservedSet>()) || Inv.invalidate(C, PA); } }; TestDoublyIndirectSCCAnalysis(int &Runs) : Runs(Runs) {} /// Run the analysis pass over the function and return a result. Result run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG) { ++Runs; auto &IDep = AM.getResult(C, CG); return Result(IDep); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; }; AnalysisKey TestDoublyIndirectSCCAnalysis::Key; /// A test analysis pass which caches results from three different IR unit /// layers and requires intermediate layers to correctly propagate the entire /// distance. struct TestIndirectFunctionAnalysis : public AnalysisInfoMixin { struct Result { Result(TestFunctionAnalysis::Result &FDep, TestModuleAnalysis::Result &MDep, TestSCCAnalysis::Result &SCCDep) : FDep(FDep), MDep(MDep), SCCDep(SCCDep) {} TestFunctionAnalysis::Result &FDep; TestModuleAnalysis::Result &MDep; TestSCCAnalysis::Result &SCCDep; bool invalidate(Function &F, const PreservedAnalyses &PA, FunctionAnalysisManager::Invalidator &Inv) { auto PAC = PA.getChecker(); return !(PAC.preserved() || PAC.preservedSet>()) || Inv.invalidate(F, PA); } }; TestIndirectFunctionAnalysis(int &Runs, ModuleAnalysisManager &MAM, CGSCCAnalysisManager &CGAM) : Runs(Runs), MAM(MAM), CGAM(CGAM) {} /// Run the analysis pass over the function and return a result. Result run(Function &F, FunctionAnalysisManager &AM) { ++Runs; auto &FDep = AM.getResult(F); auto &ModuleProxy = AM.getResult(F); // For the test, we insist that the module analysis starts off in the // cache. Getting a cached result that isn't stateless triggers an assert. // Use MAM, for the purposes of this unittest. auto &MDep = *MAM.getCachedResult(*F.getParent()); // Register the dependency as module analysis dependencies have to be // pre-registered on the proxy. ModuleProxy.registerOuterAnalysisInvalidation< TestModuleAnalysis, TestIndirectFunctionAnalysis>(); // For the test we assume this is run inside a CGSCC pass manager. // Use MAM, for the purposes of this unittest. const LazyCallGraph &CG = *MAM.getCachedResult(*F.getParent()); auto &CGSCCProxy = AM.getResult(F); // For the test, we insist that the CGSCC analysis starts off in the cache. // Getting a cached result that isn't stateless triggers an assert. // Use CGAM, for the purposes of this unittest. auto &SCCDep = *CGAM.getCachedResult(*CG.lookupSCC(*CG.lookup(F))); // Register the dependency as CGSCC analysis dependencies have to be // pre-registered on the proxy. CGSCCProxy.registerOuterAnalysisInvalidation< TestSCCAnalysis, TestIndirectFunctionAnalysis>(); return Result(FDep, MDep, SCCDep); } private: friend AnalysisInfoMixin; static AnalysisKey Key; int &Runs; ModuleAnalysisManager &MAM; CGSCCAnalysisManager &CGAM; }; AnalysisKey TestIndirectFunctionAnalysis::Key; TEST_F(CGSCCPassManagerTest, TestIndirectAnalysisInvalidation) { int ModuleAnalysisRuns = 0; MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); }); int SCCAnalysisRuns = 0, IndirectSCCAnalysisRuns = 0, DoublyIndirectSCCAnalysisRuns = 0; CGAM.registerPass([&] { return TestSCCAnalysis(SCCAnalysisRuns); }); CGAM.registerPass( [&] { return TestIndirectSCCAnalysis(IndirectSCCAnalysisRuns, MAM); }); CGAM.registerPass([&] { return TestDoublyIndirectSCCAnalysis(DoublyIndirectSCCAnalysisRuns); }); int FunctionAnalysisRuns = 0, IndirectFunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); FAM.registerPass([&] { return TestIndirectFunctionAnalysis(IndirectFunctionAnalysisRuns, MAM, CGAM); }); ModulePassManager MPM; int FunctionCount = 0; CGSCCPassManager CGPM; // First just use the analysis to get the function count and preserve // everything. CGPM.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &) { auto &DoublyIndirectResult = AM.getResult(C, CG); auto &IndirectResult = DoublyIndirectResult.IDep; FunctionCount += IndirectResult.SCCDep.FunctionCount; return PreservedAnalyses::all(); })); CGPM.addPass(createCGSCCToFunctionPassAdaptor( RequireAnalysisPass())); // Next, invalidate // - both analyses for the (f) and (x) SCCs, // - just the underlying (indirect) analysis for (g) SCC, and // - just the direct analysis for (h1,h2,h3) SCC. CGPM.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &) { auto &DoublyIndirectResult = AM.getResult(C, CG); auto &IndirectResult = DoublyIndirectResult.IDep; FunctionCount += IndirectResult.SCCDep.FunctionCount; auto PA = PreservedAnalyses::none(); PA.preserve(); PA.preserveSet>(); if (C.getName() == "(g)") PA.preserve(); else if (C.getName() == "(h3, h1, h2)") PA.preserve(); return PA; })); // Finally, use the analysis again on each SCC (and function), forcing // re-computation for all of them. CGPM.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &) { auto &DoublyIndirectResult = AM.getResult(C, CG); auto &IndirectResult = DoublyIndirectResult.IDep; FunctionCount += IndirectResult.SCCDep.FunctionCount; return PreservedAnalyses::all(); })); CGPM.addPass(createCGSCCToFunctionPassAdaptor( RequireAnalysisPass())); // Create a second CGSCC pass manager. This will cause the module-level // invalidation to occur, which will force yet another invalidation of the // indirect SCC-level analysis as the module analysis it depends on gets // invalidated. CGSCCPassManager CGPM2; CGPM2.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &) { auto &DoublyIndirectResult = AM.getResult(C, CG); auto &IndirectResult = DoublyIndirectResult.IDep; FunctionCount += IndirectResult.SCCDep.FunctionCount; return PreservedAnalyses::all(); })); CGPM2.addPass(createCGSCCToFunctionPassAdaptor( RequireAnalysisPass())); // Add a requires pass to populate the module analysis and then our CGSCC // pass pipeline. MPM.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); // Now require the module analysis again (it will have been invalidated once) // and then use it again from our second CGSCC pipeline.. MPM.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.run(*M, MAM); // There are generally two possible runs for each of the four SCCs. But // for one SCC, we only invalidate the indirect analysis so the base one // only gets run seven times. EXPECT_EQ(7, SCCAnalysisRuns); // The module analysis pass should be run twice here. EXPECT_EQ(2, ModuleAnalysisRuns); // The indirect analysis is invalidated (either directly or indirectly) three // times for each of four SCCs. EXPECT_EQ(3 * 4, IndirectSCCAnalysisRuns); EXPECT_EQ(3 * 4, DoublyIndirectSCCAnalysisRuns); // We run the indirect function analysis once per function the first time. // Then we re-run it for every SCC but "(g)". Then we re-run it for every // function again. EXPECT_EQ(6 + 5 + 6, IndirectFunctionAnalysisRuns); // Four passes count each of six functions once (via SCCs). EXPECT_EQ(4 * 6, FunctionCount); } TEST_F(CGSCCPassManagerTest, TestAnalysisInvalidationCGSCCUpdate) { int ModuleAnalysisRuns = 0; MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); }); int SCCAnalysisRuns = 0, IndirectSCCAnalysisRuns = 0, DoublyIndirectSCCAnalysisRuns = 0; CGAM.registerPass([&] { return TestSCCAnalysis(SCCAnalysisRuns); }); CGAM.registerPass( [&] { return TestIndirectSCCAnalysis(IndirectSCCAnalysisRuns, MAM); }); CGAM.registerPass([&] { return TestDoublyIndirectSCCAnalysis(DoublyIndirectSCCAnalysisRuns); }); int FunctionAnalysisRuns = 0, IndirectFunctionAnalysisRuns = 0; FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); }); FAM.registerPass([&] { return TestIndirectFunctionAnalysis(IndirectFunctionAnalysisRuns, MAM, CGAM); }); ModulePassManager MPM; CGSCCPassManager CGPM; // First just use the analysis to get the function count and preserve // everything. using RequireTestIndirectFunctionAnalysisPass = RequireAnalysisPass; using RequireTestDoublyIndirectSCCAnalysisPass = RequireAnalysisPass; CGPM.addPass(RequireTestDoublyIndirectSCCAnalysisPass()); CGPM.addPass(createCGSCCToFunctionPassAdaptor( RequireTestIndirectFunctionAnalysisPass())); // Next, we inject an SCC pass that invalidates everything for the `(h3, h1, // h2)` SCC but also deletes the call edge from `h2` to `h3` and updates the // CG. This should successfully invalidate (and force to be re-run) all the // analyses for that SCC and for the functions. CGPM.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { (void)AM.getResult(C, CG); if (C.getName() != "(h3, h1, h2)") return PreservedAnalyses::all(); // Build the preserved set. auto PA = PreservedAnalyses::none(); PA.preserve(); PA.preserve(); PA.preserve(); // Delete the call from `h2` to `h3`. auto &H2N = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "h2"; }); auto &H2F = H2N.getFunction(); auto &H3F = *cast(H2F.begin()->begin())->getCalledFunction(); assert(H3F.getName() == "h3" && "Wrong called function!"); H2F.begin()->begin()->eraseFromParent(); // Insert a bitcast of `h3` so that we retain a ref edge to it. (void)CastInst::CreatePointerCast(&H3F, Type::getInt8PtrTy(H2F.getContext()), "dummy", &*H2F.begin()->begin()); // Now update the call graph. auto &NewC = updateCGAndAnalysisManagerForFunctionPass(CG, C, H2N, AM, UR, FAM); assert(&NewC != &C && "Should get a new SCC due to update!"); (void)&NewC; return PA; })); // Now use the analysis again on each SCC and function, forcing // re-computation for all of them. CGPM.addPass(RequireTestDoublyIndirectSCCAnalysisPass()); CGPM.addPass(createCGSCCToFunctionPassAdaptor( RequireTestIndirectFunctionAnalysisPass())); // Create another CGSCC pipeline that requires all the analyses again. CGSCCPassManager CGPM2; CGPM2.addPass(RequireTestDoublyIndirectSCCAnalysisPass()); CGPM2.addPass(createCGSCCToFunctionPassAdaptor( RequireTestIndirectFunctionAnalysisPass())); // Next we inject an SCC pass that finds the `(h2)` SCC, adds a call to `h3` // back to `h2`, and then invalidates everything for what will then be the // `(h3, h1, h2)` SCC again. CGSCCPassManager CGPM3; CGPM3.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { (void)AM.getResult(C, CG); if (C.getName() != "(h2)") return PreservedAnalyses::all(); // Build the preserved set. auto PA = PreservedAnalyses::none(); PA.preserve(); PA.preserve(); PA.preserve(); // Delete the bitcast of `h3` that we added earlier. auto &H2N = *C.begin(); auto &H2F = H2N.getFunction(); auto &H3F = *cast(cast(H2F.begin()->begin())->getOperand(0)); assert(H3F.getName() == "h3" && "Wrong called function!"); H2F.begin()->begin()->eraseFromParent(); // And insert a call to `h3`. (void)CallInst::Create(&H3F, {}, "", &*H2F.begin()->begin()); // Now update the call graph. auto &NewC = updateCGAndAnalysisManagerForFunctionPass(CG, C, H2N, AM, UR, FAM); assert(&NewC != &C && "Should get a new SCC due to update!"); (void)&NewC; return PA; })); // Now use the analysis again on each SCC and function, forcing // re-computation for all of them. CGPM3.addPass(RequireTestDoublyIndirectSCCAnalysisPass()); CGPM3.addPass(createCGSCCToFunctionPassAdaptor( RequireTestIndirectFunctionAnalysisPass())); // Create a second CGSCC pass manager. This will cause the module-level // invalidation to occur, which will force yet another invalidation of the // indirect SCC-level analysis as the module analysis it depends on gets // invalidated. CGSCCPassManager CGPM4; CGPM4.addPass(RequireTestDoublyIndirectSCCAnalysisPass()); CGPM4.addPass(createCGSCCToFunctionPassAdaptor( RequireTestIndirectFunctionAnalysisPass())); // Add a requires pass to populate the module analysis and then one of our // CGSCC pipelines. Repeat for all four CGSCC pipelines. MPM.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2))); MPM.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM3))); MPM.addPass(RequireAnalysisPass()); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM4))); MPM.run(*M, MAM); // We run over four SCCs the first time. But then we split an SCC into three. // And then we merge those three back into one. However, this also // invalidates all three SCCs further down in the PO walk. EXPECT_EQ(4 + 3 + 3, SCCAnalysisRuns); // The module analysis pass should be run three times. EXPECT_EQ(3, ModuleAnalysisRuns); // We run over four SCCs the first time. Then over the two new ones. Then the // entire module is invalidated causing a full run over all seven. Then we // fold three SCCs back to one, re-compute for it and the two SCCs above it // in the graph, and then run over the whole module again. EXPECT_EQ(4 + 2 + 7 + 3 + 4, IndirectSCCAnalysisRuns); EXPECT_EQ(4 + 2 + 7 + 3 + 4, DoublyIndirectSCCAnalysisRuns); // First we run over all six functions. Then we re-run it over three when we // split their SCCs. Then we re-run over the whole module. Then we re-run // over three functions merged back into a single SCC, then those three // functions again, the two functions in SCCs above it in the graph, and then // over the whole module again. EXPECT_EQ(6 + 3 + 6 + 3 + 2 + 6, FunctionAnalysisRuns); // Re run the function analysis over the entire module, and then re-run it // over the `(h3, h1, h2)` SCC due to invalidation. Then we re-run it over // the entire module, then the three functions merged back into a single SCC, // those three functions again, then the two functions in SCCs above it in // the graph, and then over the whole module. EXPECT_EQ(6 + 3 + 6 + 3 + 2 + 6, IndirectFunctionAnalysisRuns); } // The (negative) tests below check for assertions so we only run them if NDEBUG // is not defined. #ifndef NDEBUG struct LambdaSCCPassNoPreserve : public PassInfoMixin { template LambdaSCCPassNoPreserve(T &&Arg) : Func(std::forward(Arg)) {} PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { Func(C, AM, CG, UR); PreservedAnalyses PA; // We update the core CGSCC data structures and so can preserve the proxy to // the function analysis manager. PA.preserve(); return PA; } std::function Func; }; TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses0) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(h3, h1, h2)") return; auto &FAM = AM.getResult(C, CG).getManager(); Function *FnX = M->getFunction("x"); Function *FnH1 = M->getFunction("h1"); Function *FnH2 = M->getFunction("h2"); Function *FnH3 = M->getFunction("h3"); ASSERT_NE(FnX, nullptr); ASSERT_NE(FnH1, nullptr); ASSERT_NE(FnH2, nullptr); ASSERT_NE(FnH3, nullptr); // And insert a call to `h1`, `h2`, and `h3`. Instruction *IP = &FnH2->getEntryBlock().front(); (void)CallInst::Create(FnH1, {}, "", IP); (void)CallInst::Create(FnH2, {}, "", IP); (void)CallInst::Create(FnH3, {}, "", IP); auto &H2N = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "h2"; }); ASSERT_NO_FATAL_FAILURE( updateCGAndAnalysisManagerForCGSCCPass(CG, C, H2N, AM, UR, FAM)); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses1) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(h3, h1, h2)") return; auto &FAM = AM.getResult(C, CG).getManager(); Function *FnX = M->getFunction("x"); Function *FnH1 = M->getFunction("h1"); Function *FnH2 = M->getFunction("h2"); Function *FnH3 = M->getFunction("h3"); ASSERT_NE(FnX, nullptr); ASSERT_NE(FnH1, nullptr); ASSERT_NE(FnH2, nullptr); ASSERT_NE(FnH3, nullptr); // And insert a call to `h1`, `h2`, and `h3`. Instruction *IP = &FnH2->getEntryBlock().front(); (void)CallInst::Create(FnH1, {}, "", IP); (void)CallInst::Create(FnH2, {}, "", IP); (void)CallInst::Create(FnH3, {}, "", IP); auto &H2N = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "h2"; }); ASSERT_DEATH( updateCGAndAnalysisManagerForFunctionPass(CG, C, H2N, AM, UR, FAM), "Any new calls should be modeled as"); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses2) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; auto &FAM = AM.getResult(C, CG).getManager(); Function *FnF = M->getFunction("f"); Function *FnH2 = M->getFunction("h2"); ASSERT_NE(FnF, nullptr); ASSERT_NE(FnH2, nullptr); // And insert a call to `h2` Instruction *IP = &FnF->getEntryBlock().front(); (void)CallInst::Create(FnH2, {}, "", IP); auto &FN = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "f"; }); ASSERT_NO_FATAL_FAILURE( updateCGAndAnalysisManagerForCGSCCPass(CG, C, FN, AM, UR, FAM)); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses3) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; auto &FAM = AM.getResult(C, CG).getManager(); Function *FnF = M->getFunction("f"); Function *FnH2 = M->getFunction("h2"); ASSERT_NE(FnF, nullptr); ASSERT_NE(FnH2, nullptr); // And insert a call to `h2` Instruction *IP = &FnF->getEntryBlock().front(); (void)CallInst::Create(FnH2, {}, "", IP); auto &FN = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "f"; }); ASSERT_DEATH( updateCGAndAnalysisManagerForFunctionPass(CG, C, FN, AM, UR, FAM), "Any new calls should be modeled as"); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses4) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; auto &FAM = AM.getResult(C, CG).getManager(); Function *FnF = M->getFunction("f"); Function *FnewF = Function::Create(FnF->getFunctionType(), FnF->getLinkage(), "newF", *M); BasicBlock *BB = BasicBlock::Create(FnewF->getContext(), "", FnewF); ReturnInst::Create(FnewF->getContext(), BB); // And insert a call to `newF` Instruction *IP = &FnF->getEntryBlock().front(); (void)CallInst::Create(FnewF, {}, "", IP); // Use the CallGraphUpdater to update the call graph for the new // function. CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); CGU.registerOutlinedFunction(*FnF, *FnewF); auto &FN = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "f"; }); ASSERT_NO_FATAL_FAILURE( updateCGAndAnalysisManagerForCGSCCPass(CG, C, FN, AM, UR, FAM)); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses5) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; auto &FAM = AM.getResult(C, CG).getManager(); Function *FnF = M->getFunction("f"); Function *FnewF = Function::Create(FnF->getFunctionType(), FnF->getLinkage(), "newF", *M); BasicBlock *BB = BasicBlock::Create(FnewF->getContext(), "", FnewF); ReturnInst::Create(FnewF->getContext(), BB); // Use the CallGraphUpdater to update the call graph for the new // function. CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); // And insert a call to `newF` Instruction *IP = &FnF->getEntryBlock().front(); (void)CallInst::Create(FnewF, {}, "", IP); auto &FN = *llvm::find_if( C, [](LazyCallGraph::Node &N) { return N.getName() == "f"; }); ASSERT_DEATH(updateCGAndAnalysisManagerForCGSCCPass(CG, C, FN, AM, UR, FAM), "should already have an associated node"); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses6) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(h3, h1, h2)") return; Function *FnX = M->getFunction("x"); Function *FnH1 = M->getFunction("h1"); Function *FnH2 = M->getFunction("h2"); Function *FnH3 = M->getFunction("h3"); ASSERT_NE(FnX, nullptr); ASSERT_NE(FnH1, nullptr); ASSERT_NE(FnH2, nullptr); ASSERT_NE(FnH3, nullptr); // And insert a call to `h1`, `h2`, and `h3`. Instruction *IP = &FnH2->getEntryBlock().front(); (void)CallInst::Create(FnH1, {}, "", IP); (void)CallInst::Create(FnH2, {}, "", IP); (void)CallInst::Create(FnH3, {}, "", IP); // Use the CallGraphUpdater to update the call graph for the new // function. CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); ASSERT_NO_FATAL_FAILURE(CGU.reanalyzeFunction(*FnH2)); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses7) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; Function *FnF = M->getFunction("f"); Function *FnH2 = M->getFunction("h2"); ASSERT_NE(FnF, nullptr); ASSERT_NE(FnH2, nullptr); // And insert a call to `h2` Instruction *IP = &FnF->getEntryBlock().front(); (void)CallInst::Create(FnH2, {}, "", IP); // Use the CallGraphUpdater to update the call graph for the new // function. CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); ASSERT_NO_FATAL_FAILURE(CGU.reanalyzeFunction(*FnF)); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses8) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; Function *FnF = M->getFunction("f"); Function *FnewF = Function::Create(FnF->getFunctionType(), FnF->getLinkage(), "newF", *M); BasicBlock *BB = BasicBlock::Create(FnewF->getContext(), "", FnewF); auto *RI = ReturnInst::Create(FnewF->getContext(), BB); while (FnF->getEntryBlock().size() > 1) FnF->getEntryBlock().front().moveBefore(RI); ASSERT_NE(FnF, nullptr); // Create an unsused constant that is referencing the old (=replaced) // function. ConstantExpr::getBitCast(FnF, Type::getInt8PtrTy(FnF->getContext())); // Use the CallGraphUpdater to update the call graph. CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); ASSERT_NO_FATAL_FAILURE(CGU.replaceFunctionWith(*FnF, *FnewF)); ASSERT_TRUE(FnF->isDeclaration()); ASSERT_EQ(FnF->getNumUses(), 0U); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses9) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(f)") return; Function *FnF = M->getFunction("f"); // Use the CallGraphUpdater to update the call graph. { CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); ASSERT_NO_FATAL_FAILURE(CGU.removeFunction(*FnF)); ASSERT_EQ(M->getFunctionList().size(), 6U); } ASSERT_EQ(M->getFunctionList().size(), 5U); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses10) { CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (C.getName() != "(h3, h1, h2)") return; Function *FnX = M->getFunction("x"); Function *FnH1 = M->getFunction("h1"); Function *FnH2 = M->getFunction("h2"); Function *FnH3 = M->getFunction("h3"); ASSERT_NE(FnX, nullptr); ASSERT_NE(FnH1, nullptr); ASSERT_NE(FnH2, nullptr); ASSERT_NE(FnH3, nullptr); // And insert a call to `h1`, and `h3`. Instruction *IP = &FnH1->getEntryBlock().front(); (void)CallInst::Create(FnH1, {}, "", IP); (void)CallInst::Create(FnH3, {}, "", IP); // Remove the `h2` call. ASSERT_TRUE(isa(IP)); ASSERT_EQ(cast(IP)->getCalledFunction(), FnH2); IP->eraseFromParent(); // Use the CallGraphUpdater to update the call graph. CallGraphUpdater CGU; CGU.initialize(CG, C, AM, UR); ASSERT_NO_FATAL_FAILURE(CGU.reanalyzeFunction(*FnH1)); ASSERT_NO_FATAL_FAILURE(CGU.removeFunction(*FnH2)); })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); } // Returns a vector containing the SCC's nodes. Useful for not iterating over an // SCC while mutating it. static SmallVector SCCNodes(LazyCallGraph::SCC &C) { SmallVector Nodes; for (auto &N : C) Nodes.push_back(&N); return Nodes; } // Start with call recursive f, create f -> g and ref recursive f. TEST_F(CGSCCPassManagerTest, TestInsertionOfNewFunctions1) { std::unique_ptr M = parseIR("define void @f() {\n" "entry:\n" " call void @f()\n" " ret void\n" "}\n"); bool Ran = false; CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve( [&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (Ran) return; auto &FAM = AM.getResult(C, CG).getManager(); for (LazyCallGraph::Node *N : SCCNodes(C)) { Function &F = N->getFunction(); if (F.getName() != "f") continue; // Create a new function 'g'. auto *G = Function::Create(F.getFunctionType(), F.getLinkage(), F.getAddressSpace(), "g", F.getParent()); auto *GBB = BasicBlock::Create(F.getParent()->getContext(), "entry", G); (void)ReturnInst::Create(G->getContext(), GBB); // Instruct the LazyCallGraph to create a new node for 'g', as the // single node in a new SCC, into the call graph. As a result // the call graph is composed of a single RefSCC with two SCCs: // [(f), (g)]. // "Demote" the 'f -> f' call edge to a ref edge. // 1. Erase the call edge from 'f' to 'f'. F.getEntryBlock().front().eraseFromParent(); // 2. Insert a ref edge from 'f' to 'f'. (void)CastInst::CreatePointerCast( &F, Type::getInt8PtrTy(F.getContext()), "f.ref", &F.getEntryBlock().front()); // 3. Insert a ref edge from 'f' to 'g'. (void)CastInst::CreatePointerCast( G, Type::getInt8PtrTy(F.getContext()), "g.ref", &F.getEntryBlock().front()); CG.addSplitFunction(F, *G); ASSERT_FALSE(verifyModule(*F.getParent(), &errs())); ASSERT_NO_FATAL_FAILURE( updateCGAndAnalysisManagerForCGSCCPass(CG, C, *N, AM, UR, FAM)) << "Updating the call graph with a demoted, self-referential " "call edge 'f -> f', and a newly inserted ref edge 'f -> g', " "caused a fatal failure"; Ran = true; } })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); ASSERT_TRUE(Ran); } // Start with f, end with f -> g1, f -> g2, and f -ref-> (h1 <-ref-> h2). TEST_F(CGSCCPassManagerTest, TestInsertionOfNewFunctions2) { std::unique_ptr M = parseIR("define void @f() {\n" "entry:\n" " ret void\n" "}\n"); bool Ran = false; CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (Ran) return; auto &FAM = AM.getResult(C, CG).getManager(); for (LazyCallGraph::Node *N : SCCNodes(C)) { Function &F = N->getFunction(); if (F.getName() != "f") continue; // Create g1 and g2. auto *G1 = Function::Create(F.getFunctionType(), F.getLinkage(), F.getAddressSpace(), "g1", F.getParent()); auto *G2 = Function::Create(F.getFunctionType(), F.getLinkage(), F.getAddressSpace(), "g2", F.getParent()); BasicBlock *G1BB = BasicBlock::Create(F.getParent()->getContext(), "entry", G1); BasicBlock *G2BB = BasicBlock::Create(F.getParent()->getContext(), "entry", G2); (void)ReturnInst::Create(G1->getContext(), G1BB); (void)ReturnInst::Create(G2->getContext(), G2BB); // Add 'f -> g1' call edge. (void)CallInst::Create(G1, {}, "", &F.getEntryBlock().front()); // Add 'f -> g2' call edge. (void)CallInst::Create(G2, {}, "", &F.getEntryBlock().front()); CG.addSplitFunction(F, *G1); CG.addSplitFunction(F, *G2); // Create mutually recursive functions (ref only) 'h1' and 'h2'. auto *H1 = Function::Create(F.getFunctionType(), F.getLinkage(), F.getAddressSpace(), "h1", F.getParent()); auto *H2 = Function::Create(F.getFunctionType(), F.getLinkage(), F.getAddressSpace(), "h2", F.getParent()); BasicBlock *H1BB = BasicBlock::Create(F.getParent()->getContext(), "entry", H1); BasicBlock *H2BB = BasicBlock::Create(F.getParent()->getContext(), "entry", H2); (void)CastInst::CreatePointerCast(H2, Type::getInt8PtrTy(F.getContext()), "h2.ref", H1BB); (void)ReturnInst::Create(H1->getContext(), H1BB); (void)CastInst::CreatePointerCast(H1, Type::getInt8PtrTy(F.getContext()), "h1.ref", H2BB); (void)ReturnInst::Create(H2->getContext(), H2BB); // Add 'f -> h1' ref edge. (void)CastInst::CreatePointerCast(H1, Type::getInt8PtrTy(F.getContext()), "h1.ref", &F.getEntryBlock().front()); // Add 'f -> h2' ref edge. (void)CastInst::CreatePointerCast(H2, Type::getInt8PtrTy(F.getContext()), "h2.ref", &F.getEntryBlock().front()); CG.addSplitRefRecursiveFunctions(F, SmallVector({H1, H2})); ASSERT_FALSE(verifyModule(*F.getParent(), &errs())); ASSERT_NO_FATAL_FAILURE( updateCGAndAnalysisManagerForCGSCCPass(CG, C, *N, AM, UR, FAM)) << "Updating the call graph with mutually recursive g1 <-> g2, h1 " "<-> h2 caused a fatal failure"; Ran = true; } })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); ASSERT_TRUE(Ran); } TEST_F(CGSCCPassManagerTest, TestInsertionOfNewNonTrivialCallEdge) { std::unique_ptr M = parseIR("define void @f1() {\n" "entry:\n" " %a = bitcast void ()* @f4 to i8*\n" " %b = bitcast void ()* @f2 to i8*\n" " ret void\n" "}\n" "define void @f2() {\n" "entry:\n" " %a = bitcast void ()* @f1 to i8*\n" " %b = bitcast void ()* @f3 to i8*\n" " ret void\n" "}\n" "define void @f3() {\n" "entry:\n" " %a = bitcast void ()* @f2 to i8*\n" " %b = bitcast void ()* @f4 to i8*\n" " ret void\n" "}\n" "define void @f4() {\n" "entry:\n" " %a = bitcast void ()* @f3 to i8*\n" " %b = bitcast void ()* @f1 to i8*\n" " ret void\n" "}\n"); bool Ran = false; CGSCCPassManager CGPM; CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { if (Ran) return; auto &FAM = AM.getResult(C, CG).getManager(); for (LazyCallGraph::Node *N : SCCNodes(C)) { Function &F = N->getFunction(); if (F.getName() != "f1") continue; Function *F3 = F.getParent()->getFunction("f3"); ASSERT_TRUE(F3 != nullptr); // Create call from f1 to f3. (void)CallInst::Create(F3, {}, "", F.getEntryBlock().getTerminator()); ASSERT_NO_FATAL_FAILURE( updateCGAndAnalysisManagerForCGSCCPass(CG, C, *N, AM, UR, FAM)) << "Updating the call graph with mutually recursive g1 <-> g2, h1 " "<-> h2 caused a fatal failure"; Ran = true; } })); ModulePassManager MPM; MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); ASSERT_TRUE(Ran); } TEST_F(CGSCCPassManagerTest, TestFunctionPassesAreQueriedForInvalidation) { std::unique_ptr M = parseIR("define void @f() { ret void }"); CGSCCPassManager CGPM; bool SCCCalled = false; FunctionPassManager FPM; int ImmRuns = 0; FAM.registerPass([&] { return TestImmutableFunctionAnalysis(ImmRuns); }); FPM.addPass(RequireAnalysisPass()); CGPM.addPass( LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG, CGSCCUpdateResult &UR) { SCCCalled = true; return PreservedAnalyses::none(); })); CGPM.addPass(createCGSCCToFunctionPassAdaptor( RequireAnalysisPass())); ModulePassManager MPM; MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM))); MPM.run(*M, MAM); ASSERT_EQ(ImmRuns, 1); ASSERT_TRUE(SCCCalled); } #endif } // namespace