2013-11-09 21:09:08 +08:00
|
|
|
//===- llvm/unittest/IR/PassManager.cpp - PassManager tests ---------------===//
|
|
|
|
//
|
2019-01-19 16:50:56 +08:00
|
|
|
// 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
|
2013-11-09 21:09:08 +08:00
|
|
|
//
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
|
2017-06-06 19:06:56 +08:00
|
|
|
#include "llvm/IR/PassManager.h"
|
2020-11-18 10:44:16 +08:00
|
|
|
#include "llvm/Analysis/AssumptionCache.h"
|
|
|
|
#include "llvm/Analysis/TargetTransformInfo.h"
|
2014-01-07 20:34:26 +08:00
|
|
|
#include "llvm/AsmParser/Parser.h"
|
2021-01-01 22:06:56 +08:00
|
|
|
#include "llvm/IR/Dominators.h"
|
2013-11-09 21:09:08 +08:00
|
|
|
#include "llvm/IR/Function.h"
|
|
|
|
#include "llvm/IR/LLVMContext.h"
|
|
|
|
#include "llvm/IR/Module.h"
|
Add PassManagerImpl.h to hide implementation details
ClangBuildAnalyzer results show that a lot of time is spent
instantiating AnalysisManager::getResultImpl across the code base:
**** Templates that took longest to instantiate:
50445 ms: llvm::AnalysisManager<llvm::Function>::getResultImpl (412 times, avg 122 ms)
47797 ms: llvm::AnalysisManager<llvm::Function>::getResult<llvm::TargetLibraryAnalysis> (389 times, avg 122 ms)
46894 ms: std::tie<const unsigned long long, const bool> (2452 times, avg 19 ms)
43851 ms: llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator, 4096, 4096>::Allocate (3228 times, avg 13 ms)
33911 ms: std::tie<const unsigned int, const unsigned int, const unsigned int, const unsigned int> (897 times, avg 37 ms)
33854 ms: std::tie<const unsigned long long, const unsigned long long> (1897 times, avg 17 ms)
27886 ms: std::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string (11156 times, avg 2 ms)
I mentioned this result to @chandlerc, and he suggested this direction.
AnalysisManager is already explicitly instantiated, and getResultImpl
doesn't need to be inlined. Move the definition to an Impl header, and
include that header in files that explicitly instantiate
AnalysisManager. There are only four (real) IR units:
- function
- module
- loop
- cgscc
Looking at a specific transform (ArgumentPromotion.cpp), here are three
compilations before & after this change:
BEFORE:
$ for i in $(seq 3) ; do ./ccit.bat ; done
peak memory: 258.15MB
real: 0m6.297s
peak memory: 257.54MB
real: 0m5.906s
peak memory: 257.47MB
real: 0m6.219s
AFTER:
$ for i in $(seq 3) ; do ./ccit.bat ; done
peak memory: 235.35MB
real: 0m5.454s
peak memory: 234.72MB
real: 0m5.235s
peak memory: 234.39MB
real: 0m5.469s
The 20MB of memory saved seems real, and the time improvement seems like
it is there.
Reviewed By: MaskRay
Differential Revision: https://reviews.llvm.org/D73817
2020-02-01 08:05:32 +08:00
|
|
|
#include "llvm/IR/PassManagerImpl.h"
|
2020-11-18 10:44:16 +08:00
|
|
|
#include "llvm/Passes/StandardInstrumentations.h"
|
2013-11-09 21:09:08 +08:00
|
|
|
#include "llvm/Support/SourceMgr.h"
|
2020-11-18 10:44:16 +08:00
|
|
|
#include "llvm/Transforms/Scalar/SimplifyCFG.h"
|
2013-11-09 21:09:08 +08:00
|
|
|
#include "gtest/gtest.h"
|
|
|
|
|
|
|
|
using namespace llvm;
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
2016-03-11 18:33:22 +08:00
|
|
|
class TestFunctionAnalysis : public AnalysisInfoMixin<TestFunctionAnalysis> {
|
2013-11-13 09:12:08 +08:00
|
|
|
public:
|
|
|
|
struct Result {
|
|
|
|
Result(int Count) : InstructionCount(Count) {}
|
|
|
|
int InstructionCount;
|
2020-01-16 06:30:21 +08:00
|
|
|
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>>());
|
|
|
|
}
|
2013-11-13 09:12:08 +08:00
|
|
|
};
|
|
|
|
|
2013-11-23 09:25:02 +08:00
|
|
|
TestFunctionAnalysis(int &Runs) : Runs(Runs) {}
|
2013-11-21 10:11:31 +08:00
|
|
|
|
2018-05-01 23:54:18 +08:00
|
|
|
/// Run the analysis pass over the function and return a result.
|
2016-03-11 19:05:24 +08:00
|
|
|
Result run(Function &F, FunctionAnalysisManager &AM) {
|
2013-11-21 10:11:31 +08:00
|
|
|
++Runs;
|
2013-11-13 09:12:08 +08:00
|
|
|
int Count = 0;
|
2015-01-05 10:47:05 +08:00
|
|
|
for (Function::iterator BBI = F.begin(), BBE = F.end(); BBI != BBE; ++BBI)
|
2013-11-13 09:12:08 +08:00
|
|
|
for (BasicBlock::iterator II = BBI->begin(), IE = BBI->end(); II != IE;
|
|
|
|
++II)
|
|
|
|
++Count;
|
|
|
|
return Result(Count);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2016-03-11 18:33:22 +08:00
|
|
|
friend AnalysisInfoMixin<TestFunctionAnalysis>;
|
2016-11-24 01:53:26 +08:00
|
|
|
static AnalysisKey Key;
|
2016-03-11 18:22:49 +08:00
|
|
|
|
2013-11-21 10:11:31 +08:00
|
|
|
int &Runs;
|
2013-11-13 09:12:08 +08:00
|
|
|
};
|
|
|
|
|
2016-11-24 01:53:26 +08:00
|
|
|
AnalysisKey TestFunctionAnalysis::Key;
|
2016-03-11 18:22:49 +08:00
|
|
|
|
2016-03-11 18:33:22 +08:00
|
|
|
class TestModuleAnalysis : public AnalysisInfoMixin<TestModuleAnalysis> {
|
2013-11-23 09:25:07 +08:00
|
|
|
public:
|
|
|
|
struct Result {
|
|
|
|
Result(int Count) : FunctionCount(Count) {}
|
|
|
|
int FunctionCount;
|
2020-01-16 06:30:21 +08:00
|
|
|
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>>());
|
|
|
|
}
|
2013-11-23 09:25:07 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
TestModuleAnalysis(int &Runs) : Runs(Runs) {}
|
|
|
|
|
2016-03-11 19:05:24 +08:00
|
|
|
Result run(Module &M, ModuleAnalysisManager &AM) {
|
2013-11-23 09:25:07 +08:00
|
|
|
++Runs;
|
|
|
|
int Count = 0;
|
2015-01-05 10:47:05 +08:00
|
|
|
for (Module::iterator I = M.begin(), E = M.end(); I != E; ++I)
|
2013-11-23 09:25:07 +08:00
|
|
|
++Count;
|
|
|
|
return Result(Count);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2016-03-11 18:33:22 +08:00
|
|
|
friend AnalysisInfoMixin<TestModuleAnalysis>;
|
2016-11-24 01:53:26 +08:00
|
|
|
static AnalysisKey Key;
|
2016-03-11 18:22:49 +08:00
|
|
|
|
2013-11-23 09:25:07 +08:00
|
|
|
int &Runs;
|
|
|
|
};
|
|
|
|
|
2016-11-24 01:53:26 +08:00
|
|
|
AnalysisKey TestModuleAnalysis::Key;
|
2016-03-11 18:22:49 +08:00
|
|
|
|
2016-03-11 18:33:22 +08:00
|
|
|
struct TestModulePass : PassInfoMixin<TestModulePass> {
|
2013-11-09 21:09:08 +08:00
|
|
|
TestModulePass(int &RunCount) : RunCount(RunCount) {}
|
|
|
|
|
2016-06-17 08:11:01 +08:00
|
|
|
PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
|
2013-11-09 21:09:08 +08:00
|
|
|
++RunCount;
|
2013-11-20 19:31:50 +08:00
|
|
|
return PreservedAnalyses::none();
|
2013-11-09 21:09:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int &RunCount;
|
|
|
|
};
|
|
|
|
|
2016-03-11 18:33:22 +08:00
|
|
|
struct TestPreservingModulePass : PassInfoMixin<TestPreservingModulePass> {
|
2016-06-17 08:11:01 +08:00
|
|
|
PreservedAnalyses run(Module &M, ModuleAnalysisManager &) {
|
|
|
|
return PreservedAnalyses::all();
|
|
|
|
}
|
2013-11-21 18:53:05 +08:00
|
|
|
};
|
|
|
|
|
2016-03-11 18:33:22 +08:00
|
|
|
struct TestFunctionPass : PassInfoMixin<TestFunctionPass> {
|
2013-11-23 08:38:42 +08:00
|
|
|
TestFunctionPass(int &RunCount, int &AnalyzedInstrCount,
|
2020-01-15 02:27:20 +08:00
|
|
|
int &AnalyzedFunctionCount, ModuleAnalysisManager &MAM,
|
2013-11-23 08:38:42 +08:00
|
|
|
bool OnlyUseCachedResults = false)
|
|
|
|
: RunCount(RunCount), AnalyzedInstrCount(AnalyzedInstrCount),
|
2020-01-15 02:27:20 +08:00
|
|
|
AnalyzedFunctionCount(AnalyzedFunctionCount), MAM(MAM),
|
2013-11-23 08:38:42 +08:00
|
|
|
OnlyUseCachedResults(OnlyUseCachedResults) {}
|
2013-11-09 21:09:08 +08:00
|
|
|
|
2016-03-11 19:05:24 +08:00
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
|
2013-11-09 21:09:08 +08:00
|
|
|
++RunCount;
|
2013-11-13 09:12:08 +08:00
|
|
|
|
2020-01-15 02:27:20 +08:00
|
|
|
// Getting a cached result that isn't stateless through the proxy will
|
|
|
|
// trigger an assert:
|
|
|
|
// auto &ModuleProxy = AM.getResult<ModuleAnalysisManagerFunctionProxy>(F);
|
|
|
|
// Use MAM, for the purposes of this unittest.
|
2014-02-06 05:41:42 +08:00
|
|
|
if (TestModuleAnalysis::Result *TMA =
|
2020-01-15 02:27:20 +08:00
|
|
|
MAM.getCachedResult<TestModuleAnalysis>(*F.getParent())) {
|
2013-11-23 09:25:07 +08:00
|
|
|
AnalyzedFunctionCount += TMA->FunctionCount;
|
2020-01-15 02:27:20 +08:00
|
|
|
}
|
2013-11-23 09:25:07 +08:00
|
|
|
|
2013-11-23 08:38:42 +08:00
|
|
|
if (OnlyUseCachedResults) {
|
|
|
|
// Hack to force the use of the cached interface.
|
2014-02-06 05:41:42 +08:00
|
|
|
if (TestFunctionAnalysis::Result *AR =
|
2016-03-11 19:05:24 +08:00
|
|
|
AM.getCachedResult<TestFunctionAnalysis>(F))
|
2013-11-23 08:38:42 +08:00
|
|
|
AnalyzedInstrCount += AR->InstructionCount;
|
|
|
|
} else {
|
|
|
|
// Typical path just runs the analysis as needed.
|
2016-03-11 19:05:24 +08:00
|
|
|
TestFunctionAnalysis::Result &AR = AM.getResult<TestFunctionAnalysis>(F);
|
2013-11-23 08:38:42 +08:00
|
|
|
AnalyzedInstrCount += AR.InstructionCount;
|
|
|
|
}
|
2013-11-13 09:12:08 +08:00
|
|
|
|
2013-11-21 10:11:31 +08:00
|
|
|
return PreservedAnalyses::all();
|
2013-11-09 21:09:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int &RunCount;
|
2013-11-13 09:12:08 +08:00
|
|
|
int &AnalyzedInstrCount;
|
2013-11-23 09:25:07 +08:00
|
|
|
int &AnalyzedFunctionCount;
|
2020-01-15 02:27:20 +08:00
|
|
|
ModuleAnalysisManager &MAM;
|
2013-11-23 08:38:42 +08:00
|
|
|
bool OnlyUseCachedResults;
|
2013-11-09 21:09:08 +08:00
|
|
|
};
|
|
|
|
|
2013-11-23 07:38:07 +08:00
|
|
|
// A test function pass that invalidates all function analyses for a function
|
|
|
|
// with a specific name.
|
2016-03-11 18:33:22 +08:00
|
|
|
struct TestInvalidationFunctionPass
|
|
|
|
: PassInfoMixin<TestInvalidationFunctionPass> {
|
2013-11-23 07:38:07 +08:00
|
|
|
TestInvalidationFunctionPass(StringRef FunctionName) : Name(FunctionName) {}
|
|
|
|
|
2016-06-17 08:11:01 +08:00
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
|
2015-01-05 10:47:05 +08:00
|
|
|
return F.getName() == Name ? PreservedAnalyses::none()
|
|
|
|
: PreservedAnalyses::all();
|
2013-11-23 07:38:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
StringRef Name;
|
|
|
|
};
|
|
|
|
|
2016-04-15 05:59:01 +08:00
|
|
|
std::unique_ptr<Module> parseIR(LLVMContext &Context, const char *IR) {
|
2013-11-09 21:09:08 +08:00
|
|
|
SMDiagnostic Err;
|
2016-04-15 05:59:01 +08:00
|
|
|
return parseAssemblyString(IR, Err, Context);
|
2013-11-09 21:09:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
class PassManagerTest : public ::testing::Test {
|
|
|
|
protected:
|
2016-04-15 05:59:01 +08:00
|
|
|
LLVMContext Context;
|
2014-03-06 13:51:42 +08:00
|
|
|
std::unique_ptr<Module> M;
|
2013-11-09 21:09:08 +08:00
|
|
|
|
|
|
|
public:
|
|
|
|
PassManagerTest()
|
2016-04-15 05:59:01 +08:00
|
|
|
: M(parseIR(Context, "define void @f() {\n"
|
|
|
|
"entry:\n"
|
|
|
|
" call void @g()\n"
|
|
|
|
" call void @h()\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n"
|
|
|
|
"define void @g() {\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n"
|
|
|
|
"define void @h() {\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n")) {}
|
2013-11-09 21:09:08 +08:00
|
|
|
};
|
|
|
|
|
2016-12-27 16:40:39 +08:00
|
|
|
TEST(PreservedAnalysesTest, Basic) {
|
2014-03-13 18:42:18 +08:00
|
|
|
PreservedAnalyses PA1 = PreservedAnalyses();
|
2016-12-27 16:40:39 +08:00
|
|
|
{
|
|
|
|
auto PAC = PA1.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PAC.preserved());
|
|
|
|
EXPECT_FALSE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
}
|
|
|
|
{
|
|
|
|
auto PAC = PA1.getChecker<TestModuleAnalysis>();
|
|
|
|
EXPECT_FALSE(PAC.preserved());
|
|
|
|
EXPECT_FALSE(PAC.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
}
|
|
|
|
auto PA2 = PreservedAnalyses::none();
|
|
|
|
{
|
|
|
|
auto PAC = PA2.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PAC.preserved());
|
|
|
|
EXPECT_FALSE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
}
|
|
|
|
auto PA3 = PreservedAnalyses::all();
|
|
|
|
{
|
|
|
|
auto PAC = PA3.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_TRUE(PAC.preserved());
|
|
|
|
EXPECT_TRUE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
}
|
2014-03-13 18:42:18 +08:00
|
|
|
PreservedAnalyses PA4 = PA1;
|
2016-12-27 16:40:39 +08:00
|
|
|
{
|
|
|
|
auto PAC = PA4.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PAC.preserved());
|
|
|
|
EXPECT_FALSE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
}
|
2014-03-13 18:42:18 +08:00
|
|
|
PA4 = PA3;
|
2016-12-27 16:40:39 +08:00
|
|
|
{
|
|
|
|
auto PAC = PA4.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_TRUE(PAC.preserved());
|
|
|
|
EXPECT_TRUE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
}
|
2014-03-13 18:42:18 +08:00
|
|
|
PA4 = std::move(PA2);
|
2016-12-27 16:40:39 +08:00
|
|
|
{
|
|
|
|
auto PAC = PA4.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PAC.preserved());
|
|
|
|
EXPECT_FALSE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
}
|
2017-07-09 15:23:27 +08:00
|
|
|
auto PA5 = PreservedAnalyses::allInSet<AllAnalysesOn<Function>>();
|
|
|
|
{
|
|
|
|
auto PAC = PA5.getChecker<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PAC.preserved());
|
|
|
|
EXPECT_TRUE(PAC.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(PAC.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
}
|
2016-12-27 16:40:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
TEST(PreservedAnalysesTest, Preserve) {
|
|
|
|
auto PA = PreservedAnalyses::none();
|
|
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
PA.preserve<TestModuleAnalysis>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
|
|
|
|
// Redundant calls are fine.
|
|
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(PreservedAnalysesTest, PreserveSets) {
|
|
|
|
auto PA = PreservedAnalyses::none();
|
|
|
|
PA.preserveSet<AllAnalysesOn<Function>>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
PA.preserveSet<AllAnalysesOn<Module>>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Mixing is fine.
|
|
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Redundant calls are fine.
|
|
|
|
PA.preserveSet<AllAnalysesOn<Module>>();
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_TRUE(PA.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(PreservedAnalysisTest, Intersect) {
|
|
|
|
// Setup the initial sets.
|
|
|
|
auto PA1 = PreservedAnalyses::none();
|
2014-03-13 18:42:18 +08:00
|
|
|
PA1.preserve<TestFunctionAnalysis>();
|
2016-12-27 16:40:39 +08:00
|
|
|
PA1.preserveSet<AllAnalysesOn<Module>>();
|
|
|
|
auto PA2 = PreservedAnalyses::none();
|
|
|
|
PA2.preserve<TestFunctionAnalysis>();
|
|
|
|
PA2.preserveSet<AllAnalysesOn<Function>>();
|
|
|
|
PA2.preserve<TestModuleAnalysis>();
|
|
|
|
PA2.preserveSet<AllAnalysesOn<Module>>();
|
|
|
|
auto PA3 = PreservedAnalyses::none();
|
|
|
|
PA3.preserve<TestModuleAnalysis>();
|
|
|
|
PA3.preserveSet<AllAnalysesOn<Function>>();
|
|
|
|
|
|
|
|
// Self intersection is a no-op.
|
|
|
|
auto Intersected = PA1;
|
|
|
|
Intersected.intersect(PA1);
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting with all is a no-op.
|
|
|
|
Intersected.intersect(PreservedAnalyses::all());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting a narrow set with a more broad set is the narrow set.
|
|
|
|
Intersected.intersect(PA2);
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting a broad set with a more narrow set is the narrow set.
|
|
|
|
Intersected = PA2;
|
|
|
|
Intersected.intersect(PA1);
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting with empty clears.
|
|
|
|
Intersected.intersect(PreservedAnalyses::none());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting non-overlapping clears.
|
|
|
|
Intersected = PA1;
|
|
|
|
Intersected.intersect(PA3);
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting with moves works in when there is storage on both sides.
|
|
|
|
Intersected = PA1;
|
|
|
|
auto Tmp = PA2;
|
|
|
|
Intersected.intersect(std::move(Tmp));
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// Intersecting with move works for incoming all and existing all.
|
|
|
|
auto Tmp2 = PreservedAnalyses::all();
|
|
|
|
Intersected.intersect(std::move(Tmp2));
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
Intersected = PreservedAnalyses::all();
|
|
|
|
auto Tmp3 = PA1;
|
|
|
|
Intersected.intersect(std::move(Tmp3));
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(Intersected.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
EXPECT_TRUE(Intersected.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST(PreservedAnalysisTest, Abandon) {
|
|
|
|
auto PA = PreservedAnalyses::none();
|
|
|
|
|
|
|
|
// We can abandon things after they are preserved.
|
|
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
|
|
PA.abandon<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
|
|
|
|
// Repeated is fine, and abandoning if they were never preserved is fine.
|
|
|
|
PA.abandon<TestFunctionAnalysis>();
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestFunctionAnalysis>().preserved());
|
|
|
|
PA.abandon<TestModuleAnalysis>();
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestModuleAnalysis>().preserved());
|
|
|
|
|
|
|
|
// Even if the sets are preserved, the abandoned analyses' checker won't
|
|
|
|
// return true for those sets.
|
|
|
|
PA.preserveSet<AllAnalysesOn<Function>>();
|
|
|
|
PA.preserveSet<AllAnalysesOn<Module>>();
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestFunctionAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_FALSE(PA.getChecker<TestModuleAnalysis>()
|
|
|
|
.preservedSet<AllAnalysesOn<Module>>());
|
|
|
|
|
|
|
|
// But an arbitrary (opaque) analysis will still observe the sets as
|
|
|
|
// preserved. This also checks that we can use an explicit ID rather than
|
|
|
|
// a type.
|
|
|
|
AnalysisKey FakeKey, *FakeID = &FakeKey;
|
|
|
|
EXPECT_TRUE(PA.getChecker(FakeID).preservedSet<AllAnalysesOn<Function>>());
|
|
|
|
EXPECT_TRUE(PA.getChecker(FakeID).preservedSet<AllAnalysesOn<Module>>());
|
2014-03-13 18:42:18 +08:00
|
|
|
}
|
|
|
|
|
2013-11-09 21:09:08 +08:00
|
|
|
TEST_F(PassManagerTest, Basic) {
|
2021-05-08 05:32:40 +08:00
|
|
|
FunctionAnalysisManager FAM;
|
2013-11-23 09:25:07 +08:00
|
|
|
int FunctionAnalysisRuns = 0;
|
2016-02-18 17:45:17 +08:00
|
|
|
FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); });
|
2013-11-21 10:11:31 +08:00
|
|
|
|
2021-05-08 05:32:40 +08:00
|
|
|
ModuleAnalysisManager MAM;
|
2013-11-23 09:25:07 +08:00
|
|
|
int ModuleAnalysisRuns = 0;
|
2016-02-18 17:45:17 +08:00
|
|
|
MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); });
|
|
|
|
MAM.registerPass([&] { return FunctionAnalysisManagerModuleProxy(FAM); });
|
|
|
|
FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); });
|
2013-11-21 10:11:31 +08:00
|
|
|
|
2018-09-21 01:08:45 +08:00
|
|
|
MAM.registerPass([&] { return PassInstrumentationAnalysis(); });
|
|
|
|
FAM.registerPass([&] { return PassInstrumentationAnalysis(); });
|
|
|
|
|
[PM] Switch analysis managers to be threaded through the run methods
rather than the constructors of passes.
This simplifies the APIs of passes significantly and removes an error
prone pattern where the *same* manager had to be given to every
different layer. With the new API the analysis managers themselves will
have to be cross connected with proxy analyses that allow a pass at one
layer to query for the analysis manager of another layer. The proxy will
both expose a handle to the other layer's manager and it will provide
the invalidation hooks to ensure things remain consistent across layers.
Finally, the outer-most analysis manager has to be passed to the run
method of the outer-most pass manager. The rest of the propagation is
automatic.
I've used SFINAE again to allow passes to completely disregard the
analysis manager if they don't need or want to care. This helps keep
simple things simple for users of the new pass manager.
Also, the system specifically supports passing a null pointer into the
outer-most run method if your pass pipeline neither needs nor wants to
deal with analyses. I find this of dubious utility as while some
*passes* don't care about analysis, I'm not sure there are any
real-world users of the pass manager itself that need to avoid even
creating an analysis manager. But it is easy to support, so there we go.
Finally I renamed the module proxy for the function analysis manager to
the more verbose but less confusing name of
FunctionAnalysisManagerModuleProxy. I hate this name, but I have no idea
what else to name these things. I'm expecting in the fullness of time to
potentially have the complete cross product of types at the proxy layer:
{Module,SCC,Function,Loop,Region}AnalysisManager{Module,SCC,Function,Loop,Region}Proxy
(except for XAnalysisManagerXProxy which doesn't make any sense)
This should make it somewhat easier to do the next phases which is to
build the upward proxy and get its invalidation correct, as well as to
make the invalidation within the Module -> Function mapping pass be more
fine grained so as to invalidate fewer fuction analyses.
After all of the proxy analyses are done and the invalidation working,
I'll finally be able to start working on the next two fun fronts: how to
adapt an existing pass to work in both the legacy pass world and the new
one, and building the SCC, Loop, and Region counterparts. Fun times!
llvm-svn: 195400
2013-11-22 08:43:29 +08:00
|
|
|
ModulePassManager MPM;
|
2013-11-13 09:12:08 +08:00
|
|
|
|
2013-11-21 10:11:31 +08:00
|
|
|
// Count the runs over a Function.
|
|
|
|
int FunctionPassRunCount1 = 0;
|
|
|
|
int AnalyzedInstrCount1 = 0;
|
2013-11-23 09:25:07 +08:00
|
|
|
int AnalyzedFunctionCount1 = 0;
|
2014-03-09 19:49:53 +08:00
|
|
|
{
|
2014-03-13 18:42:18 +08:00
|
|
|
// Pointless scoped copy to test move assignment.
|
2021-05-04 07:09:56 +08:00
|
|
|
ModulePassManager NestedMPM;
|
2014-03-09 19:49:53 +08:00
|
|
|
FunctionPassManager FPM;
|
2014-03-13 18:42:18 +08:00
|
|
|
{
|
|
|
|
// Pointless scope to test move assignment.
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager NestedFPM;
|
2020-01-15 02:27:20 +08:00
|
|
|
NestedFPM.addPass(TestFunctionPass(FunctionPassRunCount1,
|
|
|
|
AnalyzedInstrCount1,
|
|
|
|
AnalyzedFunctionCount1, MAM));
|
2014-03-13 18:42:18 +08:00
|
|
|
FPM = std::move(NestedFPM);
|
|
|
|
}
|
|
|
|
NestedMPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
MPM = std::move(NestedMPM);
|
2014-03-09 19:49:53 +08:00
|
|
|
}
|
2013-11-09 21:09:08 +08:00
|
|
|
|
|
|
|
// Count the runs over a module.
|
|
|
|
int ModulePassRunCount = 0;
|
|
|
|
MPM.addPass(TestModulePass(ModulePassRunCount));
|
|
|
|
|
2013-11-21 10:11:31 +08:00
|
|
|
// Count the runs over a Function in a separate manager.
|
|
|
|
int FunctionPassRunCount2 = 0;
|
|
|
|
int AnalyzedInstrCount2 = 0;
|
2013-11-23 09:25:07 +08:00
|
|
|
int AnalyzedFunctionCount2 = 0;
|
2014-03-09 19:49:53 +08:00
|
|
|
{
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM;
|
2014-03-09 19:49:53 +08:00
|
|
|
FPM.addPass(TestFunctionPass(FunctionPassRunCount2, AnalyzedInstrCount2,
|
2020-01-15 02:27:20 +08:00
|
|
|
AnalyzedFunctionCount2, MAM));
|
2014-03-09 19:49:53 +08:00
|
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
}
|
2013-11-09 21:09:08 +08:00
|
|
|
|
2013-11-23 07:38:07 +08:00
|
|
|
// A third function pass manager but with only preserving intervening passes
|
|
|
|
// and with a function pass that invalidates exactly one analysis.
|
2013-11-21 18:53:05 +08:00
|
|
|
MPM.addPass(TestPreservingModulePass());
|
|
|
|
int FunctionPassRunCount3 = 0;
|
|
|
|
int AnalyzedInstrCount3 = 0;
|
2013-11-23 09:25:07 +08:00
|
|
|
int AnalyzedFunctionCount3 = 0;
|
2014-03-09 19:49:53 +08:00
|
|
|
{
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM;
|
2014-03-09 19:49:53 +08:00
|
|
|
FPM.addPass(TestFunctionPass(FunctionPassRunCount3, AnalyzedInstrCount3,
|
2020-01-15 02:27:20 +08:00
|
|
|
AnalyzedFunctionCount3, MAM));
|
2014-03-09 19:49:53 +08:00
|
|
|
FPM.addPass(TestInvalidationFunctionPass("f"));
|
|
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
}
|
2013-11-21 18:53:05 +08:00
|
|
|
|
[PM] Support invalidation of inner analysis managers from a pass over the outer IR unit.
Summary:
This never really got implemented, and was very hard to test before
a lot of the refactoring changes to make things more robust. But now we
can test it thoroughly and cleanly, especially at the CGSCC level.
The core idea is that when an inner analysis manager proxy receives the
invalidation event for the outer IR unit, it needs to walk the inner IR
units and propagate it to the inner analysis manager for each of those
units. For example, each function in the SCC needs to get an
invalidation event when the SCC gets one.
The function / module interaction is somewhat boring here. This really
becomes interesting in the face of analysis-backed IR units. This patch
effectively handles all of the CGSCC layer's needs -- both invalidating
SCC analysis and invalidating function analysis when an SCC gets
invalidated.
However, this second aspect doesn't really handle the
LoopAnalysisManager well at this point. That one will need some change
of design in order to fully integrate, because unlike the call graph,
the entire function behind a LoopAnalysis's results can vanish out from
under us, and we won't even have a cached API to access. I'd like to try
to separate solving the loop problems into a subsequent patch though in
order to keep this more focused so I've adapted them to the API and
updated the tests that immediately fail, but I've not added the level of
testing and validation at that layer that I have at the CGSCC layer.
An important aspect of this change is that the proxy for the
FunctionAnalysisManager at the SCC pass layer doesn't work like the
other proxies for an inner IR unit as it doesn't directly manage the
FunctionAnalysisManager and invalidation or clearing of it. This would
create an ever worsening problem of dual ownership of this
responsibility, split between the module-level FAM proxy and this
SCC-level FAM proxy. Instead, this patch changes the SCC-level FAM proxy
to work in terms of the module-level proxy and defer to it to handle
much of the updates. It only does SCC-specific invalidation. This will
become more important in subsequent patches that support more complex
invalidaiton scenarios.
Reviewers: jlebar
Subscribers: mehdi_amini, mcrosier, mzolotukhin, llvm-commits
Differential Revision: https://reviews.llvm.org/D27197
llvm-svn: 289317
2016-12-10 14:34:44 +08:00
|
|
|
// A fourth function pass manager but with only preserving intervening
|
|
|
|
// passes but triggering the module analysis.
|
|
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
2013-11-21 18:53:05 +08:00
|
|
|
int FunctionPassRunCount4 = 0;
|
|
|
|
int AnalyzedInstrCount4 = 0;
|
2013-11-23 09:25:07 +08:00
|
|
|
int AnalyzedFunctionCount4 = 0;
|
2014-03-09 19:49:53 +08:00
|
|
|
{
|
|
|
|
FunctionPassManager FPM;
|
|
|
|
FPM.addPass(TestFunctionPass(FunctionPassRunCount4, AnalyzedInstrCount4,
|
2020-01-15 02:27:20 +08:00
|
|
|
AnalyzedFunctionCount4, MAM));
|
2014-03-09 19:49:53 +08:00
|
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
}
|
2013-11-21 18:53:05 +08:00
|
|
|
|
[PM] Support invalidation of inner analysis managers from a pass over the outer IR unit.
Summary:
This never really got implemented, and was very hard to test before
a lot of the refactoring changes to make things more robust. But now we
can test it thoroughly and cleanly, especially at the CGSCC level.
The core idea is that when an inner analysis manager proxy receives the
invalidation event for the outer IR unit, it needs to walk the inner IR
units and propagate it to the inner analysis manager for each of those
units. For example, each function in the SCC needs to get an
invalidation event when the SCC gets one.
The function / module interaction is somewhat boring here. This really
becomes interesting in the face of analysis-backed IR units. This patch
effectively handles all of the CGSCC layer's needs -- both invalidating
SCC analysis and invalidating function analysis when an SCC gets
invalidated.
However, this second aspect doesn't really handle the
LoopAnalysisManager well at this point. That one will need some change
of design in order to fully integrate, because unlike the call graph,
the entire function behind a LoopAnalysis's results can vanish out from
under us, and we won't even have a cached API to access. I'd like to try
to separate solving the loop problems into a subsequent patch though in
order to keep this more focused so I've adapted them to the API and
updated the tests that immediately fail, but I've not added the level of
testing and validation at that layer that I have at the CGSCC layer.
An important aspect of this change is that the proxy for the
FunctionAnalysisManager at the SCC pass layer doesn't work like the
other proxies for an inner IR unit as it doesn't directly manage the
FunctionAnalysisManager and invalidation or clearing of it. This would
create an ever worsening problem of dual ownership of this
responsibility, split between the module-level FAM proxy and this
SCC-level FAM proxy. Instead, this patch changes the SCC-level FAM proxy
to work in terms of the module-level proxy and defer to it to handle
much of the updates. It only does SCC-specific invalidation. This will
become more important in subsequent patches that support more complex
invalidaiton scenarios.
Reviewers: jlebar
Subscribers: mehdi_amini, mcrosier, mzolotukhin, llvm-commits
Differential Revision: https://reviews.llvm.org/D27197
llvm-svn: 289317
2016-12-10 14:34:44 +08:00
|
|
|
// A fifth function pass manager which invalidates one function first but
|
|
|
|
// uses only cached results.
|
2013-11-23 08:38:42 +08:00
|
|
|
int FunctionPassRunCount5 = 0;
|
|
|
|
int AnalyzedInstrCount5 = 0;
|
2013-11-23 09:25:07 +08:00
|
|
|
int AnalyzedFunctionCount5 = 0;
|
2014-03-09 19:49:53 +08:00
|
|
|
{
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM;
|
2014-03-09 19:49:53 +08:00
|
|
|
FPM.addPass(TestInvalidationFunctionPass("f"));
|
|
|
|
FPM.addPass(TestFunctionPass(FunctionPassRunCount5, AnalyzedInstrCount5,
|
2020-01-15 02:27:20 +08:00
|
|
|
AnalyzedFunctionCount5, MAM,
|
2014-03-09 19:49:53 +08:00
|
|
|
/*OnlyUseCachedResults=*/true));
|
|
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
|
|
|
}
|
2013-11-23 08:38:42 +08:00
|
|
|
|
2016-03-11 19:05:24 +08:00
|
|
|
MPM.run(*M, MAM);
|
2013-11-21 10:11:31 +08:00
|
|
|
|
|
|
|
// Validate module pass counters.
|
2013-11-09 21:09:08 +08:00
|
|
|
EXPECT_EQ(1, ModulePassRunCount);
|
2013-11-21 10:11:31 +08:00
|
|
|
|
2013-11-23 07:38:07 +08:00
|
|
|
// Validate all function pass counter sets are the same.
|
2013-11-21 10:11:31 +08:00
|
|
|
EXPECT_EQ(3, FunctionPassRunCount1);
|
|
|
|
EXPECT_EQ(5, AnalyzedInstrCount1);
|
2013-11-23 09:25:07 +08:00
|
|
|
EXPECT_EQ(0, AnalyzedFunctionCount1);
|
2013-11-21 10:11:31 +08:00
|
|
|
EXPECT_EQ(3, FunctionPassRunCount2);
|
|
|
|
EXPECT_EQ(5, AnalyzedInstrCount2);
|
2013-11-23 09:25:07 +08:00
|
|
|
EXPECT_EQ(0, AnalyzedFunctionCount2);
|
2013-11-21 18:53:05 +08:00
|
|
|
EXPECT_EQ(3, FunctionPassRunCount3);
|
|
|
|
EXPECT_EQ(5, AnalyzedInstrCount3);
|
2013-11-23 09:25:07 +08:00
|
|
|
EXPECT_EQ(0, AnalyzedFunctionCount3);
|
2013-11-21 18:53:05 +08:00
|
|
|
EXPECT_EQ(3, FunctionPassRunCount4);
|
|
|
|
EXPECT_EQ(5, AnalyzedInstrCount4);
|
[PM] Support invalidation of inner analysis managers from a pass over the outer IR unit.
Summary:
This never really got implemented, and was very hard to test before
a lot of the refactoring changes to make things more robust. But now we
can test it thoroughly and cleanly, especially at the CGSCC level.
The core idea is that when an inner analysis manager proxy receives the
invalidation event for the outer IR unit, it needs to walk the inner IR
units and propagate it to the inner analysis manager for each of those
units. For example, each function in the SCC needs to get an
invalidation event when the SCC gets one.
The function / module interaction is somewhat boring here. This really
becomes interesting in the face of analysis-backed IR units. This patch
effectively handles all of the CGSCC layer's needs -- both invalidating
SCC analysis and invalidating function analysis when an SCC gets
invalidated.
However, this second aspect doesn't really handle the
LoopAnalysisManager well at this point. That one will need some change
of design in order to fully integrate, because unlike the call graph,
the entire function behind a LoopAnalysis's results can vanish out from
under us, and we won't even have a cached API to access. I'd like to try
to separate solving the loop problems into a subsequent patch though in
order to keep this more focused so I've adapted them to the API and
updated the tests that immediately fail, but I've not added the level of
testing and validation at that layer that I have at the CGSCC layer.
An important aspect of this change is that the proxy for the
FunctionAnalysisManager at the SCC pass layer doesn't work like the
other proxies for an inner IR unit as it doesn't directly manage the
FunctionAnalysisManager and invalidation or clearing of it. This would
create an ever worsening problem of dual ownership of this
responsibility, split between the module-level FAM proxy and this
SCC-level FAM proxy. Instead, this patch changes the SCC-level FAM proxy
to work in terms of the module-level proxy and defer to it to handle
much of the updates. It only does SCC-specific invalidation. This will
become more important in subsequent patches that support more complex
invalidaiton scenarios.
Reviewers: jlebar
Subscribers: mehdi_amini, mcrosier, mzolotukhin, llvm-commits
Differential Revision: https://reviews.llvm.org/D27197
llvm-svn: 289317
2016-12-10 14:34:44 +08:00
|
|
|
EXPECT_EQ(9, AnalyzedFunctionCount4);
|
2013-11-23 08:38:42 +08:00
|
|
|
EXPECT_EQ(3, FunctionPassRunCount5);
|
|
|
|
EXPECT_EQ(2, AnalyzedInstrCount5); // Only 'g' and 'h' were cached.
|
[PM] Support invalidation of inner analysis managers from a pass over the outer IR unit.
Summary:
This never really got implemented, and was very hard to test before
a lot of the refactoring changes to make things more robust. But now we
can test it thoroughly and cleanly, especially at the CGSCC level.
The core idea is that when an inner analysis manager proxy receives the
invalidation event for the outer IR unit, it needs to walk the inner IR
units and propagate it to the inner analysis manager for each of those
units. For example, each function in the SCC needs to get an
invalidation event when the SCC gets one.
The function / module interaction is somewhat boring here. This really
becomes interesting in the face of analysis-backed IR units. This patch
effectively handles all of the CGSCC layer's needs -- both invalidating
SCC analysis and invalidating function analysis when an SCC gets
invalidated.
However, this second aspect doesn't really handle the
LoopAnalysisManager well at this point. That one will need some change
of design in order to fully integrate, because unlike the call graph,
the entire function behind a LoopAnalysis's results can vanish out from
under us, and we won't even have a cached API to access. I'd like to try
to separate solving the loop problems into a subsequent patch though in
order to keep this more focused so I've adapted them to the API and
updated the tests that immediately fail, but I've not added the level of
testing and validation at that layer that I have at the CGSCC layer.
An important aspect of this change is that the proxy for the
FunctionAnalysisManager at the SCC pass layer doesn't work like the
other proxies for an inner IR unit as it doesn't directly manage the
FunctionAnalysisManager and invalidation or clearing of it. This would
create an ever worsening problem of dual ownership of this
responsibility, split between the module-level FAM proxy and this
SCC-level FAM proxy. Instead, this patch changes the SCC-level FAM proxy
to work in terms of the module-level proxy and defer to it to handle
much of the updates. It only does SCC-specific invalidation. This will
become more important in subsequent patches that support more complex
invalidaiton scenarios.
Reviewers: jlebar
Subscribers: mehdi_amini, mcrosier, mzolotukhin, llvm-commits
Differential Revision: https://reviews.llvm.org/D27197
llvm-svn: 289317
2016-12-10 14:34:44 +08:00
|
|
|
EXPECT_EQ(9, AnalyzedFunctionCount5);
|
2013-11-21 10:11:31 +08:00
|
|
|
|
2013-11-23 07:38:07 +08:00
|
|
|
// Validate the analysis counters:
|
|
|
|
// first run over 3 functions, then module pass invalidates
|
|
|
|
// second run over 3 functions, nothing invalidates
|
|
|
|
// third run over 0 functions, but 1 function invalidated
|
|
|
|
// fourth run over 1 function
|
[PM] Support invalidation of inner analysis managers from a pass over the outer IR unit.
Summary:
This never really got implemented, and was very hard to test before
a lot of the refactoring changes to make things more robust. But now we
can test it thoroughly and cleanly, especially at the CGSCC level.
The core idea is that when an inner analysis manager proxy receives the
invalidation event for the outer IR unit, it needs to walk the inner IR
units and propagate it to the inner analysis manager for each of those
units. For example, each function in the SCC needs to get an
invalidation event when the SCC gets one.
The function / module interaction is somewhat boring here. This really
becomes interesting in the face of analysis-backed IR units. This patch
effectively handles all of the CGSCC layer's needs -- both invalidating
SCC analysis and invalidating function analysis when an SCC gets
invalidated.
However, this second aspect doesn't really handle the
LoopAnalysisManager well at this point. That one will need some change
of design in order to fully integrate, because unlike the call graph,
the entire function behind a LoopAnalysis's results can vanish out from
under us, and we won't even have a cached API to access. I'd like to try
to separate solving the loop problems into a subsequent patch though in
order to keep this more focused so I've adapted them to the API and
updated the tests that immediately fail, but I've not added the level of
testing and validation at that layer that I have at the CGSCC layer.
An important aspect of this change is that the proxy for the
FunctionAnalysisManager at the SCC pass layer doesn't work like the
other proxies for an inner IR unit as it doesn't directly manage the
FunctionAnalysisManager and invalidation or clearing of it. This would
create an ever worsening problem of dual ownership of this
responsibility, split between the module-level FAM proxy and this
SCC-level FAM proxy. Instead, this patch changes the SCC-level FAM proxy
to work in terms of the module-level proxy and defer to it to handle
much of the updates. It only does SCC-specific invalidation. This will
become more important in subsequent patches that support more complex
invalidaiton scenarios.
Reviewers: jlebar
Subscribers: mehdi_amini, mcrosier, mzolotukhin, llvm-commits
Differential Revision: https://reviews.llvm.org/D27197
llvm-svn: 289317
2016-12-10 14:34:44 +08:00
|
|
|
// fifth run invalidates 1 function first, but runs over 0 functions
|
2013-11-23 09:25:07 +08:00
|
|
|
EXPECT_EQ(7, FunctionAnalysisRuns);
|
|
|
|
|
|
|
|
EXPECT_EQ(1, ModuleAnalysisRuns);
|
2013-11-09 21:09:08 +08:00
|
|
|
}
|
2016-08-20 02:36:06 +08:00
|
|
|
|
|
|
|
// A customized pass manager that passes extra arguments through the
|
|
|
|
// infrastructure.
|
|
|
|
typedef AnalysisManager<Function, int> CustomizedAnalysisManager;
|
|
|
|
typedef PassManager<Function, CustomizedAnalysisManager, int, int &>
|
|
|
|
CustomizedPassManager;
|
|
|
|
|
|
|
|
class CustomizedAnalysis : public AnalysisInfoMixin<CustomizedAnalysis> {
|
|
|
|
public:
|
|
|
|
struct Result {
|
|
|
|
Result(int I) : I(I) {}
|
|
|
|
int I;
|
|
|
|
};
|
|
|
|
|
|
|
|
Result run(Function &F, CustomizedAnalysisManager &AM, int I) {
|
|
|
|
return Result(I);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
friend AnalysisInfoMixin<CustomizedAnalysis>;
|
2016-11-24 01:53:26 +08:00
|
|
|
static AnalysisKey Key;
|
2016-08-20 02:36:06 +08:00
|
|
|
};
|
|
|
|
|
2016-11-24 01:53:26 +08:00
|
|
|
AnalysisKey CustomizedAnalysis::Key;
|
2016-08-20 02:36:06 +08:00
|
|
|
|
|
|
|
struct CustomizedPass : PassInfoMixin<CustomizedPass> {
|
|
|
|
std::function<void(CustomizedAnalysis::Result &, int &)> Callback;
|
|
|
|
|
|
|
|
template <typename CallbackT>
|
|
|
|
CustomizedPass(CallbackT Callback) : Callback(Callback) {}
|
|
|
|
|
|
|
|
PreservedAnalyses run(Function &F, CustomizedAnalysisManager &AM, int I,
|
|
|
|
int &O) {
|
|
|
|
Callback(AM.getResult<CustomizedAnalysis>(F, I), O);
|
|
|
|
return PreservedAnalyses::none();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(PassManagerTest, CustomizedPassManagerArgs) {
|
|
|
|
CustomizedAnalysisManager AM;
|
|
|
|
AM.registerPass([&] { return CustomizedAnalysis(); });
|
2018-09-21 01:08:45 +08:00
|
|
|
PassInstrumentationCallbacks PIC;
|
|
|
|
AM.registerPass([&] { return PassInstrumentationAnalysis(&PIC); });
|
2016-08-20 02:36:06 +08:00
|
|
|
|
|
|
|
CustomizedPassManager PM;
|
|
|
|
|
|
|
|
// Add an instance of the customized pass that just accumulates the input
|
|
|
|
// after it is round-tripped through the analysis.
|
|
|
|
int Result = 0;
|
|
|
|
PM.addPass(
|
|
|
|
CustomizedPass([](CustomizedAnalysis::Result &R, int &O) { O += R.I; }));
|
|
|
|
|
|
|
|
// Run this over every function with the input of 42.
|
|
|
|
for (Function &F : *M)
|
|
|
|
PM.run(F, AM, 42, Result);
|
|
|
|
|
|
|
|
// And ensure that we accumulated the correct result.
|
|
|
|
EXPECT_EQ(42 * (int)M->size(), Result);
|
|
|
|
}
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
|
|
|
|
/// A test 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.
|
|
|
|
struct TestIndirectFunctionAnalysis
|
|
|
|
: public AnalysisInfoMixin<TestIndirectFunctionAnalysis> {
|
|
|
|
struct Result {
|
2016-12-27 16:40:39 +08:00
|
|
|
Result(TestFunctionAnalysis::Result &FDep, TestModuleAnalysis::Result &MDep)
|
|
|
|
: FDep(FDep), MDep(MDep) {}
|
|
|
|
TestFunctionAnalysis::Result &FDep;
|
|
|
|
TestModuleAnalysis::Result &MDep;
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
|
|
|
|
bool invalidate(Function &F, const PreservedAnalyses &PA,
|
|
|
|
FunctionAnalysisManager::Invalidator &Inv) {
|
2016-12-27 16:40:39 +08:00
|
|
|
auto PAC = PA.getChecker<TestIndirectFunctionAnalysis>();
|
|
|
|
return !(PAC.preserved() ||
|
|
|
|
PAC.preservedSet<AllAnalysesOn<Function>>()) ||
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
Inv.invalidate<TestFunctionAnalysis>(F, PA);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-01-15 02:27:20 +08:00
|
|
|
TestIndirectFunctionAnalysis(int &Runs, ModuleAnalysisManager &MAM)
|
|
|
|
: Runs(Runs), MAM(MAM) {}
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
|
|
|
|
/// Run the analysis pass over the function and return a result.
|
|
|
|
Result run(Function &F, FunctionAnalysisManager &AM) {
|
|
|
|
++Runs;
|
2016-12-27 16:40:39 +08:00
|
|
|
auto &FDep = AM.getResult<TestFunctionAnalysis>(F);
|
2020-01-15 02:27:20 +08:00
|
|
|
auto &MAMProxy = AM.getResult<ModuleAnalysisManagerFunctionProxy>(F);
|
2016-12-27 16:40:39 +08:00
|
|
|
// For the test, we insist that the module analysis starts off in the
|
2020-01-15 02:27:20 +08:00
|
|
|
// cache. Getting a cached result that isn't stateless trigger an assert.
|
|
|
|
// Use MAM, for the purposes of this unittest.
|
2016-12-27 16:40:39 +08:00
|
|
|
auto &MDep = *MAM.getCachedResult<TestModuleAnalysis>(*F.getParent());
|
|
|
|
// And register the dependency as module analysis dependencies have to be
|
|
|
|
// pre-registered on the proxy.
|
2020-01-15 02:27:20 +08:00
|
|
|
MAMProxy.registerOuterAnalysisInvalidation<TestModuleAnalysis,
|
|
|
|
TestIndirectFunctionAnalysis>();
|
2016-12-27 16:40:39 +08:00
|
|
|
return Result(FDep, MDep);
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
friend AnalysisInfoMixin<TestIndirectFunctionAnalysis>;
|
|
|
|
static AnalysisKey Key;
|
|
|
|
|
|
|
|
int &Runs;
|
2020-01-15 02:27:20 +08:00
|
|
|
ModuleAnalysisManager &MAM;
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
AnalysisKey TestIndirectFunctionAnalysis::Key;
|
|
|
|
|
2016-12-27 16:40:39 +08:00
|
|
|
/// A test analysis pass which chaches 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 TestDoublyIndirectFunctionAnalysis
|
|
|
|
: public AnalysisInfoMixin<TestDoublyIndirectFunctionAnalysis> {
|
|
|
|
struct Result {
|
|
|
|
Result(TestIndirectFunctionAnalysis::Result &IDep) : IDep(IDep) {}
|
|
|
|
TestIndirectFunctionAnalysis::Result &IDep;
|
|
|
|
|
|
|
|
bool invalidate(Function &F, const PreservedAnalyses &PA,
|
|
|
|
FunctionAnalysisManager::Invalidator &Inv) {
|
|
|
|
auto PAC = PA.getChecker<TestDoublyIndirectFunctionAnalysis>();
|
|
|
|
return !(PAC.preserved() ||
|
|
|
|
PAC.preservedSet<AllAnalysesOn<Function>>()) ||
|
|
|
|
Inv.invalidate<TestIndirectFunctionAnalysis>(F, PA);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
TestDoublyIndirectFunctionAnalysis(int &Runs) : Runs(Runs) {}
|
|
|
|
|
|
|
|
/// Run the analysis pass over the function and return a result.
|
|
|
|
Result run(Function &F, FunctionAnalysisManager &AM) {
|
|
|
|
++Runs;
|
|
|
|
auto &IDep = AM.getResult<TestIndirectFunctionAnalysis>(F);
|
|
|
|
return Result(IDep);
|
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
friend AnalysisInfoMixin<TestDoublyIndirectFunctionAnalysis>;
|
|
|
|
static AnalysisKey Key;
|
|
|
|
|
|
|
|
int &Runs;
|
|
|
|
};
|
|
|
|
|
|
|
|
AnalysisKey TestDoublyIndirectFunctionAnalysis::Key;
|
|
|
|
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
struct LambdaPass : public PassInfoMixin<LambdaPass> {
|
|
|
|
using FuncT = std::function<PreservedAnalyses(Function &, FunctionAnalysisManager &)>;
|
|
|
|
|
|
|
|
LambdaPass(FuncT Func) : Func(std::move(Func)) {}
|
|
|
|
|
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM) {
|
|
|
|
return Func(F, AM);
|
|
|
|
}
|
|
|
|
|
|
|
|
FuncT Func;
|
|
|
|
};
|
|
|
|
|
|
|
|
TEST_F(PassManagerTest, IndirectAnalysisInvalidation) {
|
2021-05-08 05:32:40 +08:00
|
|
|
FunctionAnalysisManager FAM;
|
|
|
|
ModuleAnalysisManager MAM;
|
2016-12-27 16:40:39 +08:00
|
|
|
int FunctionAnalysisRuns = 0, ModuleAnalysisRuns = 0,
|
|
|
|
IndirectAnalysisRuns = 0, DoublyIndirectAnalysisRuns = 0;
|
|
|
|
FAM.registerPass([&] { return TestFunctionAnalysis(FunctionAnalysisRuns); });
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
FAM.registerPass(
|
2020-01-15 02:27:20 +08:00
|
|
|
[&] { return TestIndirectFunctionAnalysis(IndirectAnalysisRuns, MAM); });
|
2016-12-27 16:40:39 +08:00
|
|
|
FAM.registerPass([&] {
|
|
|
|
return TestDoublyIndirectFunctionAnalysis(DoublyIndirectAnalysisRuns);
|
|
|
|
});
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
|
2016-12-27 16:40:39 +08:00
|
|
|
MAM.registerPass([&] { return TestModuleAnalysis(ModuleAnalysisRuns); });
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
MAM.registerPass([&] { return FunctionAnalysisManagerModuleProxy(FAM); });
|
|
|
|
FAM.registerPass([&] { return ModuleAnalysisManagerFunctionProxy(MAM); });
|
|
|
|
|
2018-09-21 01:08:45 +08:00
|
|
|
PassInstrumentationCallbacks PIC;
|
|
|
|
MAM.registerPass([&] { return PassInstrumentationAnalysis(&PIC); });
|
|
|
|
FAM.registerPass([&] { return PassInstrumentationAnalysis(&PIC); });
|
|
|
|
|
2016-12-27 16:40:39 +08:00
|
|
|
int InstrCount = 0, FunctionCount = 0;
|
2021-05-04 07:09:56 +08:00
|
|
|
ModulePassManager MPM;
|
|
|
|
FunctionPassManager FPM;
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
// First just use the analysis to get the instruction count, and preserve
|
|
|
|
// everything.
|
|
|
|
FPM.addPass(LambdaPass([&](Function &F, FunctionAnalysisManager &AM) {
|
2016-12-27 16:40:39 +08:00
|
|
|
auto &DoublyIndirectResult =
|
|
|
|
AM.getResult<TestDoublyIndirectFunctionAnalysis>(F);
|
|
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
|
|
InstrCount += IndirectResult.FDep.InstructionCount;
|
|
|
|
FunctionCount += IndirectResult.MDep.FunctionCount;
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
return PreservedAnalyses::all();
|
|
|
|
}));
|
|
|
|
// Next, invalidate
|
|
|
|
// - both analyses for "f",
|
|
|
|
// - just the underlying (indirect) analysis for "g", and
|
|
|
|
// - just the direct analysis for "h".
|
|
|
|
FPM.addPass(LambdaPass([&](Function &F, FunctionAnalysisManager &AM) {
|
2016-12-27 16:40:39 +08:00
|
|
|
auto &DoublyIndirectResult =
|
|
|
|
AM.getResult<TestDoublyIndirectFunctionAnalysis>(F);
|
|
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
|
|
InstrCount += IndirectResult.FDep.InstructionCount;
|
|
|
|
FunctionCount += IndirectResult.MDep.FunctionCount;
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
auto PA = PreservedAnalyses::none();
|
|
|
|
if (F.getName() == "g")
|
|
|
|
PA.preserve<TestFunctionAnalysis>();
|
|
|
|
else if (F.getName() == "h")
|
|
|
|
PA.preserve<TestIndirectFunctionAnalysis>();
|
|
|
|
return PA;
|
|
|
|
}));
|
|
|
|
// Finally, use the analysis again on each function, forcing re-computation
|
|
|
|
// for all of them.
|
|
|
|
FPM.addPass(LambdaPass([&](Function &F, FunctionAnalysisManager &AM) {
|
2016-12-27 16:40:39 +08:00
|
|
|
auto &DoublyIndirectResult =
|
|
|
|
AM.getResult<TestDoublyIndirectFunctionAnalysis>(F);
|
|
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
|
|
InstrCount += IndirectResult.FDep.InstructionCount;
|
|
|
|
FunctionCount += IndirectResult.MDep.FunctionCount;
|
|
|
|
return PreservedAnalyses::all();
|
|
|
|
}));
|
|
|
|
|
|
|
|
// Create a second function pass manager. This will cause the module-level
|
|
|
|
// invalidation to occur, which will force yet another invalidation of the
|
|
|
|
// indirect function-level analysis as the module analysis it depends on gets
|
|
|
|
// invalidated.
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM2;
|
2016-12-27 16:40:39 +08:00
|
|
|
FPM2.addPass(LambdaPass([&](Function &F, FunctionAnalysisManager &AM) {
|
|
|
|
auto &DoublyIndirectResult =
|
|
|
|
AM.getResult<TestDoublyIndirectFunctionAnalysis>(F);
|
|
|
|
auto &IndirectResult = DoublyIndirectResult.IDep;
|
|
|
|
InstrCount += IndirectResult.FDep.InstructionCount;
|
|
|
|
FunctionCount += IndirectResult.MDep.FunctionCount;
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
return PreservedAnalyses::all();
|
|
|
|
}));
|
2016-12-27 16:40:39 +08:00
|
|
|
|
|
|
|
// Add a requires pass to populate the module analysis and then our function
|
|
|
|
// pass pipeline.
|
|
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM)));
|
2016-12-27 16:40:39 +08:00
|
|
|
// Now require the module analysis again (it will have been invalidated once)
|
|
|
|
// and then use it again from a function pass manager.
|
|
|
|
MPM.addPass(RequireAnalysisPass<TestModuleAnalysis, Module>());
|
|
|
|
MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM2)));
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
MPM.run(*M, MAM);
|
|
|
|
|
|
|
|
// There are generally two possible runs for each of the three functions. But
|
|
|
|
// for one function, we only invalidate the indirect analysis so the base one
|
|
|
|
// only gets run five times.
|
2016-12-27 16:40:39 +08:00
|
|
|
EXPECT_EQ(5, FunctionAnalysisRuns);
|
|
|
|
// The module analysis pass should be run twice here.
|
|
|
|
EXPECT_EQ(2, ModuleAnalysisRuns);
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
// The indirect analysis is invalidated for each function (either directly or
|
|
|
|
// indirectly) and run twice for each.
|
2016-12-27 16:40:39 +08:00
|
|
|
EXPECT_EQ(9, IndirectAnalysisRuns);
|
|
|
|
EXPECT_EQ(9, DoublyIndirectAnalysisRuns);
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
|
2016-12-27 16:40:39 +08:00
|
|
|
// There are five instructions in the module and we add the count four
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
// times.
|
2016-12-27 16:40:39 +08:00
|
|
|
EXPECT_EQ(5 * 4, InstrCount);
|
|
|
|
|
|
|
|
// There are three functions and we count them four times for each of the
|
|
|
|
// three functions.
|
|
|
|
EXPECT_EQ(3 * 4 * 3, FunctionCount);
|
[PM] Extend the explicit 'invalidate' method API on analysis results to
accept an Invalidator that allows them to invalidate themselves if their
dependencies are in turn invalidated.
Rather than recording the dependency graph ahead of time when analysis
get results from other analyses, this simply lets each result trigger
the immediate invalidation of any analyses they actually depend on. They
do this in a way that has three nice properties:
1) They don't have to handle transitive dependencies because the
infrastructure will recurse for them.
2) The invalidate methods are still called only once. We just
dynamically discover the necessary topological ordering, everything
is memoized nicely.
3) The infrastructure still provides a default implementation and can
access it so that only analyses which have dependencies need to do
anything custom.
To make this work at all, the invalidation logic also has to defer the
deletion of the result objects themselves so that they can remain alive
until we have collected the complete set of results to invalidate.
A unittest is added here that has exactly the dependency pattern we are
concerned with. It hit the use-after-free described by Sean in much
detail in the long thread about analysis invalidation before this
change, and even in an intermediate form of this change where we failed
to defer the deletion of the result objects.
There is an important problem with doing dependency invalidation that
*isn't* solved here: we don't *enforce* that results correctly
invalidate all the analyses whose results they depend on.
I actually looked at what it would take to do that, and it isn't as hard
as I had thought but the complexity it introduces seems very likely to
outweigh the benefit. The technique would be to provide a base class for
an analysis result that would be populated with other results, and
automatically provide the invalidate method which immediately does the
correct thing. This approach has some nice pros IMO:
- Handles the case we care about and nothing else: only *results*
that depend on other analyses trigger extra invalidation.
- Localized to the result rather than centralized in the analysis
manager.
- Ties the storage of the reference to another result to the triggering
of the invalidation of that analysis.
- Still supports extending invalidation in customized ways.
But the down sides here are:
- Very heavy-weight meta-programming is needed to provide this base
class.
- Requires a pretty awful API for accessing the dependencies.
Ultimately, I fear it will not pull its weight. But we can re-evaluate
this at any point if we start discovering consistent problems where the
invalidation and dependencies get out of sync. It will fit as a clean
layer on top of the facilities in this patch that we can add if and when
we need it.
Note that I'm not really thrilled with the names for these APIs... The
name "Invalidator" seems ok but not great. The method name "invalidate"
also. In review some improvements were suggested, but they really need
*other* uses of these terms to be updated as well so I'm going to do
that in a follow-up commit.
I'm working on the actual fixes to various analyses that need to use
these, but I want to try to get tests for each of them so we don't
regress. And those changes are seperable and obvious so once this goes
in I should be able to roll them out throughout LLVM.
Many thanks to Sean, Justin, and others for help reviewing here.
Differential Revision: https://reviews.llvm.org/D23738
llvm-svn: 288077
2016-11-29 06:04:31 +08:00
|
|
|
}
|
2020-11-18 10:44:16 +08:00
|
|
|
|
|
|
|
// Run SimplifyCFGPass that makes CFG changes and reports PreservedAnalyses
|
|
|
|
// without CFGAnalyses. So the CFGChecker does not complain.
|
|
|
|
TEST_F(PassManagerTest, FunctionPassCFGChecker) {
|
|
|
|
LLVMContext Context;
|
|
|
|
// SimplifyCFG changes this function to
|
|
|
|
// define void @foo {next: ret void}
|
|
|
|
auto M = parseIR(Context, "define void @foo() {\n"
|
|
|
|
" br label %next\n"
|
|
|
|
"next:\n"
|
|
|
|
" br label %exit\n"
|
|
|
|
"exit:\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n");
|
|
|
|
|
|
|
|
auto *F = M->getFunction("foo");
|
2021-05-08 05:32:40 +08:00
|
|
|
FunctionAnalysisManager FAM;
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM;
|
2020-11-18 10:44:16 +08:00
|
|
|
PassInstrumentationCallbacks PIC;
|
|
|
|
StandardInstrumentations SI(/*DebugLogging*/ true);
|
2021-04-06 12:55:18 +08:00
|
|
|
SI.registerCallbacks(PIC, &FAM);
|
2020-11-18 10:44:16 +08:00
|
|
|
FAM.registerPass([&] { return PassInstrumentationAnalysis(&PIC); });
|
2021-01-01 22:06:56 +08:00
|
|
|
FAM.registerPass([&] { return DominatorTreeAnalysis(); });
|
2020-11-18 10:44:16 +08:00
|
|
|
FAM.registerPass([&] { return AssumptionAnalysis(); });
|
|
|
|
FAM.registerPass([&] { return TargetIRAnalysis(); });
|
|
|
|
|
|
|
|
FPM.addPass(SimplifyCFGPass());
|
|
|
|
FPM.run(*F, FAM);
|
|
|
|
}
|
|
|
|
|
|
|
|
// FunctionPass that manually invalidates analyses and always returns
|
|
|
|
// PreservedAnalyses::all().
|
|
|
|
struct TestSimplifyCFGInvalidatingAnalysisPass
|
|
|
|
: PassInfoMixin<TestSimplifyCFGInvalidatingAnalysisPass> {
|
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
|
|
|
|
// Run SimplifyCFG and if it changes CFG then invalidate the CFG analysis.
|
|
|
|
// This allows to return PreserveAnalysis::all().
|
|
|
|
PreservedAnalyses PA = CFGSimplifier.run(F, FAM);
|
|
|
|
FAM.invalidate(F, PA);
|
|
|
|
return PreservedAnalyses::all();
|
|
|
|
}
|
|
|
|
|
|
|
|
SimplifyCFGPass CFGSimplifier;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Run TestSimplifyCFGInvalidatingAnalysisPass which changes CFG by running
|
|
|
|
// SimplifyCFGPass then manually invalidates analyses and always returns
|
|
|
|
// PreservedAnalyses::all(). CFGChecker does not complain because it resets
|
|
|
|
// its saved CFG snapshot when the analyses are invalidated manually.
|
|
|
|
TEST_F(PassManagerTest, FunctionPassCFGCheckerInvalidateAnalysis) {
|
|
|
|
LLVMContext Context;
|
|
|
|
// SimplifyCFG changes this function to
|
|
|
|
// define void @foo {next: ret void}
|
|
|
|
auto M = parseIR(Context, "define void @foo() {\n"
|
|
|
|
" br label %next\n"
|
|
|
|
"next:\n"
|
|
|
|
" br label %exit\n"
|
|
|
|
"exit:\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n");
|
|
|
|
|
|
|
|
auto *F = M->getFunction("foo");
|
2021-05-08 05:32:40 +08:00
|
|
|
FunctionAnalysisManager FAM;
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM;
|
2020-11-18 10:44:16 +08:00
|
|
|
PassInstrumentationCallbacks PIC;
|
|
|
|
StandardInstrumentations SI(/*DebugLogging*/ true);
|
2021-04-06 12:55:18 +08:00
|
|
|
SI.registerCallbacks(PIC, &FAM);
|
2020-11-18 10:44:16 +08:00
|
|
|
FAM.registerPass([&] { return PassInstrumentationAnalysis(&PIC); });
|
2021-01-01 22:06:56 +08:00
|
|
|
FAM.registerPass([&] { return DominatorTreeAnalysis(); });
|
2020-11-18 10:44:16 +08:00
|
|
|
FAM.registerPass([&] { return AssumptionAnalysis(); });
|
|
|
|
FAM.registerPass([&] { return TargetIRAnalysis(); });
|
|
|
|
|
|
|
|
FPM.addPass(TestSimplifyCFGInvalidatingAnalysisPass());
|
|
|
|
FPM.run(*F, FAM);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wrap a FunctionPassManager running SimplifyCFG pass with another
|
|
|
|
// FunctionPassManager.
|
|
|
|
struct TestSimplifyCFGWrapperPass : PassInfoMixin<TestSimplifyCFGWrapperPass> {
|
|
|
|
TestSimplifyCFGWrapperPass(FunctionPassManager &InnerPM) : InnerPM(InnerPM) {}
|
|
|
|
|
|
|
|
PreservedAnalyses run(Function &F, FunctionAnalysisManager &FAM) {
|
|
|
|
// Here we simulate exactly what FunctionPassManager::run() does but
|
|
|
|
// instead of running all passes from InnerPM.Passes we run them in bulk
|
|
|
|
// by calling InnerPM.run().
|
|
|
|
PreservedAnalyses PA = PreservedAnalyses::all();
|
|
|
|
PassInstrumentation PI = FAM.getResult<PassInstrumentationAnalysis>(F);
|
|
|
|
|
|
|
|
if (!PI.runBeforePass<Function>(InnerPM, F))
|
|
|
|
return PreservedAnalyses::all();
|
|
|
|
|
|
|
|
PreservedAnalyses PassPA = InnerPM.run(F, FAM);
|
|
|
|
PI.runAfterPass(InnerPM, F, PassPA);
|
|
|
|
FAM.invalidate(F, PassPA);
|
|
|
|
PA.intersect(PassPA);
|
|
|
|
PA.preserveSet<AllAnalysesOn<Function>>();
|
|
|
|
return PA;
|
|
|
|
}
|
|
|
|
|
|
|
|
FunctionPassManager &InnerPM;
|
|
|
|
};
|
|
|
|
|
|
|
|
// Run TestSimplifyCFGWrapperPass which simulates behavior of
|
|
|
|
// FunctionPassManager::run() except that it runs all passes at once by calling
|
|
|
|
// an inner pass manager's passes with PassManager::run(). This is how one pass
|
|
|
|
// manager is expected to wrap another pass manager.
|
|
|
|
// SimplifyCFGPass, which is called by the inner pass manager, changes the CFG.
|
|
|
|
// The CFGChecker's AfterPassCallback, run right after SimplifyCFGPass, does not
|
|
|
|
// complain because CFGAnalyses is not in the PreservedAnalises set returned by
|
|
|
|
// SimplifyCFGPass. Then the CFG analysis is invalidated by the analysis manager
|
|
|
|
// according to the PreservedAnalises set. Further calls to CFGChecker's
|
|
|
|
// AfterPassCallback see that all analyses for the current function are
|
|
|
|
// preserved but there is no CFG snapshot available (i.e.
|
|
|
|
// AM.getCachedResult<PreservedCFGCheckerAnalysis>(F) returns nullptr).
|
|
|
|
TEST_F(PassManagerTest, FunctionPassCFGCheckerWrapped) {
|
|
|
|
LLVMContext Context;
|
|
|
|
// SimplifyCFG changes this function to
|
|
|
|
// define void @foo {next: ret void}
|
|
|
|
auto M = parseIR(Context, "define void @foo() {\n"
|
|
|
|
" br label %next\n"
|
|
|
|
"next:\n"
|
|
|
|
" br label %exit\n"
|
|
|
|
"exit:\n"
|
|
|
|
" ret void\n"
|
|
|
|
"}\n");
|
|
|
|
|
|
|
|
auto *F = M->getFunction("foo");
|
2021-05-08 05:32:40 +08:00
|
|
|
FunctionAnalysisManager FAM;
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager FPM;
|
2020-11-18 10:44:16 +08:00
|
|
|
PassInstrumentationCallbacks PIC;
|
|
|
|
StandardInstrumentations SI(/*DebugLogging*/ true);
|
2021-04-06 12:55:18 +08:00
|
|
|
SI.registerCallbacks(PIC, &FAM);
|
2020-11-18 10:44:16 +08:00
|
|
|
FAM.registerPass([&] { return PassInstrumentationAnalysis(&PIC); });
|
2021-01-01 22:06:56 +08:00
|
|
|
FAM.registerPass([&] { return DominatorTreeAnalysis(); });
|
2020-11-18 10:44:16 +08:00
|
|
|
FAM.registerPass([&] { return AssumptionAnalysis(); });
|
|
|
|
FAM.registerPass([&] { return TargetIRAnalysis(); });
|
|
|
|
|
2021-05-04 07:09:56 +08:00
|
|
|
FunctionPassManager InnerFPM;
|
2020-11-18 10:44:16 +08:00
|
|
|
InnerFPM.addPass(SimplifyCFGPass());
|
|
|
|
|
|
|
|
FPM.addPass(TestSimplifyCFGWrapperPass(InnerFPM));
|
|
|
|
FPM.run(*F, FAM);
|
|
|
|
}
|
2013-11-09 21:09:08 +08:00
|
|
|
}
|