forked from OSchip/llvm-project
1771 lines
69 KiB
C++
1771 lines
69 KiB
C++
//===- 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/Support/SourceMgr.h"
|
|
#include "llvm/Transforms/Utils/CallGraphUpdater.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace llvm;
|
|
|
|
namespace {
|
|
|
|
class TestModuleAnalysis : public AnalysisInfoMixin<TestModuleAnalysis> {
|
|
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<TestModuleAnalysis>();
|
|
return !(PAC.preserved() || PAC.preservedSet<AllAnalysesOn<Module>>());
|
|
}
|
|
};
|
|
|
|
TestModuleAnalysis(int &Runs) : Runs(Runs) {}
|
|
|
|
Result run(Module &M, ModuleAnalysisManager &AM) {
|
|
++Runs;
|
|
return Result(M.size());
|
|
}
|
|
|
|
private:
|
|
friend AnalysisInfoMixin<TestModuleAnalysis>;
|
|
static AnalysisKey Key;
|
|
|
|
int &Runs;
|
|
};
|
|
|
|
AnalysisKey TestModuleAnalysis::Key;
|
|
|
|
class TestSCCAnalysis : public AnalysisInfoMixin<TestSCCAnalysis> {
|
|
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<TestSCCAnalysis>();
|
|
return !(PAC.preserved() ||
|
|
PAC.preservedSet<AllAnalysesOn<LazyCallGraph::SCC>>());
|
|
}
|
|
};
|
|
|
|
TestSCCAnalysis(int &Runs) : Runs(Runs) {}
|
|
|
|
Result run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &) {
|
|
++Runs;
|
|
return Result(C.size());
|
|
}
|
|
|
|
private:
|
|
friend AnalysisInfoMixin<TestSCCAnalysis>;
|
|
static AnalysisKey Key;
|
|
|
|
int &Runs;
|
|
};
|
|
|
|
AnalysisKey TestSCCAnalysis::Key;
|
|
|
|
class TestFunctionAnalysis : public AnalysisInfoMixin<TestFunctionAnalysis> {
|
|
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<TestFunctionAnalysis>();
|
|
return !(PAC.preserved() || PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
}
|
|
};
|
|
|
|
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<TestFunctionAnalysis>;
|
|
static AnalysisKey Key;
|
|
|
|
int &Runs;
|
|
};
|
|
|
|
AnalysisKey TestFunctionAnalysis::Key;
|
|
|
|
class TestImmutableFunctionAnalysis
|
|
: public AnalysisInfoMixin<TestImmutableFunctionAnalysis> {
|
|
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<TestImmutableFunctionAnalysis>;
|
|
static AnalysisKey Key;
|
|
|
|
int &Runs;
|
|
};
|
|
|
|
AnalysisKey TestImmutableFunctionAnalysis::Key;
|
|
|
|
struct LambdaModulePass : public PassInfoMixin<LambdaModulePass> {
|
|
template <typename T>
|
|
LambdaModulePass(T &&Arg) : Func(std::forward<T>(Arg)) {}
|
|
|
|
PreservedAnalyses run(Module &F, ModuleAnalysisManager &AM) {
|
|
return Func(F, AM);
|
|
}
|
|
|
|
std::function<PreservedAnalyses(Module &, ModuleAnalysisManager &)> Func;
|
|
};
|
|
|
|
struct LambdaSCCPass : public PassInfoMixin<LambdaSCCPass> {
|
|
template <typename T> LambdaSCCPass(T &&Arg) : Func(std::forward<T>(Arg)) {}
|
|
|
|
PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG, CGSCCUpdateResult &UR) {
|
|
return Func(C, AM, CG, UR);
|
|
}
|
|
|
|
std::function<PreservedAnalyses(LazyCallGraph::SCC &, CGSCCAnalysisManager &,
|
|
LazyCallGraph &, CGSCCUpdateResult &)>
|
|
Func;
|
|
};
|
|
|
|
struct LambdaFunctionPass : public PassInfoMixin<LambdaFunctionPass> {
|
|
template <typename T>
|
|
LambdaFunctionPass(T &&Arg) : Func(std::forward<T>(Arg)) {}
|
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
|
|
return Func(F, AM);
|
|
}
|
|
|
|
std::function<PreservedAnalyses(Function &, FunctionAnalysisManager &)> Func;
|
|
};
|
|
|
|
std::unique_ptr<Module> 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<Module> M;
|
|
|
|
public:
|
|
CGSCCPassManagerTest()
|
|
: FAM(/*DebugLogging*/ true), CGAM(/*DebugLogging*/ true),
|
|
MAM(/*DebugLogging*/ true),
|
|
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 @f() {\n"
|
|
"entry:\n"
|
|
" call void @g()\n"
|
|
" call void @h1()\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 @h1() {\n"
|
|
"entry:\n"
|
|
" call void @h2()\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 @h3() {\n"
|
|
"entry:\n"
|
|
" call void @h1()\n"
|
|
" ret void\n"
|
|
"}\n"
|
|
"define void @x() {\n"
|
|
"entry:\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(/*DebugLogging*/ true);
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
FunctionPassManager FPM1(/*DebugLogging*/ true);
|
|
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<ModuleAnalysisManagerCGSCCProxy>(C, CG);
|
|
// ```
|
|
// However getting a stateful analysis is incorrect usage, and the call
|
|
// to getCachedResult below asserts:
|
|
// ```
|
|
// if (TestModuleAnalysis::Result *TMA =
|
|
// MAMProxy.getCachedResult<TestModuleAnalysis>(
|
|
// *C.begin()->getFunction().getParent()))
|
|
// AnalyzedModuleFunctionCount1 += TMA->FunctionCount;
|
|
// ```
|
|
// For the purposes of this unittest, use the above MAM directly.
|
|
if (TestModuleAnalysis::Result *TMA =
|
|
MAM.getCachedResult<TestModuleAnalysis>(
|
|
*C.begin()->getFunction().getParent()))
|
|
AnalyzedModuleFunctionCount1 += TMA->FunctionCount;
|
|
|
|
FunctionAnalysisManager &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(C, CG).getManager();
|
|
TestSCCAnalysis::Result &AR = AM.getResult<TestSCCAnalysis>(C, CG);
|
|
AnalyzedSCCFunctionCount1 += AR.FunctionCount;
|
|
for (LazyCallGraph::Node &N : C) {
|
|
TestFunctionAnalysis::Result &FAR =
|
|
FAM.getResult<TestFunctionAnalysis>(N.getFunction());
|
|
AnalyzedInstrCount1 += FAR.InstructionCount;
|
|
|
|
// Just ensure we get the immutable results.
|
|
(void)FAM.getResult<TestImmutableFunctionAnalysis>(N.getFunction());
|
|
}
|
|
|
|
return PreservedAnalyses::all();
|
|
}));
|
|
|
|
FunctionPassManager FPM2(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
|
|
// 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(/*DebugLogging*/ true);
|
|
int CountFoundModuleAnalysis1 = 0;
|
|
CGPM1.addPass(LambdaSCCPass([&](LazyCallGraph::SCC &C,
|
|
CGSCCAnalysisManager &AM, LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
const auto &MAMProxy = AM.getResult<ModuleAnalysisManagerCGSCCProxy>(C, CG);
|
|
if (MAMProxy.cachedResultExists<TestModuleAnalysis>(
|
|
*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(/*DebugLogging*/ true);
|
|
int CountFoundModuleAnalysis2 = 0;
|
|
CGPM2.addPass(
|
|
LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG, CGSCCUpdateResult &UR) {
|
|
const auto &MAMProxy =
|
|
AM.getResult<ModuleAnalysisManagerCGSCCProxy>(C, CG);
|
|
if (MAMProxy.cachedResultExists<TestModuleAnalysis>(
|
|
*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(/*DebugLogging*/ true);
|
|
int CountFoundModuleAnalysis3 = 0;
|
|
CGPM3.addPass(LambdaSCCPass([&](LazyCallGraph::SCC &C,
|
|
CGSCCAnalysisManager &AM, LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
const auto &MAMProxy = AM.getResult<ModuleAnalysisManagerCGSCCProxy>(C, CG);
|
|
if (MAMProxy.cachedResultExists<TestModuleAnalysis>(
|
|
*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(/*DebugLogging*/ true);
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
|
|
// 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(/*DebugLogging*/ true);
|
|
// 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<ModuleAnalysisManagerFunctionProxy>(F);
|
|
if (!MAMProxy.cachedResultExists<TestModuleAnalysis>(*F.getParent()))
|
|
FoundModuleAnalysis1 = false;
|
|
|
|
return PreservedAnalyses::all();
|
|
}));
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
// 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<ModuleAnalysisManagerFunctionProxy>(F);
|
|
if (!MAMProxy.cachedResultExists<TestModuleAnalysis>(*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(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
// 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<ModuleAnalysisManagerFunctionProxy>(F);
|
|
if (MAMProxy.cachedResultExists<TestModuleAnalysis>(*F.getParent()))
|
|
FoundModuleAnalysis3 = true;
|
|
|
|
return PreservedAnalyses::none();
|
|
}));
|
|
CGSCCPassManager CGPM3(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
CGPM1.addPass(RequireAnalysisPass<TestSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>());
|
|
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<LazyCallGraphAnalysis>();
|
|
PA.preserve<CGSCCAnalysisManagerModuleProxy>();
|
|
PA.preserve<FunctionAnalysisManagerModuleProxy>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again. This
|
|
// will trigger re-running it.
|
|
CGSCCPassManager CGPM2(/*DebugLogging*/ true);
|
|
CGPM2.addPass(RequireAnalysisPass<TestSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>());
|
|
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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
CGPM1.addPass(RequireAnalysisPass<TestSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>());
|
|
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<LazyCallGraphAnalysis>();
|
|
PA.preserve<CGSCCAnalysisManagerModuleProxy>();
|
|
PA.preserve<FunctionAnalysisManagerModuleProxy>();
|
|
PA.preserve<TestSCCAnalysis>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again but find
|
|
// it in the cache.
|
|
CGSCCPassManager CGPM2(/*DebugLogging*/ true);
|
|
CGPM2.addPass(RequireAnalysisPass<TestSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>());
|
|
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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
CGPM1.addPass(RequireAnalysisPass<TestSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>());
|
|
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<TestSCCAnalysis>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again.
|
|
CGSCCPassManager CGPM2(/*DebugLogging*/ true);
|
|
CGPM2.addPass(RequireAnalysisPass<TestSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>());
|
|
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<Module> 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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
FunctionPassManager FPM1(/*DebugLogging*/ true);
|
|
FPM1.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
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<LazyCallGraphAnalysis>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again. This
|
|
// will trigger re-running it.
|
|
FunctionPassManager FPM2(/*DebugLogging*/ true);
|
|
FPM2.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2)));
|
|
|
|
ModulePassManager MPM(/*DebugLogging*/ true);
|
|
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<Module> 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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
FunctionPassManager FPM1(/*DebugLogging*/ true);
|
|
FPM1.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
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<LazyCallGraphAnalysis>();
|
|
PA.preserve<FunctionAnalysisManagerCGSCCProxy>();
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again but find
|
|
// it in the cache.
|
|
FunctionPassManager FPM2(/*DebugLogging*/ true);
|
|
FPM2.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGPM.addPass(createCGSCCToFunctionPassAdaptor(std::move(FPM2)));
|
|
|
|
ModulePassManager MPM(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
FunctionPassManager FPM1(/*DebugLogging*/ true);
|
|
FPM1.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
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<LazyCallGraphAnalysis>();
|
|
PA.preserve<CGSCCAnalysisManagerModuleProxy>();
|
|
PA.preserve<FunctionAnalysisManagerCGSCCProxy>();
|
|
PA.preserve<FunctionAnalysisManagerModuleProxy>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again. This
|
|
// will trigger re-running it.
|
|
FunctionPassManager FPM2(/*DebugLogging*/ true);
|
|
FPM2.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGSCCPassManager CGPM2(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
FunctionPassManager FPM1(/*DebugLogging*/ true);
|
|
FPM1.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
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<LazyCallGraphAnalysis>();
|
|
PA.preserve<CGSCCAnalysisManagerModuleProxy>();
|
|
PA.preserve<FunctionAnalysisManagerCGSCCProxy>();
|
|
PA.preserve<FunctionAnalysisManagerModuleProxy>();
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
return PA;
|
|
}));
|
|
|
|
// And now a second CGSCC run which requires the SCC analysis again. This
|
|
// will trigger re-running it.
|
|
FunctionPassManager FPM2(/*DebugLogging*/ true);
|
|
FPM2.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGSCCPassManager CGPM2(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
|
|
// First force the analysis to be run.
|
|
FunctionPassManager FPM1(/*DebugLogging*/ true);
|
|
FPM1.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGSCCPassManager CGPM1(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
FPM2.addPass(RequireAnalysisPass<TestFunctionAnalysis, Function>());
|
|
CGSCCPassManager CGPM2(/*DebugLogging*/ true);
|
|
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<TestIndirectSCCAnalysis> {
|
|
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<TestIndirectSCCAnalysis>();
|
|
return !(PAC.preserved() ||
|
|
PAC.preservedSet<AllAnalysesOn<LazyCallGraph::SCC>>()) ||
|
|
Inv.invalidate<TestSCCAnalysis>(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<TestSCCAnalysis>(C, CG);
|
|
|
|
auto &ModuleProxy = AM.getResult<ModuleAnalysisManagerCGSCCProxy>(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<TestModuleAnalysis>(
|
|
// *C.begin()->getFunction().getParent());
|
|
// Use MAM, for the purposes of this unittest.
|
|
auto &MDep = *MAM.getCachedResult<TestModuleAnalysis>(
|
|
*C.begin()->getFunction().getParent());
|
|
// Register the dependency as module analysis dependencies have to be
|
|
// pre-registered on the proxy.
|
|
ModuleProxy.registerOuterAnalysisInvalidation<TestModuleAnalysis,
|
|
TestIndirectSCCAnalysis>();
|
|
|
|
return Result(SCCDep, MDep);
|
|
}
|
|
|
|
private:
|
|
friend AnalysisInfoMixin<TestIndirectSCCAnalysis>;
|
|
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<TestDoublyIndirectSCCAnalysis> {
|
|
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<TestDoublyIndirectSCCAnalysis>();
|
|
return !(PAC.preserved() ||
|
|
PAC.preservedSet<AllAnalysesOn<LazyCallGraph::SCC>>()) ||
|
|
Inv.invalidate<TestIndirectSCCAnalysis>(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<TestIndirectSCCAnalysis>(C, CG);
|
|
return Result(IDep);
|
|
}
|
|
|
|
private:
|
|
friend AnalysisInfoMixin<TestDoublyIndirectSCCAnalysis>;
|
|
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<TestIndirectFunctionAnalysis> {
|
|
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<TestIndirectFunctionAnalysis>();
|
|
return !(PAC.preserved() ||
|
|
PAC.preservedSet<AllAnalysesOn<Function>>()) ||
|
|
Inv.invalidate<TestFunctionAnalysis>(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<TestFunctionAnalysis>(F);
|
|
|
|
auto &ModuleProxy = AM.getResult<ModuleAnalysisManagerFunctionProxy>(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<TestModuleAnalysis>(*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<LazyCallGraphAnalysis>(*F.getParent());
|
|
auto &CGSCCProxy = AM.getResult<CGSCCAnalysisManagerFunctionProxy>(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<TestSCCAnalysis>(*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<TestIndirectFunctionAnalysis>;
|
|
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(/*DebugLogging*/ true);
|
|
|
|
int FunctionCount = 0;
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
// 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<TestDoublyIndirectSCCAnalysis>(C, CG);
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
FunctionCount += IndirectResult.SCCDep.FunctionCount;
|
|
return PreservedAnalyses::all();
|
|
}));
|
|
CGPM.addPass(createCGSCCToFunctionPassAdaptor(
|
|
RequireAnalysisPass<TestIndirectFunctionAnalysis, Function>()));
|
|
|
|
// 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<TestDoublyIndirectSCCAnalysis>(C, CG);
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
FunctionCount += IndirectResult.SCCDep.FunctionCount;
|
|
auto PA = PreservedAnalyses::none();
|
|
PA.preserve<FunctionAnalysisManagerCGSCCProxy>();
|
|
PA.preserveSet<AllAnalysesOn<Function>>();
|
|
if (C.getName() == "(g)")
|
|
PA.preserve<TestSCCAnalysis>();
|
|
else if (C.getName() == "(h3, h1, h2)")
|
|
PA.preserve<TestIndirectSCCAnalysis>();
|
|
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<TestDoublyIndirectSCCAnalysis>(C, CG);
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
FunctionCount += IndirectResult.SCCDep.FunctionCount;
|
|
return PreservedAnalyses::all();
|
|
}));
|
|
CGPM.addPass(createCGSCCToFunctionPassAdaptor(
|
|
RequireAnalysisPass<TestIndirectFunctionAnalysis, Function>()));
|
|
|
|
// 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(/*DebugLogging*/ true);
|
|
CGPM2.addPass(
|
|
LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG, CGSCCUpdateResult &) {
|
|
auto &DoublyIndirectResult =
|
|
AM.getResult<TestDoublyIndirectSCCAnalysis>(C, CG);
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
FunctionCount += IndirectResult.SCCDep.FunctionCount;
|
|
return PreservedAnalyses::all();
|
|
}));
|
|
CGPM2.addPass(createCGSCCToFunctionPassAdaptor(
|
|
RequireAnalysisPass<TestIndirectFunctionAnalysis, Function>()));
|
|
|
|
// Add a requires pass to populate the module analysis and then our CGSCC
|
|
// pass pipeline.
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
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<TestModuleAnalysis, Module>());
|
|
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(/*DebugLogging*/ true);
|
|
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
// First just use the analysis to get the function count and preserve
|
|
// everything.
|
|
using RequireTestIndirectFunctionAnalysisPass =
|
|
RequireAnalysisPass<TestIndirectFunctionAnalysis, Function>;
|
|
using RequireTestDoublyIndirectSCCAnalysisPass =
|
|
RequireAnalysisPass<TestDoublyIndirectSCCAnalysis, LazyCallGraph::SCC,
|
|
CGSCCAnalysisManager, LazyCallGraph &,
|
|
CGSCCUpdateResult &>;
|
|
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<TestDoublyIndirectSCCAnalysis>(C, CG);
|
|
if (C.getName() != "(h3, h1, h2)")
|
|
return PreservedAnalyses::all();
|
|
|
|
// Build the preserved set.
|
|
auto PA = PreservedAnalyses::none();
|
|
PA.preserve<FunctionAnalysisManagerCGSCCProxy>();
|
|
PA.preserve<TestIndirectSCCAnalysis>();
|
|
PA.preserve<TestDoublyIndirectSCCAnalysis>();
|
|
|
|
// 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<CallInst>(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(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
CGPM3.addPass(
|
|
LambdaSCCPass([&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG, CGSCCUpdateResult &UR) {
|
|
(void)AM.getResult<TestDoublyIndirectSCCAnalysis>(C, CG);
|
|
if (C.getName() != "(h2)")
|
|
return PreservedAnalyses::all();
|
|
|
|
// Build the preserved set.
|
|
auto PA = PreservedAnalyses::none();
|
|
PA.preserve<FunctionAnalysisManagerCGSCCProxy>();
|
|
PA.preserve<TestIndirectSCCAnalysis>();
|
|
PA.preserve<TestDoublyIndirectSCCAnalysis>();
|
|
|
|
// Delete the bitcast of `h3` that we added earlier.
|
|
auto &H2N = *C.begin();
|
|
auto &H2F = H2N.getFunction();
|
|
auto &H3F = *cast<Function>(cast<BitCastInst>(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(/*DebugLogging*/ true);
|
|
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<TestModuleAnalysis, Module>());
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM2)));
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM3)));
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
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<LambdaSCCPassNoPreserve> {
|
|
template <typename T>
|
|
LambdaSCCPassNoPreserve(T &&Arg) : Func(std::forward<T>(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<FunctionAnalysisManagerCGSCCProxy>();
|
|
return PA;
|
|
}
|
|
|
|
std::function<void(LazyCallGraph::SCC &, CGSCCAnalysisManager &,
|
|
LazyCallGraph &, CGSCCUpdateResult &)>
|
|
Func;
|
|
};
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses0) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve(
|
|
[&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
if (C.getName() != "(h3, h1, h2)")
|
|
return;
|
|
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses1) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C,
|
|
CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
if (C.getName() != "(h3, h1, h2)")
|
|
return;
|
|
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses2) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve(
|
|
[&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
if (C.getName() != "(f)")
|
|
return;
|
|
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses3) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C,
|
|
CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
if (C.getName() != "(f)")
|
|
return;
|
|
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses4) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve(
|
|
[&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
if (C.getName() != "(f)")
|
|
return;
|
|
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(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);
|
|
CGU.registerOutlinedFunction(*FnewF);
|
|
|
|
// 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_NO_FATAL_FAILURE(
|
|
updateCGAndAnalysisManagerForCGSCCPass(CG, C, FN, AM, UR, FAM));
|
|
}));
|
|
|
|
ModulePassManager MPM(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses5) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve([&](LazyCallGraph::SCC &C,
|
|
CGSCCAnalysisManager &AM,
|
|
LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
if (C.getName() != "(f)")
|
|
return;
|
|
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(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);
|
|
CGU.registerOutlinedFunction(*FnewF);
|
|
|
|
// 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(
|
|
updateCGAndAnalysisManagerForFunctionPass(CG, C, FN, AM, UR, FAM),
|
|
"Any new calls should be modeled as");
|
|
}));
|
|
|
|
ModulePassManager MPM(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses6) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses7) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses8) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses9) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestUpdateCGAndAnalysisManagerForPasses10) {
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
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<CallBase>(IP));
|
|
ASSERT_EQ(cast<CallBase>(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(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
TEST_F(CGSCCPassManagerTest, TestInsertionOfNewRefSCC) {
|
|
std::unique_ptr<Module> M = parseIR("define void @f() {\n"
|
|
"entry:\n"
|
|
" call void @f()\n"
|
|
" ret void\n"
|
|
"}\n");
|
|
|
|
CGSCCPassManager CGPM(/*DebugLogging*/ true);
|
|
CGPM.addPass(LambdaSCCPassNoPreserve(
|
|
[&](LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, LazyCallGraph &CG,
|
|
CGSCCUpdateResult &UR) {
|
|
auto &FAM =
|
|
AM.getResult<FunctionAnalysisManagerCGSCCProxy>(C, CG).getManager();
|
|
|
|
for (auto &N : C) {
|
|
auto &F = N.getFunction();
|
|
if (F.getName() != "f")
|
|
continue;
|
|
auto *Call = dyn_cast<CallInst>(F.begin()->begin());
|
|
if (!Call || Call->getCalledFunction()->getName() != "f")
|
|
continue;
|
|
|
|
// Create a new function 'g'.
|
|
auto *G = Function::Create(F.getFunctionType(), F.getLinkage(),
|
|
F.getAddressSpace(), "g", F.getParent());
|
|
BasicBlock::Create(F.getParent()->getContext(), "entry", G);
|
|
// 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)].
|
|
CG.addNewFunctionIntoRefSCC(*G, C.getOuterRefSCC());
|
|
|
|
// "Demote" the 'f -> f' call egde to a ref edge.
|
|
// 1. Erase the call edge from 'f' to 'f'.
|
|
Call->eraseFromParent();
|
|
// 2. Insert a ref edge from 'f' to 'f'.
|
|
(void)CastInst::CreatePointerCast(&F,
|
|
Type::getInt8PtrTy(F.getContext()),
|
|
"f.ref", &*F.begin()->begin());
|
|
|
|
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";
|
|
}
|
|
}));
|
|
|
|
ModulePassManager MPM(/*DebugLogging*/ true);
|
|
MPM.addPass(createModuleToPostOrderCGSCCPassAdaptor(std::move(CGPM)));
|
|
MPM.run(*M, MAM);
|
|
}
|
|
|
|
#endif
|
|
} // namespace
|