llvm-project/llvm/unittests/IR/PassBuilderCallbacksTest.cpp

521 lines
19 KiB
C++
Raw Normal View History

//===- unittests/IR/PassBuilderCallbacksTest.cpp - PB Callback Tests --===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <llvm/Analysis/CGSCCPassManager.h>
#include <llvm/Analysis/LoopAnalysisManager.h>
#include <llvm/AsmParser/Parser.h>
#include <llvm/IR/LLVMContext.h>
#include <llvm/IR/PassManager.h>
#include <llvm/Passes/PassBuilder.h>
#include <llvm/Support/SourceMgr.h>
#include <llvm/Transforms/Scalar/LoopPassManager.h>
using namespace llvm;
namespace llvm {
/// Provide an ostream operator for StringRef.
///
/// For convenience we provide a custom matcher below for IRUnit's and analysis
/// result's getName functions, which most of the time returns a StringRef. The
/// matcher makes use of this operator.
static std::ostream &operator<<(std::ostream &O, StringRef S) {
return O << S.str();
}
}
namespace {
using testing::DoDefault;
using testing::Return;
using testing::Expectation;
using testing::Invoke;
using testing::WithArgs;
using testing::_;
/// A CRTP base for analysis mock handles
///
/// This class reconciles mocking with the value semantics implementation of the
/// AnalysisManager. Analysis mock handles should derive from this class and
/// call \c setDefault() in their constroctur for wiring up the defaults defined
/// by this base with their mock run() and invalidate() implementations.
template <typename DerivedT, typename IRUnitT,
typename AnalysisManagerT = AnalysisManager<IRUnitT>,
typename... ExtraArgTs>
class MockAnalysisHandleBase {
public:
class Analysis : public AnalysisInfoMixin<Analysis> {
friend AnalysisInfoMixin<Analysis>;
friend MockAnalysisHandleBase;
static AnalysisKey Key;
DerivedT *Handle;
Analysis(DerivedT &Handle) : Handle(&Handle) {
static_assert(std::is_base_of<MockAnalysisHandleBase, DerivedT>::value,
"Must pass the derived type to this template!");
}
public:
class Result {
friend MockAnalysisHandleBase;
DerivedT *Handle;
Result(DerivedT &Handle) : Handle(&Handle) {}
public:
// Forward invalidation events to the mock handle.
bool invalidate(IRUnitT &IR, const PreservedAnalyses &PA,
typename AnalysisManagerT::Invalidator &Inv) {
return Handle->invalidate(IR, PA, Inv);
}
};
Result run(IRUnitT &IR, AnalysisManagerT &AM, ExtraArgTs... ExtraArgs) {
return Handle->run(IR, AM, ExtraArgs...);
}
};
Analysis getAnalysis() { return Analysis(static_cast<DerivedT &>(*this)); }
typename Analysis::Result getResult() {
return typename Analysis::Result(static_cast<DerivedT &>(*this));
}
protected:
// FIXME: MSVC seems unable to handle a lambda argument to Invoke from within
// the template, so we use a boring static function.
static bool invalidateCallback(IRUnitT &IR, const PreservedAnalyses &PA,
typename AnalysisManagerT::Invalidator &Inv) {
auto PAC = PA.template getChecker<Analysis>();
return !PAC.preserved() &&
!PAC.template preservedSet<AllAnalysesOn<IRUnitT>>();
}
/// Derived classes should call this in their constructor to set up default
/// mock actions. (We can't do this in our constructor because this has to
/// run after the DerivedT is constructed.)
void setDefaults() {
ON_CALL(static_cast<DerivedT &>(*this),
run(_, _, testing::Matcher<ExtraArgTs>(_)...))
.WillByDefault(Return(this->getResult()));
ON_CALL(static_cast<DerivedT &>(*this), invalidate(_, _, _))
.WillByDefault(Invoke(&invalidateCallback));
}
};
/// A CRTP base for pass mock handles
///
/// This class reconciles mocking with the value semantics implementation of the
/// PassManager. Pass mock handles should derive from this class and
/// call \c setDefault() in their constroctur for wiring up the defaults defined
/// by this base with their mock run() and invalidate() implementations.
template <typename DerivedT, typename IRUnitT, typename AnalysisManagerT,
typename... ExtraArgTs>
AnalysisKey MockAnalysisHandleBase<DerivedT, IRUnitT, AnalysisManagerT,
ExtraArgTs...>::Analysis::Key;
template <typename DerivedT, typename IRUnitT,
typename AnalysisManagerT = AnalysisManager<IRUnitT>,
typename... ExtraArgTs>
class MockPassHandleBase {
public:
class Pass : public PassInfoMixin<Pass> {
friend MockPassHandleBase;
DerivedT *Handle;
Pass(DerivedT &Handle) : Handle(&Handle) {
static_assert(std::is_base_of<MockPassHandleBase, DerivedT>::value,
"Must pass the derived type to this template!");
}
public:
PreservedAnalyses run(IRUnitT &IR, AnalysisManagerT &AM,
ExtraArgTs... ExtraArgs) {
return Handle->run(IR, AM, ExtraArgs...);
}
};
Pass getPass() { return Pass(static_cast<DerivedT &>(*this)); }
protected:
/// Derived classes should call this in their constructor to set up default
/// mock actions. (We can't do this in our constructor because this has to
/// run after the DerivedT is constructed.)
void setDefaults() {
ON_CALL(static_cast<DerivedT &>(*this),
run(_, _, testing::Matcher<ExtraArgTs>(_)...))
.WillByDefault(Return(PreservedAnalyses::all()));
}
};
/// Mock handles for passes for the IRUnits Module, CGSCC, Function, Loop.
/// These handles define the appropriate run() mock interface for the respective
/// IRUnit type.
template <typename IRUnitT> struct MockPassHandle;
template <>
struct MockPassHandle<Loop>
: MockPassHandleBase<MockPassHandle<Loop>, Loop, LoopAnalysisManager,
LoopStandardAnalysisResults &, LPMUpdater &> {
MOCK_METHOD4(run,
PreservedAnalyses(Loop &, LoopAnalysisManager &,
LoopStandardAnalysisResults &, LPMUpdater &));
MockPassHandle() { setDefaults(); }
};
template <>
struct MockPassHandle<Function>
: MockPassHandleBase<MockPassHandle<Function>, Function> {
MOCK_METHOD2(run, PreservedAnalyses(Function &, FunctionAnalysisManager &));
MockPassHandle() { setDefaults(); }
};
template <>
struct MockPassHandle<LazyCallGraph::SCC>
: MockPassHandleBase<MockPassHandle<LazyCallGraph::SCC>, LazyCallGraph::SCC,
CGSCCAnalysisManager, LazyCallGraph &,
CGSCCUpdateResult &> {
MOCK_METHOD4(run,
PreservedAnalyses(LazyCallGraph::SCC &, CGSCCAnalysisManager &,
LazyCallGraph &G, CGSCCUpdateResult &UR));
MockPassHandle() { setDefaults(); }
};
template <>
struct MockPassHandle<Module>
: MockPassHandleBase<MockPassHandle<Module>, Module> {
MOCK_METHOD2(run, PreservedAnalyses(Module &, ModuleAnalysisManager &));
MockPassHandle() { setDefaults(); }
};
/// Mock handles for analyses for the IRUnits Module, CGSCC, Function, Loop.
/// These handles define the appropriate run() and invalidate() mock interfaces
/// for the respective IRUnit type.
template <typename IRUnitT> struct MockAnalysisHandle;
template <>
struct MockAnalysisHandle<Loop>
: MockAnalysisHandleBase<MockAnalysisHandle<Loop>, Loop,
LoopAnalysisManager,
LoopStandardAnalysisResults &> {
MOCK_METHOD3_T(run, typename Analysis::Result(Loop &, LoopAnalysisManager &,
LoopStandardAnalysisResults &));
MOCK_METHOD3_T(invalidate, bool(Loop &, const PreservedAnalyses &,
LoopAnalysisManager::Invalidator &));
MockAnalysisHandle<Loop>() { this->setDefaults(); }
};
template <>
struct MockAnalysisHandle<Function>
: MockAnalysisHandleBase<MockAnalysisHandle<Function>, Function> {
MOCK_METHOD2(run, Analysis::Result(Function &, FunctionAnalysisManager &));
MOCK_METHOD3(invalidate, bool(Function &, const PreservedAnalyses &,
FunctionAnalysisManager::Invalidator &));
MockAnalysisHandle<Function>() { setDefaults(); }
};
template <>
struct MockAnalysisHandle<LazyCallGraph::SCC>
: MockAnalysisHandleBase<MockAnalysisHandle<LazyCallGraph::SCC>,
LazyCallGraph::SCC, CGSCCAnalysisManager,
LazyCallGraph &> {
MOCK_METHOD3(run, Analysis::Result(LazyCallGraph::SCC &,
CGSCCAnalysisManager &, LazyCallGraph &));
MOCK_METHOD3(invalidate, bool(LazyCallGraph::SCC &, const PreservedAnalyses &,
CGSCCAnalysisManager::Invalidator &));
MockAnalysisHandle<LazyCallGraph::SCC>() { setDefaults(); }
};
template <>
struct MockAnalysisHandle<Module>
: MockAnalysisHandleBase<MockAnalysisHandle<Module>, Module> {
MOCK_METHOD2(run, Analysis::Result(Module &, ModuleAnalysisManager &));
MOCK_METHOD3(invalidate, bool(Module &, const PreservedAnalyses &,
ModuleAnalysisManager::Invalidator &));
MockAnalysisHandle<Module>() { setDefaults(); }
};
static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) {
SMDiagnostic Err;
return parseAssemblyString(IR, Err, C);
}
template <typename PassManagerT> class PassBuilderCallbacksTest;
/// This test fixture is shared between all the actual tests below and
/// takes care of setting up appropriate defaults.
///
/// The template specialization serves to extract the IRUnit and AM types from
/// the given PassManagerT.
template <typename TestIRUnitT, typename... ExtraPassArgTs,
typename... ExtraAnalysisArgTs>
class PassBuilderCallbacksTest<PassManager<
TestIRUnitT, AnalysisManager<TestIRUnitT, ExtraAnalysisArgTs...>,
ExtraPassArgTs...>> : public testing::Test {
protected:
using IRUnitT = TestIRUnitT;
using AnalysisManagerT = AnalysisManager<TestIRUnitT, ExtraAnalysisArgTs...>;
using PassManagerT =
PassManager<TestIRUnitT, AnalysisManagerT, ExtraPassArgTs...>;
using AnalysisT = typename MockAnalysisHandle<IRUnitT>::Analysis;
LLVMContext Context;
std::unique_ptr<Module> M;
PassBuilder PB;
ModulePassManager PM;
LoopAnalysisManager LAM;
FunctionAnalysisManager FAM;
CGSCCAnalysisManager CGAM;
ModuleAnalysisManager AM;
MockPassHandle<IRUnitT> PassHandle;
MockAnalysisHandle<IRUnitT> AnalysisHandle;
static PreservedAnalyses getAnalysisResult(IRUnitT &U, AnalysisManagerT &AM,
ExtraAnalysisArgTs &&... Args) {
(void)AM.template getResult<AnalysisT>(
U, std::forward<ExtraAnalysisArgTs>(Args)...);
return PreservedAnalyses::all();
}
PassBuilderCallbacksTest()
: M(parseIR(Context,
"declare void @bar()\n"
"define void @foo(i32 %n) {\n"
"entry:\n"
" br label %loop\n"
"loop:\n"
" %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]\n"
" %iv.next = add i32 %iv, 1\n"
" tail call void @bar()\n"
" %cmp = icmp eq i32 %iv, %n\n"
" br i1 %cmp, label %exit, label %loop\n"
"exit:\n"
" ret void\n"
"}\n")),
PM(true), LAM(true), FAM(true), CGAM(true), AM(true) {
/// Register a callback for analysis registration.
///
/// The callback is a function taking a reference to an AnalyisManager
/// object. When called, the callee gets to register its own analyses with
/// this PassBuilder instance.
PB.registerAnalysisRegistrationCallback([this](AnalysisManagerT &AM) {
// Register our mock analysis
AM.registerPass([this] { return AnalysisHandle.getAnalysis(); });
});
/// Register a callback for pipeline parsing.
///
/// During parsing of a textual pipeline, the PassBuilder will call these
/// callbacks for each encountered pass name that it does not know. This
/// includes both simple pass names as well as names of sub-pipelines. In
/// the latter case, the InnerPipeline is not empty.
PB.registerPipelineParsingCallback(
[this](StringRef Name, PassManagerT &PM,
ArrayRef<PassBuilder::PipelineElement> InnerPipeline) {
/// Handle parsing of the names of analysis utilities such as
/// require<test-analysis> and invalidate<test-analysis> for our
/// analysis mock handle
if (parseAnalysisUtilityPasses<AnalysisT>("test-analysis", Name, PM))
return true;
/// Parse the name of our pass mock handle
if (Name == "test-transform") {
PM.addPass(PassHandle.getPass());
return true;
}
return false;
});
/// Register builtin analyses and cross-register the analysis proxies
PB.registerModuleAnalyses(AM);
PB.registerCGSCCAnalyses(CGAM);
PB.registerFunctionAnalyses(FAM);
PB.registerLoopAnalyses(LAM);
PB.crossRegisterProxies(LAM, FAM, CGAM, AM);
}
};
/// Define a custom matcher for objects which support a 'getName' method.
///
/// LLVM often has IR objects or analysis objects which expose a name
/// and in tests it is convenient to match these by name for readability.
/// Usually, this name is either a StringRef or a plain std::string. This
/// matcher supports any type exposing a getName() method of this form whose
/// return value is compatible with an std::ostream. For StringRef, this uses
/// the shift operator defined above.
///
/// It should be used as:
///
/// HasName("my_function")
///
/// No namespace or other qualification is required.
MATCHER_P(HasName, Name, "") {
*result_listener << "has name '" << arg.getName() << "'";
return Name == arg.getName();
}
using ModuleCallbacksTest = PassBuilderCallbacksTest<ModulePassManager>;
using CGSCCCallbacksTest = PassBuilderCallbacksTest<CGSCCPassManager>;
using FunctionCallbacksTest = PassBuilderCallbacksTest<FunctionPassManager>;
using LoopCallbacksTest = PassBuilderCallbacksTest<LoopPassManager>;
/// Test parsing of the name of our mock pass for all IRUnits.
///
/// The pass should by default run our mock analysis and then preserve it.
TEST_F(ModuleCallbacksTest, Passes) {
EXPECT_CALL(AnalysisHandle, run(HasName("<string>"), _));
EXPECT_CALL(PassHandle, run(HasName("<string>"), _))
.WillOnce(Invoke(getAnalysisResult));
StringRef PipelineText = "test-transform";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
TEST_F(FunctionCallbacksTest, Passes) {
EXPECT_CALL(AnalysisHandle, run(HasName("foo"), _));
EXPECT_CALL(PassHandle, run(HasName("foo"), _))
.WillOnce(Invoke(getAnalysisResult));
StringRef PipelineText = "test-transform";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
TEST_F(LoopCallbacksTest, Passes) {
EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _));
EXPECT_CALL(PassHandle, run(HasName("loop"), _, _, _))
.WillOnce(WithArgs<0, 1, 2>(Invoke(getAnalysisResult)));
StringRef PipelineText = "test-transform";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
TEST_F(CGSCCCallbacksTest, Passes) {
EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _));
EXPECT_CALL(PassHandle, run(HasName("(foo)"), _, _, _))
.WillOnce(WithArgs<0, 1, 2>(Invoke(getAnalysisResult)));
StringRef PipelineText = "test-transform";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
/// Test parsing of the names of analysis utilities for our mock analysis
/// for all IRUnits.
///
/// We first require<>, then invalidate<> it, expecting the analysis to be run
/// once and subsequently invalidated.
TEST_F(ModuleCallbacksTest, AnalysisUtilities) {
EXPECT_CALL(AnalysisHandle, run(HasName("<string>"), _));
EXPECT_CALL(AnalysisHandle, invalidate(HasName("<string>"), _, _));
StringRef PipelineText = "require<test-analysis>,invalidate<test-analysis>";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
TEST_F(CGSCCCallbacksTest, PassUtilities) {
EXPECT_CALL(AnalysisHandle, run(HasName("(foo)"), _, _));
EXPECT_CALL(AnalysisHandle, invalidate(HasName("(foo)"), _, _));
StringRef PipelineText = "require<test-analysis>,invalidate<test-analysis>";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
TEST_F(FunctionCallbacksTest, AnalysisUtilities) {
EXPECT_CALL(AnalysisHandle, run(HasName("foo"), _));
EXPECT_CALL(AnalysisHandle, invalidate(HasName("foo"), _, _));
StringRef PipelineText = "require<test-analysis>,invalidate<test-analysis>";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
TEST_F(LoopCallbacksTest, PassUtilities) {
EXPECT_CALL(AnalysisHandle, run(HasName("loop"), _, _));
EXPECT_CALL(AnalysisHandle, invalidate(HasName("loop"), _, _));
StringRef PipelineText = "require<test-analysis>,invalidate<test-analysis>";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
}
/// Test parsing of the top-level pipeline.
///
/// The ParseTopLevelPipeline callback takes over parsing of the entire pipeline
/// from PassBuilder if it encounters an unknown pipeline entry at the top level
/// (i.e., the first entry on the pipeline).
/// This test parses a pipeline named 'another-pipeline', whose only elements
/// may be the test-transform pass or the analysis utilities
TEST_F(ModuleCallbacksTest, ParseTopLevelPipeline) {
PB.registerParseTopLevelPipelineCallback([this](
ModulePassManager &MPM, ArrayRef<PassBuilder::PipelineElement> Pipeline,
bool VerifyEachPass, bool DebugLogging) {
auto &FirstName = Pipeline.front().Name;
auto &InnerPipeline = Pipeline.front().InnerPipeline;
if (FirstName == "another-pipeline") {
for (auto &E : InnerPipeline) {
if (parseAnalysisUtilityPasses<AnalysisT>("test-analysis", E.Name, PM))
continue;
if (E.Name == "test-transform") {
PM.addPass(PassHandle.getPass());
continue;
}
return false;
}
}
return true;
});
EXPECT_CALL(AnalysisHandle, run(HasName("<string>"), _));
EXPECT_CALL(PassHandle, run(HasName("<string>"), _))
.WillOnce(Invoke(getAnalysisResult));
EXPECT_CALL(AnalysisHandle, invalidate(HasName("<string>"), _, _));
StringRef PipelineText =
"another-pipeline(test-transform,invalidate<test-analysis>)";
ASSERT_TRUE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
PM.run(*M, AM);
/// Test the negative case
PipelineText = "another-pipeline(instcombine)";
ASSERT_FALSE(PB.parsePassPipeline(PM, PipelineText, true))
<< "Pipeline was: " << PipelineText;
}
} // end anonymous namespace