#include "llvm/Analysis/CallGraph.h" #include "llvm/AsmParser/Parser.h" #include "llvm/Config/config.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" #include "llvm/Analysis/InlineOrder.h" namespace llvm { namespace { void anchor() {} std::string libPath(const std::string Name = "InlineOrderPlugin") { const auto &Argvs = testing::internal::GetArgvs(); const char *Argv0 = Argvs.size() > 0 ? Argvs[0].c_str() : "PluginInlineOrderAnalysisTest"; 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()); } 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); } 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)); } ~CompilerInstance() { // Reset the static variable that tracks if the plugin has been registered. // This is needed to allow the test to run multiple times. PluginInlineOrderAnalysis::unregister(); } std::string Output; std::unique_ptr OutputM; // Run with the dynamic inline order. auto run(StringRef IR) { OutputM = parseAssemblyString(IR, Error, Ctx); MPM.run(*OutputM, MAM); ASSERT_TRUE(OutputM); Output.clear(); raw_string_ostream OStream{Output}; OutputM->print(OStream, 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 @foo() ret void } define void @foo() { 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 // recursive 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 @foo(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 @foo(i32 %sub) %sub2 = sub i32 %n, 2 %call2 = call i32 @foo(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 @foo(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 the behaviour of a custom inline order is correct. // The custom order drops any functions named "foo" so all tests // should contain at least one function named foo. TEST(PluginInlineOrderTest, NoInlineFoo) { #if !defined(LLVM_ENABLE_PLUGINS) // Skip the test if plugins are disabled. GTEST_SKIP(); #endif CompilerInstance CI{}; CI.setupPlugin(); for (StringRef IR : TestIRS) { bool FoundFoo = false; CI.run(IR); CallGraph CGraph = CallGraph(*CI.OutputM); for (auto &Node : CGraph) { for (auto &Edge : *Node.second) { FoundFoo |= Edge.second->getFunction()->getName() == "foo"; } } ASSERT_TRUE(FoundFoo); } } } // namespace llvm