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 | return; |
153 | } |
154 | |
155 | LogicalResult InlinerPass::initializeOptions( |
156 | StringRef options, |
157 | function_ref<LogicalResult(const Twine &)> errorHandler) { |
158 | if (failed(Pass::initializeOptions(options, errorHandler))) |
159 | return failure(); |
160 | |
161 | // Initialize the pipeline builder for operations without the dedicated |
162 | // optimization pipeline in opPipelineList to use the option string. |
163 | // TODO: Use a generic pass manager for the pre-inline pipeline, and remove |
164 | // this. |
165 | if (!defaultPipelineStr.empty()) { |
166 | std::string defaultPipelineCopy = defaultPipelineStr; |
167 | config.setDefaultPipeline([=](OpPassManager &pm) { |
168 | (void)parsePassPipeline(pipeline: defaultPipelineCopy, pm); |
169 | }); |
170 | } else if (defaultPipelineStr.getNumOccurrences()) { |
171 | config.setDefaultPipeline(nullptr); |
172 | } |
173 | |
174 | // Initialize the op specific pass pipelines. |
175 | llvm::StringMap<OpPassManager> pipelines; |
176 | for (OpPassManager pipeline : opPipelineList) |
177 | if (!pipeline.empty()) |
178 | pipelines.try_emplace(pipeline.getOpAnchorName(), pipeline); |
179 | config.setOpPipelines(std::move(pipelines)); |
180 | |
181 | config.setMaxInliningIterations(maxInliningIterations); |
182 | |
183 | return success(); |
184 | } |
185 | |
186 | std::unique_ptr<Pass> mlir::createInlinerPass() { |
187 | return std::make_unique<InlinerPass>(); |
188 | } |
189 | std::unique_ptr<Pass> |
190 | mlir::createInlinerPass(llvm::StringMap<OpPassManager> opPipelines) { |
191 | return std::make_unique<InlinerPass>(args&: defaultInlinerOptPipeline, |
192 | args: std::move(opPipelines)); |
193 | } |
194 | std::unique_ptr<Pass> mlir::createInlinerPass( |
195 | llvm::StringMap<OpPassManager> opPipelines, |
196 | std::function<void(OpPassManager &)> defaultPipelineBuilder) { |
197 | return std::make_unique<InlinerPass>(args: std::move(defaultPipelineBuilder), |
198 | args: std::move(opPipelines)); |
199 | } |
200 | |