22#include "llvm/ADT/DenseSet.h"
23#include "llvm/ADT/SCCIterator.h"
24#include "llvm/ADT/STLExtras.h"
25#include "llvm/Support/DebugLog.h"
27#define DEBUG_TYPE "inlining"
43 assert(symbolUses &&
"expected uses to be valid");
47 auto refIt = resolvedRefs.try_emplace(use.getSymbolRef());
55 auto callableOp = dyn_cast_or_null<CallableOpInterface>(symbolOp);
58 node = cg.
lookupNode(callableOp.getCallableRegion());
61 callback(node, use.getUser());
87 CGUseList(Operation *op, CallGraph &cg, SymbolTableCollection &symbolTable);
91 void dropCallUses(CallGraphNode *userNode, Operation *callOp, CallGraph &cg);
94 void eraseNode(CallGraphNode *node);
97 bool isDead(CallGraphNode *node)
const;
101 bool hasOneUseAndDiscardable(CallGraphNode *node)
const;
104 void recomputeUses(CallGraphNode *node, CallGraph &cg);
108 void mergeUsesAfterInlining(CallGraphNode *
lhs, CallGraphNode *
rhs);
112 void decrementDiscardableUses(CGUser &uses);
123 SymbolTableCollection &symbolTable;
129 : symbolTable(symbolTable) {
134 auto walkFn = [&](
Operation *symbolTableOp,
bool allUsesVisible) {
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);
156 for (
auto &it : alwaysLiveNodes)
157 discardableSymNodeUses.erase(it.second);
161 recomputeUses(node, 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())
172 --discardableSymNodeUses[node];
178void CGUseList::eraseNode(CallGraphNode *node) {
180 for (
auto &edge : *node)
182 eraseNode(edge.getTarget());
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);
192bool CGUseList::isDead(CallGraphNode *node)
const {
195 if (!isa<SymbolOpInterface>(nodeOp))
199 auto symbolIt = discardableSymNodeUses.find(node);
200 return symbolIt != discardableSymNodeUses.end() && symbolIt->second == 0;
203bool CGUseList::hasOneUseAndDiscardable(CallGraphNode *node)
const {
206 if (!isa<SymbolOpInterface>(nodeOp))
210 auto symbolIt = discardableSymNodeUses.find(node);
211 return symbolIt != discardableSymNodeUses.end() && symbolIt->second == 1;
214void CGUseList::recomputeUses(CallGraphNode *node, CallGraph &cg) {
216 CGUser &uses = nodeUses[node];
217 decrementDiscardableUses(uses);
222 auto walkFn = [&](CallGraphNode *refNode, Operation *user) {
223 auto discardSymIt = discardableSymNodeUses.find(refNode);
224 if (discardSymIt == discardableSymNodeUses.end())
227 if (user != parentOp)
228 ++uses.innerUses[refNode];
229 else if (!uses.topLevelUses.insert(refNode).second)
231 ++discardSymIt->second;
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;
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;
259 CallGraphSCC(llvm::scc_iterator<const CallGraph *> &parentIterator)
260 : parentIterator(parentIterator) {}
262 std::vector<CallGraphNode *>::iterator begin() {
return nodes.begin(); }
263 std::vector<CallGraphNode *>::iterator end() {
return nodes.end(); }
266 void reset(
const std::vector<CallGraphNode *> &newNodes) { nodes = newNodes; }
269 void remove(CallGraphNode *node) {
270 auto it = llvm::find(nodes, node);
271 if (it != nodes.end()) {
273 parentIterator.ReplaceNode(node,
nullptr);
278 std::vector<CallGraphNode *> nodes;
279 llvm::scc_iterator<const CallGraph *> &parentIterator;
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()) {
293 currentSCC.reset(*cgi);
295 if (failed(sccTransformer(currentSCC)))
308 bool traverseNestedCGNodes) {
312 for (
Block &block : blocks)
313 worklist.emplace_back(&block, node);
316 addToWorklist(sourceNode, blocks);
317 while (!worklist.empty()) {
319 std::tie(block, sourceNode) = worklist.pop_back_val();
322 if (
auto call = dyn_cast<CallOpInterface>(op)) {
325 if (SymbolRefAttr symRef = dyn_cast<SymbolRefAttr>(callable)) {
326 if (!isa<FlatSymbolRefAttr>(symRef))
332 calls.emplace_back(call, sourceNode, targetNode);
341 if (traverseNestedCGNodes || !nestedNode)
342 addToWorklist(nestedNode ? nestedNode : sourceNode, nestedRegion);
353 if (llvm::dyn_cast_if_present<SymbolRefAttr>(op.getCallableForCallee()))
355 return "_unnamed_callee_";
364 while (inlineHistoryID.has_value()) {
365 assert(*inlineHistoryID < inlineHistory.size() &&
366 "Invalid inline history ID");
367 if (inlineHistory[*inlineHistoryID].first == node)
369 inlineHistoryID = inlineHistory[*inlineHistoryID].second;
376struct InlinerInterfaceImpl :
public InlinerInterface {
377 InlinerInterfaceImpl(MLIRContext *context, CallGraph &cg,
378 SymbolTableCollection &symbolTable)
379 : InlinerInterface(context), cg(cg), symbolTable(symbolTable) {}
384 processInlinedBlocks(iterator_range<Region::iterator> inlinedBlocks)
final {
387 Region *region = inlinedBlocks.
begin()->getParent();
390 assert(region &&
"expected valid parent node");
398 void markForDeletion(CallGraphNode *node) { deadNodes.insert(node); }
402 void eraseDeadCallables() {
403 for (CallGraphNode *node : deadNodes)
408 SmallPtrSet<CallGraphNode *, 8> deadNodes;
411 SmallVector<ResolvedCall, 8> calls;
417 SymbolTableCollection &symbolTable;
434 LogicalResult inlineSCC(InlinerInterfaceImpl &inlinerIface,
435 CGUseList &useList, CallGraphSCC ¤tSCC,
445 LogicalResult optimizeSCC(
CallGraph &cg, CGUseList &useList,
447 bool inlinedIntoSCC);
459 llvm::StringMap<OpPassManager> &pipelines);
463 LogicalResult inlineCallsInSCC(InlinerInterfaceImpl &inlinerIface,
464 CGUseList &useList, CallGraphSCC ¤tSCC,
477 CallGraphSCC ¤tSCC,
482 unsigned iterationCount = 0;
487 if (failed(optimizeSCC(inlinerIface.cg, useList, currentSCC, context,
488 iterationCount != 0)))
491 inlineCallsInSCC(inlinerIface, useList, currentSCC, blockedEdges)))
493 }
while (++iterationCount < inliner.config.getMaxInliningIterations());
497LogicalResult Inliner::Impl::optimizeSCC(
CallGraph &cg, CGUseList &useList,
498 CallGraphSCC ¤tSCC,
500 bool inlinedIntoSCC) {
506 if (!inlinedIntoSCC && inliner.config.getOpPipelines().empty() &&
507 !inliner.config.getDefaultPipeline())
512 for (
auto *node : currentSCC) {
527 nodesToVisit.push_back(node);
529 if (nodesToVisit.empty())
533 if (failed(optimizeSCCAsync(nodesToVisit, context)))
538 useList.recomputeUses(node, cg);
551 const auto &opPipelines = inliner.config.getOpPipelines();
552 if (pipelines.size() < numThreads) {
553 pipelines.reserve(numThreads);
554 pipelines.resize(numThreads, opPipelines);
559 for (CallGraphNode *node : nodesToVisit)
563 std::vector<std::atomic<bool>> activePMs(pipelines.size());
564 llvm::fill(activePMs,
false);
567 auto it = llvm::find_if(activePMs, [](std::atomic<bool> &isActive) {
568 bool expectedInactive =
false;
569 return isActive.compare_exchange_strong(expectedInactive,
true);
571 assert(it != activePMs.end() &&
572 "could not find inactive pass manager for thread");
573 unsigned pmIndex = it - activePMs.begin();
576 LogicalResult
result = optimizeCallable(node, pipelines[pmIndex]);
579 activePMs[pmIndex].store(
false);
585Inliner::Impl::optimizeCallable(CallGraphNode *node,
586 llvm::StringMap<OpPassManager> &pipelines) {
589 auto pipelineIt = pipelines.find(opName);
590 const auto &defaultPipeline = inliner.config.getDefaultPipeline();
591 if (pipelineIt == pipelines.end()) {
593 if (!defaultPipeline)
596 OpPassManager defaultPM(opName);
597 defaultPipeline(defaultPM);
598 pipelineIt = pipelines.try_emplace(opName, std::move(defaultPM)).first;
600 return inliner.runPipelineHelper(inliner.pass, pipelineIt->second, callable);
606Inliner::Impl::inlineCallsInSCC(InlinerInterfaceImpl &inlinerIface,
607 CGUseList &useList, CallGraphSCC ¤tSCC,
609 CallGraph &cg = inlinerIface.cg;
610 auto &calls = inlinerIface.calls;
613 llvm::SmallSetVector<CallGraphNode *, 1> deadNodes;
618 for (CallGraphNode *node : currentSCC) {
623 if (useList.isDead(node)) {
624 deadNodes.insert(node);
627 inlinerIface.symbolTable, calls,
634 using InlineHistoryT = std::optional<size_t>;
635 SmallVector<std::pair<CallGraphNode *, InlineHistoryT>, 8> inlineHistory;
636 std::vector<InlineHistoryT> callHistory(calls.size(), InlineHistoryT{});
640 LDBG() <<
"* Inliner: Initial calls in SCC are: {";
641 for (
unsigned I = 0, E = calls.size(); I < E; ++I)
642 LDBG() <<
" " << I <<
". " << calls[I].call <<
",";
648 bool inlinedAnyCalls =
false;
649 for (
unsigned i = 0; i < calls.size(); ++i) {
650 if (deadNodes.contains(calls[i].sourceNode))
654 InlineHistoryT inlineHistoryID = callHistory[i];
659 newlyBlockedEdges.insert(edge);
661 !inHistory && !blockedEdges.contains(edge) && shouldInline(it);
662 CallOpInterface call = it.
call;
665 LDBG() <<
"* Inlining call: " << i <<
". " << call;
667 LDBG() <<
"* Not inlining call: " << i <<
". " << call;
672 unsigned prevSize = calls.size();
677 bool inlineInPlace = useList.hasOneUseAndDiscardable(it.
targetNode);
679 LogicalResult inlineResult =
680 inlineCall(inlinerIface, inliner.config.getCloneCallback(), call,
681 cast<CallableOpInterface>(targetRegion->
getParentOp()),
682 targetRegion, !inlineInPlace);
683 if (
failed(inlineResult)) {
684 LDBG() <<
"** Failed to inline";
687 inlinedAnyCalls =
true;
690 InlineHistoryT newInlineHistoryID{inlineHistory.size()};
691 inlineHistory.push_back(std::make_pair(it.
targetNode, inlineHistoryID));
693 auto historyToString = [](InlineHistoryT h) {
694 return h.has_value() ? std::to_string(*h) :
"root";
696 LDBG() <<
"* new inlineHistory entry: " << newInlineHistoryID <<
". ["
697 <<
getNodeName(call) <<
", " << historyToString(inlineHistoryID)
700 for (
unsigned k = prevSize; k != calls.size(); ++k) {
701 callHistory.push_back(newInlineHistoryID);
702 LDBG() <<
"* new call " << k <<
" {" << calls[k].call
703 <<
"}\n with historyID = " << newInlineHistoryID
704 <<
", added due to inlining of\n call {" << call
705 <<
"}\n with historyID = " << historyToString(inlineHistoryID);
709 useList.dropCallUses(it.
sourceNode, call.getOperation(), cg);
722 for (CallGraphNode *node : deadNodes) {
723 currentSCC.remove(node);
724 inlinerIface.markForDeletion(node);
728 blockedEdges.insert(newlyBlockedEdges.begin(), newlyBlockedEdges.end());
730 return success(inlinedAnyCalls);
734bool Inliner::Impl::shouldInline(
ResolvedCall &resolvedCall) {
737 if (resolvedCall.
call->hasTrait<OpTrait::IsTerminator>())
743 [&](CallGraphNode::Edge
const &edge) ->
bool {
744 return edge.getTarget() == resolvedCall.targetNode ||
745 edge.getTarget() == resolvedCall.sourceNode;
752 if (callableRegion->
isAncestor(resolvedCall.
call->getParentRegion()))
757 if (!inliner.config.getCanHandleMultipleBlocks()) {
758 bool calleeHasMultipleBlocks =
759 llvm::hasNItemsOrMore(*callableRegion, 2);
764 auto callerRegionSupportsMultipleBlocks = [&]() {
766 resolvedCall.
call->getParentOp()->getName() ||
767 !resolvedCall.
call->getParentOp()
768 ->mightHaveTrait<OpTrait::SingleBlock>();
770 if (calleeHasMultipleBlocks && !callerRegionSupportsMultipleBlocks())
774 if (!inliner.isProfitableToInline(resolvedCall))
783 auto *context = op->getContext();
789 InlinerInterfaceImpl inlinerIface(context, cg, symbolTable);
790 CGUseList useList(op, cg, symbolTable);
792 return impl.inlineSCC(inlinerIface, useList, scc, context);
798 inlinerIface.eraseDeadCallables();
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.
Inliner::ResolvedCall ResolvedCall
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.
static std::string getNodeName(CallOpInterface op)
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.
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.
Block represents an ordered list of Operations.
This class represents a single callable in the callgraph.
bool isExternal() const
Returns true if this node is an external node.
bool hasChildren() const
Returns true if this node has any child edges.
Region * getCallableRegion() const
Returns the callable region this node represents.
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 ¤tSCC, MLIRContext *context)
Attempt to inline calls within the given scc, and run simplifications, until a fixed point is reached...
Inliner(Operation *op, CallGraph &cg, Pass &pass, AnalysisManager am, RunPipelineHelperTy runPipelineHelper, const InlinerConfig &config, ProfitabilityCallbackTy isProfitableToInline)
LogicalResult doInlining()
Perform inlining on a OpTrait::SymbolTable operation.
MLIRContext is the top-level object for a collection of MLIR operations.
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.
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
bool use_empty()
Returns true if this operation has no uses.
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
bool hasOneUse()
Returns true if this operation has exactly one use.
Block * getBlock()
Returns the operation block that contains this operation.
Operation * getParentOp()
Returns the closest surrounding operation that contains this operation or nullptr if this is a top-le...
OperationName getName()
The name of an operation is the key identifier for it.
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
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...
bool isAncestor(Region *other)
Return true if this region is ancestor of the other region.
iterator_range< OpIterator > getOps()
Operation * getParentOp()
Return the parent operation this region is attached to.
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...
Include the generated interface declarations.
LogicalResult failableParallelForEach(MLIRContext *context, IteratorT begin, IteratorT end, FuncT &&func)
Invoke the given function on each element, with ordered diagnostics and early termination on failure.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
std::pair< CallGraphNode *, CallGraphNode * > CallGraphEdge
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
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
std::string debugString(T &&op)
llvm::function_ref< Fn > function_ref
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.
CallGraphNode * sourceNode
CallGraphNode * targetNode