| 1 | //===- InlinerPass.cpp - Pass to inline function calls --------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // This file implements a basic inlining algorithm that operates bottom up over |
| 10 | // the Strongly Connect Components(SCCs) of the CallGraph. This enables a more |
| 11 | // incremental propagation of inlining decisions from the leafs to the roots of |
| 12 | // the callgraph. |
| 13 | // |
| 14 | //===----------------------------------------------------------------------===// |
| 15 | |
| 16 | #include "mlir/Transforms/Passes.h" |
| 17 | |
| 18 | #include "mlir/Analysis/CallGraph.h" |
| 19 | #include "mlir/Pass/PassManager.h" |
| 20 | #include "mlir/Transforms/Inliner.h" |
| 21 | |
| 22 | namespace mlir { |
| 23 | #define GEN_PASS_DEF_INLINER |
| 24 | #include "mlir/Transforms/Passes.h.inc" |
| 25 | } // namespace mlir |
| 26 | |
| 27 | #define DEBUG_TYPE "inliner-pass" |
| 28 | |
| 29 | using namespace mlir; |
| 30 | |
| 31 | /// This function implements the inliner optimization pipeline. |
| 32 | static void defaultInlinerOptPipeline(OpPassManager &pm) { |
| 33 | pm.addPass(pass: createCanonicalizerPass()); |
| 34 | } |
| 35 | |
| 36 | //===----------------------------------------------------------------------===// |
| 37 | // InlinerPass |
| 38 | //===----------------------------------------------------------------------===// |
| 39 | |
| 40 | namespace { |
| 41 | class InlinerPass : public impl::InlinerBase<InlinerPass> { |
| 42 | public: |
| 43 | InlinerPass(); |
| 44 | InlinerPass(const InlinerPass &) = default; |
| 45 | InlinerPass(std::function<void(OpPassManager &)> defaultPipeline); |
| 46 | InlinerPass(std::function<void(OpPassManager &)> defaultPipeline, |
| 47 | llvm::StringMap<OpPassManager> opPipelines); |
| 48 | void runOnOperation() override; |
| 49 | |
| 50 | /// A callback provided to the inliner driver to execute |
| 51 | /// the specified pass pipeline on the given operation |
| 52 | /// within the context of the current inliner pass, |
| 53 | /// which is passed as the first argument. |
| 54 | /// runPipeline API is protected within the Pass class, |
| 55 | /// so this helper is required to call it from the foreign |
| 56 | /// inliner driver. |
| 57 | static LogicalResult runPipelineHelper(Pass &pass, OpPassManager &pipeline, |
| 58 | Operation *op) { |
| 59 | return mlir::cast<InlinerPass>(pass).runPipeline(pipeline, op); |
| 60 | } |
| 61 | |
| 62 | private: |
| 63 | /// Attempt to initialize the options of this pass from the given string. |
| 64 | /// Derived classes may override this method to hook into the point at which |
| 65 | /// options are initialized, but should generally always invoke this base |
| 66 | /// class variant. |
| 67 | LogicalResult initializeOptions( |
| 68 | StringRef options, |
| 69 | function_ref<LogicalResult(const Twine &)> errorHandler) override; |
| 70 | |
| 71 | /// Inliner configuration parameters created from the pass options. |
| 72 | InlinerConfig config; |
| 73 | }; |
| 74 | } // namespace |
| 75 | |
| 76 | InlinerPass::InlinerPass() : InlinerPass(defaultInlinerOptPipeline) {} |
| 77 | |
| 78 | InlinerPass::InlinerPass( |
| 79 | std::function<void(OpPassManager &)> defaultPipelineArg) |
| 80 | : InlinerPass(std::move(defaultPipelineArg), |
| 81 | llvm::StringMap<OpPassManager>{}) {} |
| 82 | |
| 83 | InlinerPass::InlinerPass(std::function<void(OpPassManager &)> defaultPipeline, |
| 84 | llvm::StringMap<OpPassManager> opPipelines) |
| 85 | : config(std::move(defaultPipeline), maxInliningIterations) { |
| 86 | if (opPipelines.empty()) |
| 87 | return; |
| 88 | |
| 89 | // Update the option for the op specific optimization pipelines. |
| 90 | for (auto &it : opPipelines) |
| 91 | opPipelineList.addValue(it.second); |
| 92 | config.setOpPipelines(std::move(opPipelines)); |
| 93 | } |
| 94 | |
| 95 | // Return true if the inlining ratio does not exceed the threshold. |
| 96 | static bool isProfitableToInline(const Inliner::ResolvedCall &resolvedCall, |
| 97 | unsigned inliningThreshold) { |
| 98 | // Return early, ratio <= 0U will always be false. |
| 99 | if (inliningThreshold == 0U) |
| 100 | return false; |
| 101 | // Return early, ratio <= -1U will always be true. |
| 102 | if (inliningThreshold == -1U) |
| 103 | return true; |
| 104 | |
| 105 | Region *callerRegion = resolvedCall.sourceNode->getCallableRegion(); |
| 106 | Region *calleeRegion = resolvedCall.targetNode->getCallableRegion(); |
| 107 | |
| 108 | assert(calleeRegion && callerRegion && "unexpected external node" ); |
| 109 | |
| 110 | auto countOps = [](Region *region) { |
| 111 | unsigned count = 0; |
| 112 | region->walk(callback: [&](Operation *) { ++count; }); |
| 113 | return count; |
| 114 | }; |
| 115 | |
| 116 | unsigned callerOps = countOps(callerRegion); |
| 117 | |
| 118 | // Always inline empty callees (if it is possible at all). |
| 119 | if (callerOps == 0) |
| 120 | return true; |
| 121 | |
| 122 | unsigned ratio = countOps(calleeRegion) * 100 / callerOps; |
| 123 | LLVM_DEBUG(llvm::dbgs() << "Callee / caller operation ratio (max: " |
| 124 | << inliningThreshold << "%): " << ratio << "%\n" ); |
| 125 | return ratio <= inliningThreshold; |
| 126 | } |
| 127 | |
| 128 | void InlinerPass::runOnOperation() { |
| 129 | CallGraph &cg = getAnalysis<CallGraph>(); |
| 130 | |
| 131 | // The inliner should only be run on operations that define a symbol table, |
| 132 | // as the callgraph will need to resolve references. |
| 133 | Operation *op = getOperation(); |
| 134 | if (!op->hasTrait<OpTrait::SymbolTable>()) { |
| 135 | op->emitOpError() << " was scheduled to run under the inliner, but does " |
| 136 | "not define a symbol table" ; |
| 137 | return signalPassFailure(); |
| 138 | } |
| 139 | |
| 140 | // By default, assume that any inlining is profitable. |
| 141 | auto profitabilityCb = [=](const Inliner::ResolvedCall &call) { |
| 142 | return isProfitableToInline(call, inliningThreshold); |
| 143 | }; |
| 144 | |
| 145 | // Get an instance of the inliner. |
| 146 | Inliner inliner(op, cg, *this, getAnalysisManager(), runPipelineHelper, |
| 147 | config, profitabilityCb); |
| 148 | |
| 149 | // Run the inlining. |
| 150 | if (failed(Result: inliner.doInlining())) |
| 151 | signalPassFailure(); |
| 152 | } |
| 153 | |
| 154 | LogicalResult InlinerPass::initializeOptions( |
| 155 | StringRef options, |
| 156 | function_ref<LogicalResult(const Twine &)> errorHandler) { |
| 157 | if (failed(Pass::initializeOptions(options, errorHandler))) |
| 158 | return failure(); |
| 159 | |
| 160 | // Initialize the pipeline builder for operations without the dedicated |
| 161 | // optimization pipeline in opPipelineList to use the option string. |
| 162 | // TODO: Use a generic pass manager for the pre-inline pipeline, and remove |
| 163 | // this. |
| 164 | if (!defaultPipelineStr.empty()) { |
| 165 | std::string defaultPipelineCopy = defaultPipelineStr; |
| 166 | config.setDefaultPipeline([=](OpPassManager &pm) { |
| 167 | (void)parsePassPipeline(pipeline: defaultPipelineCopy, pm); |
| 168 | }); |
| 169 | } else if (defaultPipelineStr.getNumOccurrences()) { |
| 170 | config.setDefaultPipeline(nullptr); |
| 171 | } |
| 172 | |
| 173 | // Initialize the op specific pass pipelines. |
| 174 | llvm::StringMap<OpPassManager> pipelines; |
| 175 | for (OpPassManager pipeline : opPipelineList) |
| 176 | if (!pipeline.empty()) |
| 177 | pipelines.try_emplace(pipeline.getOpAnchorName(), pipeline); |
| 178 | config.setOpPipelines(std::move(pipelines)); |
| 179 | |
| 180 | config.setMaxInliningIterations(maxInliningIterations); |
| 181 | |
| 182 | return success(); |
| 183 | } |
| 184 | |
| 185 | std::unique_ptr<Pass> mlir::createInlinerPass() { |
| 186 | return std::make_unique<InlinerPass>(); |
| 187 | } |
| 188 | std::unique_ptr<Pass> |
| 189 | mlir::createInlinerPass(llvm::StringMap<OpPassManager> opPipelines) { |
| 190 | return std::make_unique<InlinerPass>(args&: defaultInlinerOptPipeline, |
| 191 | args: std::move(opPipelines)); |
| 192 | } |
| 193 | std::unique_ptr<Pass> mlir::createInlinerPass( |
| 194 | llvm::StringMap<OpPassManager> opPipelines, |
| 195 | std::function<void(OpPassManager &)> defaultPipelineBuilder) { |
| 196 | return std::make_unique<InlinerPass>(args: std::move(defaultPipelineBuilder), |
| 197 | args: std::move(opPipelines)); |
| 198 | } |
| 199 | |