MLIR 24.0.0git
Inliner.cpp
Go to the documentation of this file.
1//===- Inliner.cpp ---- SCC-based inliner ---------------------------------===//
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 Inliner that uses a basic inlining
10// algorithm that operates bottom up over the Strongly Connect Components(SCCs)
11// of the CallGraph. This enables a more incremental propagation of inlining
12// decisions from the leafs to the roots of the callgraph.
13//
14//===----------------------------------------------------------------------===//
15
17#include "mlir/IR/Threading.h"
22#include "llvm/ADT/DenseSet.h"
23#include "llvm/ADT/SCCIterator.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/Support/DebugLog.h"
26
27#define DEBUG_TYPE "inlining"
28
29using namespace mlir;
30
32
33//===----------------------------------------------------------------------===//
34// Symbol Use Tracking
35//===----------------------------------------------------------------------===//
36
37/// Walk all of the used symbol callgraph nodes referenced with the given op.
39 Operation *op, CallGraph &cg, SymbolTableCollection &symbolTable,
41 function_ref<void(CallGraphNode *, Operation *)> callback) {
42 auto symbolUses = SymbolTable::getSymbolUses(op);
43 assert(symbolUses && "expected uses to be valid");
44
45 Operation *symbolTableOp = op->getParentOp();
46 for (const SymbolTable::SymbolUse &use : *symbolUses) {
47 auto refIt = resolvedRefs.try_emplace(use.getSymbolRef());
48 CallGraphNode *&node = refIt.first->second;
49
50 // If this is the first instance of this reference, try to resolve a
51 // callgraph node for it.
52 if (refIt.second) {
53 auto *symbolOp = symbolTable.lookupNearestSymbolFrom(symbolTableOp,
54 use.getSymbolRef());
55 auto callableOp = dyn_cast_or_null<CallableOpInterface>(symbolOp);
56 if (!callableOp)
57 continue;
58 node = cg.lookupNode(callableOp.getCallableRegion());
59 }
60 if (node)
61 callback(node, use.getUser());
62 }
63}
64
65//===----------------------------------------------------------------------===//
66// CGUseList
67//===----------------------------------------------------------------------===//
68
69namespace {
70/// This struct tracks the uses of callgraph nodes that can be dropped when
71/// use_empty. It directly tracks and manages a use-list for all of the
72/// call-graph nodes. This is necessary because many callgraph nodes are
73/// referenced by SymbolRefAttr, which has no mechanism akin to the SSA `Use`
74/// class.
75struct CGUseList {
76 /// This struct tracks the uses of callgraph nodes within a specific
77 /// operation.
78 struct CGUser {
79 /// Any nodes referenced in the top-level attribute list of this user. We
80 /// use a set here because the number of references does not matter.
81 DenseSet<CallGraphNode *> topLevelUses;
82
83 /// Uses of nodes referenced by nested operations.
85 };
86
87 CGUseList(Operation *op, CallGraph &cg, SymbolTableCollection &symbolTable);
88
89 /// Drop uses of nodes referred to by the given call operation that resides
90 /// within 'userNode'.
91 void dropCallUses(CallGraphNode *userNode, Operation *callOp, CallGraph &cg);
92
93 /// Remove the given node from the use list.
94 void eraseNode(CallGraphNode *node);
95
96 /// Returns true if the given callgraph node has no uses and can be pruned.
97 bool isDead(CallGraphNode *node) const;
98
99 /// Returns true if the given callgraph node has a single use and can be
100 /// discarded.
101 bool hasOneUseAndDiscardable(CallGraphNode *node) const;
102
103 /// Recompute the uses held by the given callgraph node.
104 void recomputeUses(CallGraphNode *node, CallGraph &cg);
105
106 /// Merge the uses of 'lhs' with the uses of the 'rhs' after inlining a copy
107 /// of 'lhs' into 'rhs'.
108 void mergeUsesAfterInlining(CallGraphNode *lhs, CallGraphNode *rhs);
109
110private:
111 /// Decrement the uses of discardable nodes referenced by the given user.
112 void decrementDiscardableUses(CGUser &uses);
113
114 /// A mapping between a discardable callgraph node (that is a symbol) and the
115 /// number of uses for this node.
116 DenseMap<CallGraphNode *, int> discardableSymNodeUses;
117
118 /// A mapping between a callgraph node and the symbol callgraph nodes that it
119 /// uses.
121
122 /// A symbol table to use when resolving call lookups.
123 SymbolTableCollection &symbolTable;
124};
125} // namespace
126
127CGUseList::CGUseList(Operation *op, CallGraph &cg,
128 SymbolTableCollection &symbolTable)
129 : symbolTable(symbolTable) {
130 /// A set of callgraph nodes that are always known to be live during inlining.
132
133 // Walk each of the symbol tables looking for discardable callgraph nodes.
134 auto walkFn = [&](Operation *symbolTableOp, bool allUsesVisible) {
135 for (Operation &op : symbolTableOp->getRegion(0).getOps()) {
136 // If this is a callgraph operation, check to see if it is discardable.
137 if (auto callable = dyn_cast<CallableOpInterface>(&op)) {
138 if (auto *node = cg.lookupNode(callable.getCallableRegion())) {
139 SymbolOpInterface symbol = dyn_cast<SymbolOpInterface>(&op);
140 if (symbol && (allUsesVisible || symbol.isPrivate()) &&
141 symbol.canDiscardOnUseEmpty()) {
142 discardableSymNodeUses.try_emplace(node, 0);
143 }
144 continue;
145 }
146 }
147 // Otherwise, check for any referenced nodes. These will be always-live.
148 walkReferencedSymbolNodes(&op, cg, symbolTable, alwaysLiveNodes,
149 [](CallGraphNode *, Operation *) {});
150 }
151 };
152 SymbolTable::walkSymbolTables(op, /*allSymUsesVisible=*/!op->getBlock(),
153 walkFn);
154
155 // Drop the use information for any discardable nodes that are always live.
156 for (auto &it : alwaysLiveNodes)
157 discardableSymNodeUses.erase(it.second);
158
159 // Compute the uses for each of the callable nodes in the graph.
160 for (CallGraphNode *node : cg)
161 recomputeUses(node, cg);
162}
163
164void CGUseList::dropCallUses(CallGraphNode *userNode, Operation *callOp,
165 CallGraph &cg) {
166 auto &userRefs = nodeUses[userNode].innerUses;
167 auto walkFn = [&](CallGraphNode *node, Operation *user) {
168 auto parentIt = userRefs.find(node);
169 if (parentIt == userRefs.end())
170 return;
171 --parentIt->second;
172 --discardableSymNodeUses[node];
173 };
175 walkReferencedSymbolNodes(callOp, cg, symbolTable, resolvedRefs, walkFn);
176}
177
178void CGUseList::eraseNode(CallGraphNode *node) {
179 // Drop all child nodes.
180 for (auto &edge : *node)
181 if (edge.isChild())
182 eraseNode(edge.getTarget());
183
184 // Drop the uses held by this node and erase it.
185 auto useIt = nodeUses.find(node);
186 assert(useIt != nodeUses.end() && "expected node to be valid");
187 decrementDiscardableUses(useIt->getSecond());
188 nodeUses.erase(useIt);
189 discardableSymNodeUses.erase(node);
190}
191
192bool CGUseList::isDead(CallGraphNode *node) const {
193 // If the parent operation isn't a symbol, simply check normal SSA deadness.
194 Operation *nodeOp = node->getCallableRegion()->getParentOp();
195 if (!isa<SymbolOpInterface>(nodeOp))
196 return isMemoryEffectFree(nodeOp) && nodeOp->use_empty();
197
198 // Otherwise, check the number of symbol uses.
199 auto symbolIt = discardableSymNodeUses.find(node);
200 return symbolIt != discardableSymNodeUses.end() && symbolIt->second == 0;
201}
202
203bool CGUseList::hasOneUseAndDiscardable(CallGraphNode *node) const {
204 // If this isn't a symbol node, check for side-effects and SSA use count.
205 Operation *nodeOp = node->getCallableRegion()->getParentOp();
206 if (!isa<SymbolOpInterface>(nodeOp))
207 return isMemoryEffectFree(nodeOp) && nodeOp->hasOneUse();
208
209 // Otherwise, check the number of symbol uses.
210 auto symbolIt = discardableSymNodeUses.find(node);
211 return symbolIt != discardableSymNodeUses.end() && symbolIt->second == 1;
212}
213
214void CGUseList::recomputeUses(CallGraphNode *node, CallGraph &cg) {
215 Operation *parentOp = node->getCallableRegion()->getParentOp();
216 CGUser &uses = nodeUses[node];
217 decrementDiscardableUses(uses);
218
219 // Collect the new discardable uses within this node.
220 uses = CGUser();
222 auto walkFn = [&](CallGraphNode *refNode, Operation *user) {
223 auto discardSymIt = discardableSymNodeUses.find(refNode);
224 if (discardSymIt == discardableSymNodeUses.end())
225 return;
226
227 if (user != parentOp)
228 ++uses.innerUses[refNode];
229 else if (!uses.topLevelUses.insert(refNode).second)
230 return;
231 ++discardSymIt->second;
232 };
233 walkReferencedSymbolNodes(parentOp, cg, symbolTable, resolvedRefs, walkFn);
234}
235
236void CGUseList::mergeUsesAfterInlining(CallGraphNode *lhs, CallGraphNode *rhs) {
237 auto &lhsUses = nodeUses[lhs], &rhsUses = nodeUses[rhs];
238 for (auto &useIt : lhsUses.innerUses) {
239 rhsUses.innerUses[useIt.first] += useIt.second;
240 discardableSymNodeUses[useIt.first] += useIt.second;
241 }
242}
243
244void CGUseList::decrementDiscardableUses(CGUser &uses) {
245 for (CallGraphNode *node : uses.topLevelUses)
246 --discardableSymNodeUses[node];
247 for (auto &it : uses.innerUses)
248 discardableSymNodeUses[it.first] -= it.second;
249}
250
251//===----------------------------------------------------------------------===//
252// CallGraph traversal
253//===----------------------------------------------------------------------===//
254
255namespace {
256/// This class represents a specific callgraph SCC.
257class CallGraphSCC {
258public:
259 CallGraphSCC(llvm::scc_iterator<const CallGraph *> &parentIterator)
260 : parentIterator(parentIterator) {}
261 /// Return a range over the nodes within this SCC.
262 std::vector<CallGraphNode *>::iterator begin() { return nodes.begin(); }
263 std::vector<CallGraphNode *>::iterator end() { return nodes.end(); }
264
265 /// Reset the nodes of this SCC with those provided.
266 void reset(const std::vector<CallGraphNode *> &newNodes) { nodes = newNodes; }
267
268 /// Remove the given node from this SCC.
269 void remove(CallGraphNode *node) {
270 auto it = llvm::find(nodes, node);
271 if (it != nodes.end()) {
272 nodes.erase(it);
273 parentIterator.ReplaceNode(node, nullptr);
274 }
275 }
276
277private:
278 std::vector<CallGraphNode *> nodes;
279 llvm::scc_iterator<const CallGraph *> &parentIterator;
280};
281} // namespace
282
283/// Run a given transformation over the SCCs of the callgraph in a bottom up
284/// traversal.
285static LogicalResult runTransformOnCGSCCs(
286 const CallGraph &cg,
287 function_ref<LogicalResult(CallGraphSCC &)> sccTransformer) {
288 llvm::scc_iterator<const CallGraph *> cgi = llvm::scc_begin(&cg);
289 CallGraphSCC currentSCC(cgi);
290 while (!cgi.isAtEnd()) {
291 // Copy the current SCC and increment so that the transformer can modify the
292 // SCC without invalidating our iterator.
293 currentSCC.reset(*cgi);
294 ++cgi;
295 if (failed(sccTransformer(currentSCC)))
296 return failure();
297 }
298 return success();
299}
300
301/// Collect all of the callable operations within the given range of blocks. If
302/// `traverseNestedCGNodes` is true, this will also collect call operations
303/// inside of nested callgraph nodes.
305 CallGraphNode *sourceNode, CallGraph &cg,
306 SymbolTableCollection &symbolTable,
308 bool traverseNestedCGNodes) {
310 auto addToWorklist = [&](CallGraphNode *node,
312 for (Block &block : blocks)
313 worklist.emplace_back(&block, node);
314 };
315
316 addToWorklist(sourceNode, blocks);
317 while (!worklist.empty()) {
318 Block *block;
319 std::tie(block, sourceNode) = worklist.pop_back_val();
320
321 for (Operation &op : *block) {
322 if (auto call = dyn_cast<CallOpInterface>(op)) {
323 // TODO: Support inlining nested call references.
324 CallInterfaceCallable callable = call.getCallableForCallee();
325 if (SymbolRefAttr symRef = dyn_cast<SymbolRefAttr>(callable)) {
326 if (!isa<FlatSymbolRefAttr>(symRef))
327 continue;
328 }
329
330 CallGraphNode *targetNode = cg.resolveCallable(call, symbolTable);
331 if (!targetNode->isExternal())
332 calls.emplace_back(call, sourceNode, targetNode);
333 continue;
334 }
335
336 // If this is not a call, traverse the nested regions. If
337 // `traverseNestedCGNodes` is false, then don't traverse nested call graph
338 // regions.
339 for (auto &nestedRegion : op.getRegions()) {
340 CallGraphNode *nestedNode = cg.lookupNode(&nestedRegion);
341 if (traverseNestedCGNodes || !nestedNode)
342 addToWorklist(nestedNode ? nestedNode : sourceNode, nestedRegion);
343 }
344 }
345 }
346}
347
348//===----------------------------------------------------------------------===//
349// InlinerInterfaceImpl
350//===----------------------------------------------------------------------===//
351
352static std::string getNodeName(CallOpInterface op) {
353 if (llvm::dyn_cast_if_present<SymbolRefAttr>(op.getCallableForCallee()))
354 return debugString(op);
355 return "_unnamed_callee_";
356}
357
358/// Return true if the specified `inlineHistoryID` indicates an inline history
359/// that already includes `node`.
361 CallGraphNode *node, std::optional<size_t> inlineHistoryID,
362 MutableArrayRef<std::pair<CallGraphNode *, std::optional<size_t>>>
363 inlineHistory) {
364 while (inlineHistoryID.has_value()) {
365 assert(*inlineHistoryID < inlineHistory.size() &&
366 "Invalid inline history ID");
367 if (inlineHistory[*inlineHistoryID].first == node)
368 return true;
369 inlineHistoryID = inlineHistory[*inlineHistoryID].second;
370 }
371 return false;
372}
373
374namespace {
375/// This class provides a specialization of the main inlining interface.
376struct InlinerInterfaceImpl : public InlinerInterface {
377 InlinerInterfaceImpl(MLIRContext *context, CallGraph &cg,
378 SymbolTableCollection &symbolTable)
379 : InlinerInterface(context), cg(cg), symbolTable(symbolTable) {}
380
381 /// Process a set of blocks that have been inlined. This callback is invoked
382 /// *before* inlined terminator operations have been processed.
383 void
384 processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks) final {
385 // Find the closest callgraph node from the first block.
386 CallGraphNode *node;
387 Region *region = inlinedBlocks.begin()->getParent();
388 while (!(node = cg.lookupNode(region))) {
389 region = region->getParentRegion();
390 assert(region && "expected valid parent node");
391 }
392
393 collectCallOps(inlinedBlocks, node, cg, symbolTable, calls,
394 /*traverseNestedCGNodes=*/true);
395 }
396
397 /// Mark the given callgraph node for deletion.
398 void markForDeletion(CallGraphNode *node) { deadNodes.insert(node); }
399
400 /// This method properly disposes of callables that became dead during
401 /// inlining. This should not be called while iterating over the SCCs.
402 void eraseDeadCallables() {
403 for (CallGraphNode *node : deadNodes)
404 node->getCallableRegion()->getParentOp()->erase();
405 }
406
407 /// The set of callables known to be dead.
408 SmallPtrSet<CallGraphNode *, 8> deadNodes;
409
410 /// The current set of call instructions to consider for inlining.
411 SmallVector<ResolvedCall, 8> calls;
412
413 /// The callgraph being operated on.
414 CallGraph &cg;
415
416 /// A symbol table to use when resolving call lookups.
417 SymbolTableCollection &symbolTable;
418};
419} // namespace
420
421namespace mlir {
422
423using CallGraphEdge = std::pair<CallGraphNode *, CallGraphNode *>;
425
427public:
428 Impl(Inliner &inliner) : inliner(inliner) {}
429
430 /// Attempt to inline calls within the given scc, and run simplifications,
431 /// until a fixed point is reached. This allows for the inlining of newly
432 /// devirtualized calls. Returns failure if there was a fatal error during
433 /// inlining.
434 LogicalResult inlineSCC(InlinerInterfaceImpl &inlinerIface,
435 CGUseList &useList, CallGraphSCC &currentSCC,
436 MLIRContext *context);
437
438private:
439 /// Optimize the nodes within the given SCC with one of the held optimization
440 /// pass pipelines. Returns failure if an error occurred during the
441 /// optimization of the SCC, success otherwise.
442 LogicalResult optimizeSCC(CallGraph &cg, CGUseList &useList,
443 CallGraphSCC &currentSCC, MLIRContext *context);
444
445 /// Optimize the nodes within the given SCC in parallel. Returns failure if an
446 /// error occurred during the optimization of the SCC, success otherwise.
447 LogicalResult optimizeSCCAsync(MutableArrayRef<CallGraphNode *> nodesToVisit,
448 MLIRContext *context);
449
450 /// Optimize the given callable node with one of the pass managers provided
451 /// with `pipelines`, or the generic pre-inline pipeline. Returns failure if
452 /// an error occurred during the optimization of the callable, success
453 /// otherwise.
454 LogicalResult optimizeCallable(CallGraphNode *node,
455 llvm::StringMap<OpPassManager> &pipelines);
456
457 /// Attempt to inline calls within the given scc. This function returns
458 /// success if any calls were inlined, failure otherwise.
459 LogicalResult inlineCallsInSCC(InlinerInterfaceImpl &inlinerIface,
460 CGUseList &useList, CallGraphSCC &currentSCC,
461 BlockedEdges &blockedEdges);
462
463 /// Returns true if the given call should be inlined.
464 bool shouldInline(ResolvedCall &resolvedCall);
465
466private:
467 Inliner &inliner;
469};
470
471LogicalResult Inliner::Impl::inlineSCC(InlinerInterfaceImpl &inlinerIface,
472 CGUseList &useList,
473 CallGraphSCC &currentSCC,
474 MLIRContext *context) {
475 // Continuously simplify and inline until we either reach a fixed point, or
476 // hit the maximum iteration count. Simplifying early helps to refine the cost
477 // model, and in future iterations may devirtualize new calls.
478 unsigned iterationCount = 0;
479 // Optimization may replace calls, preventing exact provenance tracking.
480 // Conservatively retain proven recursive graph edges across iterations.
481 BlockedEdges blockedEdges;
482 do {
483 if (failed(optimizeSCC(inlinerIface.cg, useList, currentSCC, context)))
484 return failure();
485 if (failed(
486 inlineCallsInSCC(inlinerIface, useList, currentSCC, blockedEdges)))
487 break;
488 } while (++iterationCount < inliner.config.getMaxInliningIterations());
489 return success();
490}
491
492LogicalResult Inliner::Impl::optimizeSCC(CallGraph &cg, CGUseList &useList,
493 CallGraphSCC &currentSCC,
494 MLIRContext *context) {
495 // Collect the sets of nodes to simplify.
497 for (auto *node : currentSCC) {
498 if (node->isExternal())
499 continue;
500
501 // Don't simplify nodes with children. Nodes with children require special
502 // handling as we may remove the node during simplification. In the future,
503 // we should be able to handle this case with proper node deletion tracking.
504 if (node->hasChildren())
505 continue;
506
507 // We also won't apply simplifications to nodes that can't have passes
508 // scheduled on them.
509 auto *region = node->getCallableRegion();
511 continue;
512 nodesToVisit.push_back(node);
513 }
514 if (nodesToVisit.empty())
515 return success();
516
517 // Optimize each of the nodes within the SCC in parallel.
518 if (failed(optimizeSCCAsync(nodesToVisit, context)))
519 return failure();
520
521 // Recompute the uses held by each of the nodes.
522 for (CallGraphNode *node : nodesToVisit)
523 useList.recomputeUses(node, cg);
524 return success();
525}
526
527LogicalResult
528Inliner::Impl::optimizeSCCAsync(MutableArrayRef<CallGraphNode *> nodesToVisit,
529 MLIRContext *ctx) {
530 // We must maintain a fixed pool of pass managers which is at least as large
531 // as the maximum parallelism of the failableParallelForEach below.
532 // Note: The number of pass managers here needs to remain constant
533 // to prevent issues with pass instrumentations that rely on having the same
534 // pass manager for the main thread.
535 size_t numThreads = ctx->getNumThreads();
536 const auto &opPipelines = inliner.config.getOpPipelines();
537 if (pipelines.size() < numThreads) {
538 pipelines.reserve(numThreads);
539 pipelines.resize(numThreads, opPipelines);
540 }
541
542 // Ensure an analysis manager has been constructed for each of the nodes.
543 // This prevents thread races when running the nested pipelines.
544 for (CallGraphNode *node : nodesToVisit)
545 inliner.am.nest(node->getCallableRegion()->getParentOp());
546
547 // An atomic failure variable for the async executors.
548 std::vector<std::atomic<bool>> activePMs(pipelines.size());
549 llvm::fill(activePMs, false);
550 return failableParallelForEach(ctx, nodesToVisit, [&](CallGraphNode *node) {
551 // Find a pass manager for this operation.
552 auto it = llvm::find_if(activePMs, [](std::atomic<bool> &isActive) {
553 bool expectedInactive = false;
554 return isActive.compare_exchange_strong(expectedInactive, true);
555 });
556 assert(it != activePMs.end() &&
557 "could not find inactive pass manager for thread");
558 unsigned pmIndex = it - activePMs.begin();
559
560 // Optimize this callable node.
561 LogicalResult result = optimizeCallable(node, pipelines[pmIndex]);
562
563 // Reset the active bit for this pass manager.
564 activePMs[pmIndex].store(false);
565 return result;
566 });
567}
568
569LogicalResult
570Inliner::Impl::optimizeCallable(CallGraphNode *node,
571 llvm::StringMap<OpPassManager> &pipelines) {
572 Operation *callable = node->getCallableRegion()->getParentOp();
573 StringRef opName = callable->getName().getStringRef();
574 auto pipelineIt = pipelines.find(opName);
575 const auto &defaultPipeline = inliner.config.getDefaultPipeline();
576 if (pipelineIt == pipelines.end()) {
577 // If a pipeline didn't exist, use the generic pipeline if possible.
578 if (!defaultPipeline)
579 return success();
580
581 OpPassManager defaultPM(opName);
582 defaultPipeline(defaultPM);
583 pipelineIt = pipelines.try_emplace(opName, std::move(defaultPM)).first;
584 }
585 return inliner.runPipelineHelper(inliner.pass, pipelineIt->second, callable);
586}
587
588/// Attempt to inline calls within the given scc. This function returns
589/// success if any calls were inlined, failure otherwise.
590LogicalResult
591Inliner::Impl::inlineCallsInSCC(InlinerInterfaceImpl &inlinerIface,
592 CGUseList &useList, CallGraphSCC &currentSCC,
593 BlockedEdges &blockedEdges) {
594 CallGraph &cg = inlinerIface.cg;
595 auto &calls = inlinerIface.calls;
596
597 // A set of dead nodes to remove after inlining.
598 llvm::SmallSetVector<CallGraphNode *, 1> deadNodes;
599
600 // Collect all of the direct calls within the nodes of the current SCC. We
601 // don't traverse nested callgraph nodes, because they are handled separately
602 // likely within a different SCC.
603 for (CallGraphNode *node : currentSCC) {
604 if (node->isExternal())
605 continue;
606
607 // Don't collect calls if the node is already dead.
608 if (useList.isDead(node)) {
609 deadNodes.insert(node);
610 } else {
611 collectCallOps(*node->getCallableRegion(), node, cg,
612 inlinerIface.symbolTable, calls,
613 /*traverseNestedCGNodes=*/false);
614 }
615 }
616
617 // When inlining a callee produces new call sites, remember that they came
618 // from the callee to avoid recursively inlining through a cycle.
619 using InlineHistoryT = std::optional<size_t>;
620 SmallVector<std::pair<CallGraphNode *, InlineHistoryT>, 8> inlineHistory;
621 std::vector<InlineHistoryT> callHistory(calls.size(), InlineHistoryT{});
622 BlockedEdges newlyBlockedEdges;
623
624 LLVM_DEBUG({
625 LDBG() << "* Inliner: Initial calls in SCC are: {";
626 for (unsigned I = 0, E = calls.size(); I < E; ++I)
627 LDBG() << " " << I << ". " << calls[I].call << ",";
628 LDBG() << "}";
629 });
630
631 // Try to inline each of the call operations. Don't cache the end iterator
632 // here as more calls may be added during inlining.
633 bool inlinedAnyCalls = false;
634 for (unsigned i = 0; i < calls.size(); ++i) {
635 if (deadNodes.contains(calls[i].sourceNode))
636 continue;
637 ResolvedCall it = calls[i];
638
639 InlineHistoryT inlineHistoryID = callHistory[i];
640 bool inHistory =
641 inlineHistoryIncludes(it.targetNode, inlineHistoryID, inlineHistory);
642 auto edge = std::make_pair(it.sourceNode, it.targetNode);
643 if (inHistory)
644 newlyBlockedEdges.insert(edge);
645 bool doInline =
646 !inHistory && !blockedEdges.contains(edge) && shouldInline(it);
647 CallOpInterface call = it.call;
648 LLVM_DEBUG({
649 if (doInline)
650 LDBG() << "* Inlining call: " << i << ". " << call;
651 else
652 LDBG() << "* Not inlining call: " << i << ". " << call;
653 });
654 if (!doInline)
655 continue;
656
657 unsigned prevSize = calls.size();
658 Region *targetRegion = it.targetNode->getCallableRegion();
659
660 // If this is the last call to the target node and the node is discardable,
661 // then inline it in-place and delete the node if successful.
662 bool inlineInPlace = useList.hasOneUseAndDiscardable(it.targetNode);
663
664 LogicalResult inlineResult =
665 inlineCall(inlinerIface, inliner.config.getCloneCallback(), call,
666 cast<CallableOpInterface>(targetRegion->getParentOp()),
667 targetRegion, /*shouldCloneInlinedRegion=*/!inlineInPlace);
668 if (failed(inlineResult)) {
669 LDBG() << "** Failed to inline";
670 continue;
671 }
672 inlinedAnyCalls = true;
673
674 // Record that the new callsites came from inlining the callee.
675 InlineHistoryT newInlineHistoryID{inlineHistory.size()};
676 inlineHistory.push_back(std::make_pair(it.targetNode, inlineHistoryID));
677
678 auto historyToString = [](InlineHistoryT h) {
679 return h.has_value() ? std::to_string(*h) : "root";
680 };
681 LDBG() << "* new inlineHistory entry: " << newInlineHistoryID << ". ["
682 << getNodeName(call) << ", " << historyToString(inlineHistoryID)
683 << "]";
684
685 for (unsigned k = prevSize; k != calls.size(); ++k) {
686 callHistory.push_back(newInlineHistoryID);
687 LDBG() << "* new call " << k << " {" << calls[k].call
688 << "}\n with historyID = " << newInlineHistoryID
689 << ", added due to inlining of\n call {" << call
690 << "}\n with historyID = " << historyToString(inlineHistoryID);
691 }
692
693 // If the inlining was successful, Merge the new uses into the source node.
694 useList.dropCallUses(it.sourceNode, call.getOperation(), cg);
695 useList.mergeUsesAfterInlining(it.targetNode, it.sourceNode);
696
697 // then erase the call.
698 call.erase();
699
700 // If we inlined in place, mark the node for deletion.
701 if (inlineInPlace) {
702 useList.eraseNode(it.targetNode);
703 deadNodes.insert(it.targetNode);
704 }
705 }
706
707 for (CallGraphNode *node : deadNodes) {
708 currentSCC.remove(node);
709 inlinerIface.markForDeletion(node);
710 }
711 // Delay blocking until the next iteration so independent calls on the same
712 // edge in the current worklist retain their inlining opportunities.
713 blockedEdges.insert(newlyBlockedEdges.begin(), newlyBlockedEdges.end());
714 calls.clear();
715 return success(inlinedAnyCalls);
716}
717
718/// Returns true if the given call should be inlined.
719bool Inliner::Impl::shouldInline(ResolvedCall &resolvedCall) {
720 // Don't allow inlining terminator calls. We currently don't support this
721 // case.
722 if (resolvedCall.call->hasTrait<OpTrait::IsTerminator>())
723 return false;
724
725 // Don't allow inlining if the target is a self-recursive function.
726 // Don't allow inlining if the call graph is like A->B->A.
727 if (llvm::count_if(*resolvedCall.targetNode,
728 [&](CallGraphNode::Edge const &edge) -> bool {
729 return edge.getTarget() == resolvedCall.targetNode ||
730 edge.getTarget() == resolvedCall.sourceNode;
731 }) > 0)
732 return false;
733
734 // Don't allow inlining if the target is an ancestor of the call. This
735 // prevents inlining recursively.
736 Region *callableRegion = resolvedCall.targetNode->getCallableRegion();
737 if (callableRegion->isAncestor(resolvedCall.call->getParentRegion()))
738 return false;
739
740 // Don't allow inlining if the callee has multiple blocks (unstructured
741 // control flow) but we cannot be sure that the caller region supports that.
742 if (!inliner.config.getCanHandleMultipleBlocks()) {
743 bool calleeHasMultipleBlocks =
744 llvm::hasNItemsOrMore(*callableRegion, /*N=*/2);
745 // If both parent ops have the same type, it is safe to inline. Otherwise,
746 // decide based on whether the op has the SingleBlock trait or not.
747 // Note: This check does currently not account for
748 // SizedRegion/MaxSizedRegion.
749 auto callerRegionSupportsMultipleBlocks = [&]() {
750 return callableRegion->getParentOp()->getName() ==
751 resolvedCall.call->getParentOp()->getName() ||
752 !resolvedCall.call->getParentOp()
753 ->mightHaveTrait<OpTrait::SingleBlock>();
754 };
755 if (calleeHasMultipleBlocks && !callerRegionSupportsMultipleBlocks())
756 return false;
757 }
758
759 if (!inliner.isProfitableToInline(resolvedCall))
760 return false;
761
762 // Otherwise, inline.
763 return true;
764}
765
766LogicalResult Inliner::doInlining() {
767 Impl impl(*this);
768 auto *context = op->getContext();
769 // Run the inline transform in post-order over the SCCs in the callgraph.
770 SymbolTableCollection symbolTable;
771 // FIXME: some clean-up can be done for the arguments
772 // of the Impl's methods, if the inlinerIface and useList
773 // become the states of the Impl.
774 InlinerInterfaceImpl inlinerIface(context, cg, symbolTable);
775 CGUseList useList(op, cg, symbolTable);
776 LogicalResult result = runTransformOnCGSCCs(cg, [&](CallGraphSCC &scc) {
777 return impl.inlineSCC(inlinerIface, useList, scc, context);
778 });
779 if (failed(result))
780 return result;
781
782 // After inlining, make sure to erase any callables proven to be dead.
783 inlinerIface.eraseDeadCallables();
784 return success();
785}
786} // namespace mlir
return success()
lhs
static void collectCallOps(iterator_range< Region::iterator > blocks, CallGraphNode *sourceNode, CallGraph &cg, SymbolTableCollection &symbolTable, SmallVectorImpl< ResolvedCall > &calls, bool traverseNestedCGNodes)
Collect all of the callable operations within the given range of blocks.
Definition Inliner.cpp:304
Inliner::ResolvedCall ResolvedCall
Definition Inliner.cpp:31
static void walkReferencedSymbolNodes(Operation *op, CallGraph &cg, SymbolTableCollection &symbolTable, DenseMap< Attribute, CallGraphNode * > &resolvedRefs, function_ref< void(CallGraphNode *, Operation *)> callback)
Walk all of the used symbol callgraph nodes referenced with the given op.
Definition Inliner.cpp:38
static std::string getNodeName(CallOpInterface op)
Definition Inliner.cpp:352
static bool inlineHistoryIncludes(CallGraphNode *node, std::optional< size_t > inlineHistoryID, MutableArrayRef< std::pair< CallGraphNode *, std::optional< size_t > > > inlineHistory)
Return true if the specified inlineHistoryID indicates an inline history that already includes node.
Definition Inliner.cpp:360
static LogicalResult runTransformOnCGSCCs(const CallGraph &cg, function_ref< LogicalResult(CallGraphSCC &)> sccTransformer)
Run a given transformation over the SCCs of the callgraph in a bottom up traversal.
Definition Inliner.cpp:285
Block represents an ordered list of Operations.
Definition Block.h:33
This class represents a single callable in the callgraph.
Definition CallGraph.h:40
bool isExternal() const
Returns true if this node is an external node.
Definition CallGraph.cpp:32
bool hasChildren() const
Returns true if this node has any child edges.
Definition CallGraph.cpp:59
Region * getCallableRegion() const
Returns the callable region this node represents.
Definition CallGraph.cpp:36
iterator begin() const
Definition CallGraph.h:111
CallGraphNode * resolveCallable(CallOpInterface call, SymbolTableCollection &symbolTable) const
Resolve the callable for given callee to a node in the callgraph, or the external node if a valid nod...
CallGraphNode * lookupNode(Region *region) const
Lookup a call graph node for the given region, or nullptr if none is registered.
LogicalResult inlineSCC(InlinerInterfaceImpl &inlinerIface, CGUseList &useList, CallGraphSCC &currentSCC, MLIRContext *context)
Attempt to inline calls within the given scc, and run simplifications, until a fixed point is reached...
Definition Inliner.cpp:471
Impl(Inliner &inliner)
Definition Inliner.cpp:428
Inliner(Operation *op, CallGraph &cg, Pass &pass, AnalysisManager am, RunPipelineHelperTy runPipelineHelper, const InlinerConfig &config, ProfitabilityCallbackTy isProfitableToInline)
Definition Inliner.h:127
LogicalResult doInlining()
Perform inlining on a OpTrait::SymbolTable operation.
Definition Inliner.cpp:766
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
unsigned getNumThreads()
Return the number of threads used by the thread pool in this context.
This class provides the API for ops that are known to be isolated from above.
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:731
bool use_empty()
Returns true if this operation has no uses.
Definition Operation.h:897
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:794
bool hasOneUse()
Returns true if this operation has exactly one use.
Definition Operation.h:894
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
Definition Operation.h:251
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:722
void erase()
Remove this operation from its parent block and delete it.
Region * getParentRegion()
Return the region containing this region or nullptr if the region is attached to a top-level operatio...
Definition Region.cpp:45
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
Definition Region.h:249
iterator_range< OpIterator > getOps()
Definition Region.h:185
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:213
This class represents a collection of SymbolTables.
virtual Operation * lookupNearestSymbolFrom(Operation *from, StringAttr symbol)
Returns the operation registered with the given symbol name within the closest parent operation of,...
This class represents a specific symbol use.
static void walkSymbolTables(Operation *op, bool allSymUsesVisible, function_ref< void(Operation *, bool)> callback)
Walks all symbol table operations nested within, and including, op.
static std::optional< UseRange > getSymbolUses(Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
LogicalResult failableParallelForEach(MLIRContext *context, IteratorT begin, IteratorT end, FuncT &&func)
Invoke the given function on the elements between [begin, end) asynchronously.
Definition Threading.h:36
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
std::pair< CallGraphNode *, CallGraphNode * > CallGraphEdge
Definition Inliner.cpp:423
static std::string debugString(T &&op)
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
LogicalResult inlineCall(InlinerInterface &interface, function_ref< InlinerInterface::CloneCallbackSigTy > cloneCallback, CallOpInterface call, CallableOpInterface callable, Region *src, bool shouldCloneInlinedRegion=true)
This function inlines a given region, 'src', of a callable operation, 'callable', into the location d...
DenseSet< CallGraphEdge > BlockedEdges
Definition Inliner.cpp:424
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
llvm::function_ref< Fn > function_ref
Definition LLVM.h:147
A callable is either a symbol, or an SSA value, that is referenced by a call-like operation.
This struct represents a resolved call to a given callgraph node.
Definition Inliner.h:109
CallGraphNode * sourceNode
Definition Inliner.h:114
CallOpInterface call
Definition Inliner.h:113
CallGraphNode * targetNode
Definition Inliner.h:114