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
22namespace 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
29using namespace mlir;
30
31/// This function implements the inliner optimization pipeline.
32static void defaultInlinerOptPipeline(OpPassManager &pm) {
33 pm.addPass(pass: createCanonicalizerPass());
34}
35
36//===----------------------------------------------------------------------===//
37// InlinerPass
38//===----------------------------------------------------------------------===//
39
40namespace {
41class InlinerPass : public impl::InlinerBase<InlinerPass> {
42public:
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
62private:
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
76InlinerPass::InlinerPass() : InlinerPass(defaultInlinerOptPipeline) {}
77
78InlinerPass::InlinerPass(
79 std::function<void(OpPassManager &)> defaultPipelineArg)
80 : InlinerPass(std::move(defaultPipelineArg),
81 llvm::StringMap<OpPassManager>{}) {}
82
83InlinerPass::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.
96static 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
128void 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
155LogicalResult 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
186std::unique_ptr<Pass> mlir::createInlinerPass() {
187 return std::make_unique<InlinerPass>();
188}
189std::unique_ptr<Pass>
190mlir::createInlinerPass(llvm::StringMap<OpPassManager> opPipelines) {
191 return std::make_unique<InlinerPass>(args&: defaultInlinerOptPipeline,
192 args: std::move(opPipelines));
193}
194std::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

source code of mlir/lib/Transforms/InlinerPass.cpp