Please use GitHub pull requests for new patches. Avoid migrating existing patches. Phabricator shutdown timeline
Changeset View
Changeset View
Standalone View
Standalone View
llvm/unittests/Analysis/PluginInlineOrderAnalysisTest.cpp
- This file was copied from llvm/unittests/Analysis/PluginInlineAdvisorAnalysisTest.cpp.
#include "llvm/Analysis/CallGraph.h" | #include "llvm/Analysis/CallGraph.h" | ||||
#include "llvm/AsmParser/Parser.h" | #include "llvm/AsmParser/Parser.h" | ||||
#include "llvm/Config/config.h" | #include "llvm/Config/config.h" | ||||
#include "llvm/Passes/PassBuilder.h" | #include "llvm/Passes/PassBuilder.h" | ||||
#include "llvm/Passes/PassPlugin.h" | #include "llvm/Passes/PassPlugin.h" | ||||
#include "llvm/Support/CommandLine.h" | #include "llvm/Support/CommandLine.h" | ||||
#include "llvm/Support/RandomNumberGenerator.h" | |||||
kazu: How are we using this? | |||||
#include "llvm/Support/raw_ostream.h" | #include "llvm/Support/raw_ostream.h" | ||||
#include "llvm/Testing/Support/Error.h" | #include "llvm/Testing/Support/Error.h" | ||||
#include "gtest/gtest.h" | #include "gtest/gtest.h" | ||||
#include "llvm/Analysis/InlineOrder.h" | |||||
namespace llvm { | namespace llvm { | ||||
void anchor() {} | namespace { | ||||
static void anchor() {} | |||||
Could we drop static inside the anonymous namespace? kazu: Could we drop `static` inside the anonymous namespace? | |||||
static std::string libPath(const std::string Name = "InlineAdvisorPlugin") { | std::string libPath(const std::string Name = "InlineOrderPlugin") { | ||||
const auto &Argvs = testing::internal::GetArgvs(); | const auto &Argvs = testing::internal::GetArgvs(); | ||||
const char *Argv0 = | const char *Argv0 = | ||||
Argvs.size() > 0 ? Argvs[0].c_str() : "PluginInlineAdvisorAnalysisTest"; | Argvs.size() > 0 ? Argvs[0].c_str() : "PluginInlineOrderAnalysisTest"; | ||||
void *Ptr = (void *)(intptr_t)anchor; | void *Ptr = (void *)(intptr_t)anchor; | ||||
std::string Path = sys::fs::getMainExecutable(Argv0, Ptr); | std::string Path = sys::fs::getMainExecutable(Argv0, Ptr); | ||||
llvm::SmallString<256> Buf{sys::path::parent_path(Path)}; | llvm::SmallString<256> Buf{sys::path::parent_path(Path)}; | ||||
sys::path::append(Buf, (Name + LLVM_PLUGIN_EXT).c_str()); | sys::path::append(Buf, (Name + LLVM_PLUGIN_EXT).c_str()); | ||||
return std::string(Buf.str()); | return std::string(Buf.str()); | ||||
} | } | ||||
// Example of a custom InlineAdvisor that only inlines calls to functions called | |||||
// "foo". | |||||
class FooOnlyInlineAdvisor : public InlineAdvisor { | |||||
public: | |||||
FooOnlyInlineAdvisor(Module &M, FunctionAnalysisManager &FAM, | |||||
InlineParams Params, InlineContext IC) | |||||
: InlineAdvisor(M, FAM, IC) {} | |||||
std::unique_ptr<InlineAdvice> getAdviceImpl(CallBase &CB) override { | |||||
if (CB.getCalledFunction()->getName() == "foo") | |||||
return std::make_unique<InlineAdvice>(this, CB, getCallerORE(CB), true); | |||||
return std::make_unique<InlineAdvice>(this, CB, getCallerORE(CB), false); | |||||
} | |||||
}; | |||||
static InlineAdvisor *fooOnlyFactory(Module &M, FunctionAnalysisManager &FAM, | |||||
InlineParams Params, InlineContext IC) { | |||||
return new FooOnlyInlineAdvisor(M, FAM, Params, IC); | |||||
} | |||||
struct CompilerInstance { | struct CompilerInstance { | ||||
LLVMContext Ctx; | LLVMContext Ctx; | ||||
ModulePassManager MPM; | ModulePassManager MPM; | ||||
InlineParams IP; | InlineParams IP; | ||||
PassBuilder PB; | PassBuilder PB; | ||||
LoopAnalysisManager LAM; | LoopAnalysisManager LAM; | ||||
FunctionAnalysisManager FAM; | FunctionAnalysisManager FAM; | ||||
CGSCCAnalysisManager CGAM; | CGSCCAnalysisManager CGAM; | ||||
ModuleAnalysisManager MAM; | ModuleAnalysisManager MAM; | ||||
SMDiagnostic Error; | SMDiagnostic Error; | ||||
// connect the plugin to our compiler instance | // connect the plugin to our compiler instance | ||||
void setupPlugin() { | void setupPlugin() { | ||||
auto PluginPath = libPath(); | auto PluginPath = libPath(); | ||||
ASSERT_NE("", PluginPath); | ASSERT_NE("", PluginPath); | ||||
Expected<PassPlugin> Plugin = PassPlugin::Load(PluginPath); | Expected<PassPlugin> Plugin = PassPlugin::Load(PluginPath); | ||||
ASSERT_TRUE(!!Plugin) << "Plugin path: " << PluginPath; | ASSERT_TRUE(!!Plugin) << "Plugin path: " << PluginPath; | ||||
Plugin->registerPassBuilderCallbacks(PB); | Plugin->registerPassBuilderCallbacks(PB); | ||||
ASSERT_THAT_ERROR(PB.parsePassPipeline(MPM, "dynamic-inline-advisor"), | ASSERT_THAT_ERROR(PB.parsePassPipeline(MPM, "dynamic-inline-order"), | ||||
Succeeded()); | Succeeded()); | ||||
} | } | ||||
// connect the FooOnlyInlineAdvisor to our compiler instance | |||||
void setupFooOnly() { | |||||
MAM.registerPass( | |||||
[&] { return PluginInlineAdvisorAnalysis(fooOnlyFactory); }); | |||||
} | |||||
CompilerInstance() { | CompilerInstance() { | ||||
IP = getInlineParams(3, 0); | IP = getInlineParams(3, 0); | ||||
PB.registerModuleAnalyses(MAM); | PB.registerModuleAnalyses(MAM); | ||||
PB.registerCGSCCAnalyses(CGAM); | PB.registerCGSCCAnalyses(CGAM); | ||||
PB.registerFunctionAnalyses(FAM); | PB.registerFunctionAnalyses(FAM); | ||||
PB.registerLoopAnalyses(LAM); | PB.registerLoopAnalyses(LAM); | ||||
PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); | PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); | ||||
MPM.addPass(ModuleInlinerPass(IP, InliningAdvisorMode::Default, | MPM.addPass(ModuleInlinerPass(IP, InliningAdvisorMode::Default, | ||||
ThinOrFullLTOPhase::None)); | ThinOrFullLTOPhase::None)); | ||||
} | } | ||||
~CompilerInstance() { | |||||
// reset the static variable that tracks if the plugin has been registered | |||||
// this is needed to allow the test to run multiple times | |||||
Could you capitalize the first letter of the sentence and put a period at the end. Easier to parse that way. kazu: Could you capitalize the first letter of the sentence and put a period at the end. Easier to… | |||||
PluginInlineOrderAnalysis::HasBeenRegistered = false; | |||||
} | |||||
std::string output; | std::string output; | ||||
std::unique_ptr<Module> outputM; | std::unique_ptr<Module> outputM; | ||||
// run with the default inliner | // run with the dynamic inline order | ||||
auto run_default(StringRef IR) { | auto run(StringRef IR) { | ||||
PluginInlineAdvisorAnalysis::HasBeenRegistered = false; | |||||
outputM = parseAssemblyString(IR, Error, Ctx); | |||||
MPM.run(*outputM, MAM); | |||||
ASSERT_TRUE(outputM); | |||||
output.clear(); | |||||
raw_string_ostream o_stream{output}; | |||||
outputM->print(o_stream, nullptr); | |||||
ASSERT_TRUE(true); | |||||
} | |||||
// run with the dnamic inliner | |||||
auto run_dynamic(StringRef IR) { | |||||
// note typically the constructor for the DynamicInlineAdvisorAnalysis | |||||
// will automatically set this to true, we controll it here only to | |||||
// altenate between the default and dynamic inliner in our test | |||||
PluginInlineAdvisorAnalysis::HasBeenRegistered = true; | |||||
outputM = parseAssemblyString(IR, Error, Ctx); | outputM = parseAssemblyString(IR, Error, Ctx); | ||||
MPM.run(*outputM, MAM); | MPM.run(*outputM, MAM); | ||||
ASSERT_TRUE(outputM); | ASSERT_TRUE(outputM); | ||||
output.clear(); | output.clear(); | ||||
raw_string_ostream o_stream{output}; | raw_string_ostream o_stream{output}; | ||||
outputM->print(o_stream, nullptr); | outputM->print(o_stream, nullptr); | ||||
ASSERT_TRUE(true); | ASSERT_TRUE(true); | ||||
} | } | ||||
Show All 39 Lines | define void @f5() { | ||||
)", | )", | ||||
// test with 2 mutually recursive functions and 1 function with a loop | // test with 2 mutually recursive functions and 1 function with a loop | ||||
R"( | R"( | ||||
define void @f1() { | define void @f1() { | ||||
call void @f2() | call void @f2() | ||||
ret void | ret void | ||||
} | } | ||||
define void @f2() { | define void @f2() { | ||||
call void @f3() | call void @foo() | ||||
ret void | ret void | ||||
} | } | ||||
define void @f3() { | define void @foo() { | ||||
call void @f1() | call void @f1() | ||||
ret void | ret void | ||||
} | } | ||||
define void @f4() { | define void @f4() { | ||||
br label %loop | br label %loop | ||||
loop: | loop: | ||||
call void @f5() | call void @f5() | ||||
br label %loop | br label %loop | ||||
Show All 27 Lines | loop_body: | ||||
%add2 = add i32 %i_val2, 1 | %add2 = add i32 %i_val2, 1 | ||||
store i32 %add2, i32* %i | store i32 %add2, i32* %i | ||||
br label %loop_cond | br label %loop_cond | ||||
loop_end: | loop_end: | ||||
%curr_val3 = load i32, i32* %curr | %curr_val3 = load i32, i32* %curr | ||||
ret i32 %curr_val3 | ret i32 %curr_val3 | ||||
} | } | ||||
define i32 @fib_rec(i32 %n){ | define i32 @foo(i32 %n){ | ||||
%cmp = icmp eq i32 %n, 0 | %cmp = icmp eq i32 %n, 0 | ||||
%cmp2 = icmp eq i32 %n, 1 | %cmp2 = icmp eq i32 %n, 1 | ||||
%or = or i1 %cmp, %cmp2 | %or = or i1 %cmp, %cmp2 | ||||
br i1 %or, label %if_true, label %if_false | br i1 %or, label %if_true, label %if_false | ||||
if_true: | if_true: | ||||
ret i32 1 | ret i32 1 | ||||
if_false: | if_false: | ||||
%sub = sub i32 %n, 1 | %sub = sub i32 %n, 1 | ||||
%call = call i32 @fib_rec(i32 %sub) | %call = call i32 @foo(i32 %sub) | ||||
%sub2 = sub i32 %n, 2 | %sub2 = sub i32 %n, 2 | ||||
%call2 = call i32 @fib_rec(i32 %sub2) | %call2 = call i32 @foo(i32 %sub2) | ||||
%add = add i32 %call, %call2 | %add = add i32 %call, %call2 | ||||
ret i32 %add | ret i32 %add | ||||
} | } | ||||
define i32 @fib_check(){ | define i32 @fib_check(){ | ||||
%correct = alloca i32 | %correct = alloca i32 | ||||
%i = alloca i32 | %i = alloca i32 | ||||
store i32 1, i32* %correct | store i32 1, i32* %correct | ||||
store i32 0, i32* %i | store i32 0, i32* %i | ||||
br label %loop_cond | br label %loop_cond | ||||
loop_cond: | loop_cond: | ||||
%i_val = load i32, i32* %i | %i_val = load i32, i32* %i | ||||
%cmp = icmp slt i32 %i_val, 10 | %cmp = icmp slt i32 %i_val, 10 | ||||
br i1 %cmp, label %loop_body, label %loop_end | br i1 %cmp, label %loop_body, label %loop_end | ||||
loop_body: | loop_body: | ||||
%i_val2 = load i32, i32* %i | %i_val2 = load i32, i32* %i | ||||
%call = call i32 @fib_loop(i32 %i_val2) | %call = call i32 @fib_loop(i32 %i_val2) | ||||
%i_val3 = load i32, i32* %i | %i_val3 = load i32, i32* %i | ||||
%call2 = call i32 @fib_rec(i32 %i_val3) | %call2 = call i32 @foo(i32 %i_val3) | ||||
%cmp2 = icmp ne i32 %call, %call2 | %cmp2 = icmp ne i32 %call, %call2 | ||||
br i1 %cmp2, label %if_true, label %if_false | br i1 %cmp2, label %if_true, label %if_false | ||||
if_true: | if_true: | ||||
store i32 0, i32* %correct | store i32 0, i32* %correct | ||||
br label %if_end | br label %if_end | ||||
if_false: | if_false: | ||||
br label %if_end | br label %if_end | ||||
if_end: | if_end: | ||||
%i_val4 = load i32, i32* %i | %i_val4 = load i32, i32* %i | ||||
%add = add i32 %i_val4, 1 | %add = add i32 %i_val4, 1 | ||||
store i32 %add, i32* %i | store i32 %add, i32* %i | ||||
br label %loop_cond | br label %loop_cond | ||||
loop_end: | loop_end: | ||||
%correct_val = load i32, i32* %correct | %correct_val = load i32, i32* %correct | ||||
ret i32 %correct_val | ret i32 %correct_val | ||||
} | } | ||||
)"}; | )"}; | ||||
// check that loading a plugin works | } // namespace | ||||
// the plugin being loaded acts identically to the default inliner | |||||
TEST(PluginInlineAdvisorTest, PluginLoad) { | |||||
#if !defined(LLVM_ENABLE_PLUGINS) | |||||
// Skip the test if plugins are disabled. | |||||
GTEST_SKIP(); | |||||
#endif | |||||
CompilerInstance CI{}; | |||||
CI.setupPlugin(); | |||||
for (StringRef IR : TestIRS) { | // check that the behaviour of a custom inline order is correct | ||||
CI.run_default(IR); | // the custom order drops any functions called "foo" so all tests | ||||
std::string default_output = CI.output; | // should contain at least one function called foo | ||||
nit: Could we say named instead of called to avoid confusion when discussing inlining? kazu: nit: Could we say `named` instead of `called` to avoid confusion when discussing inlining? | |||||
CI.run_dynamic(IR); | TEST(PluginInlineOrderTest, NoInlineFoo) { | ||||
std::string dynamic_output = CI.output; | |||||
ASSERT_EQ(default_output, dynamic_output); | |||||
} | |||||
} | |||||
// check that the behaviour of a custom inliner is correct | |||||
// the custom inliner inlines all functions that are not named "foo" | |||||
// this testdoes not require plugins to be enabled | |||||
TEST(PluginInlineAdvisorTest, CustomAdvisor) { | |||||
CompilerInstance CI{}; | CompilerInstance CI{}; | ||||
CI.setupFooOnly(); | CI.setupPlugin(); | ||||
for (StringRef IR : TestIRS) { | for (StringRef IR : TestIRS) { | ||||
CI.run_dynamic(IR); | bool found_foo = false; | ||||
CI.run(IR); | |||||
CallGraph CGraph = CallGraph(*CI.outputM); | CallGraph CGraph = CallGraph(*CI.outputM); | ||||
for (auto &node : CGraph) { | for (auto &node : CGraph) { | ||||
for (auto &edge : *node.second) { | for (auto &edge : *node.second) { | ||||
if (!edge.first) | found_foo |= edge.second->getFunction()->getName() == "foo"; | ||||
continue; | |||||
ASSERT_NE(edge.second->getFunction()->getName(), "foo"); | |||||
} | } | ||||
} | } | ||||
ASSERT_TRUE(found_foo); | |||||
} | } | ||||
} | } | ||||
} // namespace llvm | } // namespace llvm |
How are we using this?