Index: llvm/include/llvm/Analysis/InlineOrder.h =================================================================== --- llvm/include/llvm/Analysis/InlineOrder.h +++ llvm/include/llvm/Analysis/InlineOrder.h @@ -32,7 +32,52 @@ }; std::unique_ptr>> -getInlineOrder(FunctionAnalysisManager &FAM, const InlineParams &Params); +getDefaultInlineOrder(FunctionAnalysisManager &FAM, const InlineParams &Params, + ModuleAnalysisManager &MAM, Module &M); + +std::unique_ptr>> +getInlineOrder(FunctionAnalysisManager &FAM, const InlineParams &Params, + ModuleAnalysisManager &MAM, Module &M); + +/// Used for dynamically loading instances of InlineOrder as plugins +/// +/// Plugins must implement an InlineOrderFactory, for an example refer to: +/// llvm/unittests/Analysis/InlineOrderPlugin/InlineOrderPlugin.cpp +/// +/// If a PluginInlineOrderAnalysis has been registered with the +/// current ModuleAnalysisManager, llvm::getInlineOrder returns an +/// InlineOrder created by the PluginInlineOrderAnalysis' Factory. +/// +class PluginInlineOrderAnalysis + : public AnalysisInfoMixin { +public: + static AnalysisKey Key; + + typedef std::unique_ptr>> ( + *InlineOrderFactory)(FunctionAnalysisManager &FAM, + const InlineParams &Params, + ModuleAnalysisManager &MAM, Module &M); + + PluginInlineOrderAnalysis(InlineOrderFactory Factory) : Factory(Factory) { + HasBeenRegistered = true; + assert(Factory != nullptr && + "The plugin inline order factory should not be a null pointer."); + } + + struct Result { + InlineOrderFactory Factory; + }; + + Result run(Module &, ModuleAnalysisManager &) { return {Factory}; } + Result getResult() { return {Factory}; } + + static bool isRegistered() { return HasBeenRegistered; } + static void unregister() { HasBeenRegistered = false; } + +private: + static bool HasBeenRegistered; + InlineOrderFactory Factory; +}; } // namespace llvm #endif // LLVM_ANALYSIS_INLINEORDER_H Index: llvm/lib/Analysis/InlineOrder.cpp =================================================================== --- llvm/lib/Analysis/InlineOrder.cpp +++ llvm/lib/Analysis/InlineOrder.cpp @@ -33,8 +33,7 @@ "Use inline cost priority."), clEnumValN(InlinePriorityMode::CostBenefit, "cost-benefit", "Use cost-benefit ratio."), - clEnumValN(InlinePriorityMode::ML, "ml", - "Use ML."))); + clEnumValN(InlinePriorityMode::ML, "ml", "Use ML."))); static cl::opt ModuleInlinerTopPriorityThreshold( "moudle-inliner-top-priority-threshold", cl::Hidden, cl::init(0), @@ -281,8 +280,13 @@ } // namespace +AnalysisKey llvm::PluginInlineOrderAnalysis::Key; +bool llvm::PluginInlineOrderAnalysis::HasBeenRegistered; + std::unique_ptr>> -llvm::getInlineOrder(FunctionAnalysisManager &FAM, const InlineParams &Params) { +llvm::getDefaultInlineOrder(FunctionAnalysisManager &FAM, + const InlineParams &Params, + ModuleAnalysisManager &MAM, Module &M) { switch (UseInlinePriority) { case InlinePriorityMode::Size: LLVM_DEBUG(dbgs() << " Current used priority: Size priority ---- \n"); @@ -295,11 +299,22 @@ case InlinePriorityMode::CostBenefit: LLVM_DEBUG( dbgs() << " Current used priority: cost-benefit priority ---- \n"); - return std::make_unique>(FAM, Params); + return std::make_unique>(FAM, + Params); case InlinePriorityMode::ML: - LLVM_DEBUG( - dbgs() << " Current used priority: ML priority ---- \n"); + LLVM_DEBUG(dbgs() << " Current used priority: ML priority ---- \n"); return std::make_unique>(FAM, Params); } return nullptr; } + +std::unique_ptr>> +llvm::getInlineOrder(FunctionAnalysisManager &FAM, const InlineParams &Params, + ModuleAnalysisManager &MAM, Module &M) { + if (llvm::PluginInlineOrderAnalysis::isRegistered()) { + LLVM_DEBUG(dbgs() << " Current used priority: plugin ---- \n"); + return MAM.getResult(M).Factory(FAM, Params, MAM, + M); + } + return getDefaultInlineOrder(FAM, Params, MAM, M); +} \ No newline at end of file Index: llvm/lib/Transforms/IPO/ModuleInliner.cpp =================================================================== --- llvm/lib/Transforms/IPO/ModuleInliner.cpp +++ llvm/lib/Transforms/IPO/ModuleInliner.cpp @@ -138,7 +138,7 @@ // // TODO: Here is a huge amount duplicate code between the module inliner and // the SCC inliner, which need some refactoring. - auto Calls = getInlineOrder(FAM, Params); + auto Calls = getInlineOrder(FAM, Params, MAM, M); assert(Calls != nullptr && "Expected an initialized InlineOrder"); // Populate the initial list of calls in this module. Index: llvm/unittests/Analysis/CMakeLists.txt =================================================================== --- llvm/unittests/Analysis/CMakeLists.txt +++ llvm/unittests/Analysis/CMakeLists.txt @@ -39,6 +39,7 @@ MLModelRunnerTest.cpp PhiValuesTest.cpp PluginInlineAdvisorAnalysisTest.cpp + PluginInlineOrderAnalysisTest.cpp ProfileSummaryInfoTest.cpp ScalarEvolutionTest.cpp VectorFunctionABITest.cpp @@ -73,3 +74,4 @@ endif() add_subdirectory(InlineAdvisorPlugin) +add_subdirectory(InlineOrderPlugin) Index: llvm/unittests/Analysis/InlineOrderPlugin/CMakeLists.txt =================================================================== --- /dev/null +++ llvm/unittests/Analysis/InlineOrderPlugin/CMakeLists.txt @@ -0,0 +1,21 @@ +# The order plugin expects to not link against the Analysis, Support and Core +# libraries, but expects them to exist in the process loading the plugin. This +# doesn't work with DLLs on Windows (where a shared library can't have undefined +# references), so just skip this testcase on Windows. +if (NOT WIN32) + unset(LLVM_LINK_COMPONENTS) + add_llvm_library(InlineOrderPlugin MODULE BUILDTREE_ONLY InlineOrderPlugin.cpp) + # Put PLUGIN next to the unit test executable. + set_output_directory(InlineOrderPlugin + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/../ + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/../ + ) + set_target_properties(InlineOrderPlugin PROPERTIES FOLDER "Tests") + + export_executable_symbols_for_plugins(AnalysisTests) + # The plugin depends on some of the output files of intrinsics_gen, so make sure + # it is built before the plugin. + add_dependencies(InlineOrderPlugin intrinsics_gen) + add_dependencies(AnalysisTests InlineOrderPlugin) + set_property(TARGET InlineOrderPlugin PROPERTY FOLDER "Tests/UnitTests/AnalysisTests") +endif() Index: llvm/unittests/Analysis/InlineOrderPlugin/InlineOrderPlugin.cpp =================================================================== --- /dev/null +++ llvm/unittests/Analysis/InlineOrderPlugin/InlineOrderPlugin.cpp @@ -0,0 +1,70 @@ +#include "llvm/IR/Function.h" +#include "llvm/IR/Module.h" +#include "llvm/Pass.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/raw_ostream.h" + +#include "llvm/Analysis/InlineOrder.h" + +using namespace llvm; + +namespace { + +class NoFooInlineOrder : public InlineOrder> { +public: + NoFooInlineOrder(FunctionAnalysisManager &FAM, const InlineParams &Params, + ModuleAnalysisManager &MAM, Module &M) { + DefaultInlineOrder = getDefaultInlineOrder(FAM, Params, MAM, M); + } + size_t size() override { return DefaultInlineOrder->size(); } + void push(const std::pair &Elt) override { + // We ignore calles named "foo" + if (Elt.first->getCalledFunction()->getName() == "foo") { + DefaultInlineOrder->push(Elt); + } + } + std::pair pop() override { + return DefaultInlineOrder->pop(); + } + void erase_if(function_ref)> Pred) override { + DefaultInlineOrder->erase_if(Pred); + } + +private: + std::unique_ptr>> DefaultInlineOrder; +}; + +std::unique_ptr>> +NoFooInlineOrderFactory(FunctionAnalysisManager &FAM, + const InlineParams &Params, ModuleAnalysisManager &MAM, + Module &M) { + return std::make_unique(FAM, Params, MAM, M); +} + +} // namespace + +/* New PM Registration */ +llvm::PassPluginLibraryInfo getDefaultDynamicInlineOrderPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, "DynamicDefaultInlineOrder", + LLVM_VERSION_STRING, [](PassBuilder &PB) { + // We use the PassBuilder's callback mechanism + // to register our Analysis: this will register + // our PluginInlineOrderAnalysis instance with + // the ModuleAnalysisManager + PB.registerAnalysisRegistrationCallback( + [](ModuleAnalysisManager &MAM) { + MAM.registerPass([] { + // defaultInlineOrderFactory will be + // used to create an InlineOrder + return PluginInlineOrderAnalysis(NoFooInlineOrderFactory); + }); + }); + }}; +} + +extern "C" LLVM_ATTRIBUTE_WEAK ::llvm::PassPluginLibraryInfo +llvmGetPassPluginInfo() { + return getDefaultDynamicInlineOrderPluginInfo(); +} Index: llvm/unittests/Analysis/PluginInlineAdvisorAnalysisTest.cpp =================================================================== --- llvm/unittests/Analysis/PluginInlineAdvisorAnalysisTest.cpp +++ llvm/unittests/Analysis/PluginInlineAdvisorAnalysisTest.cpp @@ -10,6 +10,8 @@ namespace llvm { +namespace { + void anchor() {} static std::string libPath(const std::string Name = "InlineAdvisorPlugin") { @@ -84,6 +86,12 @@ 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. + PluginInlineAdvisorAnalysis::HasBeenRegistered = false; + } + std::string output; std::unique_ptr outputM; @@ -256,6 +264,8 @@ } )"}; +} // namespace + // check that loading a plugin works // the plugin being loaded acts identically to the default inliner TEST(PluginInlineAdvisorTest, PluginLoad) { Index: llvm/unittests/Analysis/PluginInlineOrderAnalysisTest.cpp =================================================================== --- /dev/null +++ llvm/unittests/Analysis/PluginInlineOrderAnalysisTest.cpp @@ -0,0 +1,247 @@ +#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) { + 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