#include "llvm/Analysis/CallGraph.h" #include "llvm/AsmParser/Parser.h" #include "llvm/Config/config.h" #include "llvm/IR/Module.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Passes/PassPlugin.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" namespace llvm { namespace { void anchor() {} static std::string libPath(const std::string Name = "InlineAdvisorPlugin") { const auto &Argvs = testing::internal::GetArgvs(); const char *Argv0 = Argvs.size() > 0 ? Argvs[0].c_str() : "PluginInlineAdvisorAnalysisTest"; void *Ptr = (void *)(intptr_t)anchor; std::string Path = sys::fs::getMainExecutable(Argv0, Ptr); llvm::SmallString<256> Buf{sys::path::parent_path(Path)}; sys::path::append(Buf, (Name + LLVM_PLUGIN_EXT).c_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 getAdviceImpl(CallBase &CB) override { if (CB.getCalledFunction()->getName() == "foo") return std::make_unique(this, CB, getCallerORE(CB), true); return std::make_unique(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 { LLVMContext Ctx; ModulePassManager MPM; InlineParams IP; PassBuilder PB; LoopAnalysisManager LAM; FunctionAnalysisManager FAM; CGSCCAnalysisManager CGAM; ModuleAnalysisManager MAM; SMDiagnostic Error; // connect the plugin to our compiler instance void setupPlugin() { auto PluginPath = libPath(); ASSERT_NE("", PluginPath); Expected Plugin = PassPlugin::Load(PluginPath); ASSERT_TRUE(!!Plugin) << "Plugin path: " << PluginPath; Plugin->registerPassBuilderCallbacks(PB); } // connect the FooOnlyInlineAdvisor to our compiler instance void setupFooOnly() { MAM.registerPass( [&] { return PluginInlineAdvisorAnalysis(fooOnlyFactory); }); } CompilerInstance() { IP = getInlineParams(3, 0); PB.registerModuleAnalyses(MAM); PB.registerCGSCCAnalyses(CGAM); PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); MPM.addPass(ModuleInlinerPass(IP, InliningAdvisorMode::Default, ThinOrFullLTOPhase::None)); } std::string output; std::unique_ptr outputM; auto run(StringRef IR) { 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); } }; StringRef TestIRS[] = { // Simple 3 function inline case R"( define void @f1() { call void @foo() ret void } define void @foo() { call void @f3() ret void } define void @f3() { ret void } )", // Test that has 5 functions of which 2 are recursive R"( define void @f1() { call void @foo() ret void } define void @f2() { call void @foo() ret void } define void @foo() { call void @f4() call void @f5() ret void } define void @f4() { ret void } define void @f5() { call void @foo() ret void } )", // test with 2 mutually recursive functions and 1 function with a loop R"( define void @f1() { call void @f2() ret void } define void @f2() { call void @f3() ret void } define void @f3() { call void @f1() ret void } define void @f4() { br label %loop loop: call void @f5() br label %loop } define void @f5() { ret void } )", // test that has a function that computes fibonacci in a loop, one in a // recurisve manner, and one that calls both and compares them R"( define i32 @fib_loop(i32 %n){ %curr = alloca i32 %last = alloca i32 %i = alloca i32 store i32 1, i32* %curr store i32 1, i32* %last store i32 2, i32* %i br label %loop_cond loop_cond: %i_val = load i32, i32* %i %cmp = icmp slt i32 %i_val, %n br i1 %cmp, label %loop_body, label %loop_end loop_body: %curr_val = load i32, i32* %curr %last_val = load i32, i32* %last %add = add i32 %curr_val, %last_val store i32 %add, i32* %last store i32 %curr_val, i32* %curr %i_val2 = load i32, i32* %i %add2 = add i32 %i_val2, 1 store i32 %add2, i32* %i br label %loop_cond loop_end: %curr_val3 = load i32, i32* %curr ret i32 %curr_val3 } define i32 @fib_rec(i32 %n){ %cmp = icmp eq i32 %n, 0 %cmp2 = icmp eq i32 %n, 1 %or = or i1 %cmp, %cmp2 br i1 %or, label %if_true, label %if_false if_true: ret i32 1 if_false: %sub = sub i32 %n, 1 %call = call i32 @fib_rec(i32 %sub) %sub2 = sub i32 %n, 2 %call2 = call i32 @fib_rec(i32 %sub2) %add = add i32 %call, %call2 ret i32 %add } define i32 @fib_check(){ %correct = alloca i32 %i = alloca i32 store i32 1, i32* %correct store i32 0, i32* %i br label %loop_cond loop_cond: %i_val = load i32, i32* %i %cmp = icmp slt i32 %i_val, 10 br i1 %cmp, label %loop_body, label %loop_end loop_body: %i_val2 = load i32, i32* %i %call = call i32 @fib_loop(i32 %i_val2) %i_val3 = load i32, i32* %i %call2 = call i32 @fib_rec(i32 %i_val3) %cmp2 = icmp ne i32 %call, %call2 br i1 %cmp2, label %if_true, label %if_false if_true: store i32 0, i32* %correct br label %if_end if_false: br label %if_end if_end: %i_val4 = load i32, i32* %i %add = add i32 %i_val4, 1 store i32 %add, i32* %i br label %loop_cond loop_end: %correct_val = load i32, i32* %correct ret i32 %correct_val } )"}; } // namespace // check that loading a plugin works // 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 DefaultCI{}; CompilerInstance PluginCI{}; PluginCI.setupPlugin(); for (StringRef IR : TestIRS) { DefaultCI.run(IR); std::string default_output = DefaultCI.output; PluginCI.run(IR); std::string dynamic_output = PluginCI.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{}; CI.setupFooOnly(); for (StringRef IR : TestIRS) { CI.run(IR); CallGraph CGraph = CallGraph(*CI.outputM); for (auto &node : CGraph) { for (auto &edge : *node.second) { if (!edge.first) continue; ASSERT_NE(edge.second->getFunction()->getName(), "foo"); } } } } } // namespace llvm