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,
442 LogicalResult optimizeSCC(
CallGraph &cg, CGUseList &useList,
455 llvm::StringMap<OpPassManager> &pipelines);
459 LogicalResult inlineCallsInSCC(InlinerInterfaceImpl &inlinerIface,
460 CGUseList &useList, CallGraphSCC ¤tSCC,
473 CallGraphSCC ¤tSCC,
478 unsigned iterationCount = 0;
483 if (failed(optimizeSCC(inlinerIface.cg, useList, currentSCC, context)))
486 inlineCallsInSCC(inlinerIface, useList, currentSCC, blockedEdges)))
488 }
while (++iterationCount < inliner.config.getMaxInliningIterations());
492LogicalResult Inliner::Impl::optimizeSCC(
CallGraph &cg, CGUseList &useList,
493 CallGraphSCC ¤tSCC,
497 for (
auto *node : currentSCC) {
512 nodesToVisit.push_back(node);
514 if (nodesToVisit.empty())
518 if (failed(optimizeSCCAsync(nodesToVisit, context)))
523 useList.recomputeUses(node, cg);
536 const auto &opPipelines = inliner.config.getOpPipelines();
537 if (pipelines.size() < numThreads) {
538 pipelines.reserve(numThreads);
539 pipelines.resize(numThreads, opPipelines);
544 for (CallGraphNode *node : nodesToVisit)
548 std::vector<std::atomic<bool>> activePMs(pipelines.size());
549 llvm::fill(activePMs,
false);
552 auto it = llvm::find_if(activePMs, [](std::atomic<bool> &isActive) {
553 bool expectedInactive =
false;
554 return isActive.compare_exchange_strong(expectedInactive,
true);
556 assert(it != activePMs.end() &&
557 "could not find inactive pass manager for thread");
558 unsigned pmIndex = it - activePMs.begin();
561 LogicalResult
result = optimizeCallable(node, pipelines[pmIndex]);
564 activePMs[pmIndex].store(
false);
570Inliner::Impl::optimizeCallable(CallGraphNode *node,
571 llvm::StringMap<OpPassManager> &pipelines) {
574 auto pipelineIt = pipelines.find(opName);
575 const auto &defaultPipeline = inliner.config.getDefaultPipeline();
576 if (pipelineIt == pipelines.end()) {
578 if (!defaultPipeline)
581 OpPassManager defaultPM(opName);
582 defaultPipeline(defaultPM);
583 pipelineIt = pipelines.try_emplace(opName, std::move(defaultPM)).first;
585 return inliner.runPipelineHelper(inliner.pass, pipelineIt->second, callable);
591Inliner::Impl::inlineCallsInSCC(InlinerInterfaceImpl &inlinerIface,
592 CGUseList &useList, CallGraphSCC ¤tSCC,
594 CallGraph &cg = inlinerIface.cg;
595 auto &calls = inlinerIface.calls;
598 llvm::SmallSetVector<CallGraphNode *, 1> deadNodes;
603 for (CallGraphNode *node : currentSCC) {
608 if (useList.isDead(node)) {
609 deadNodes.insert(node);
612 inlinerIface.symbolTable, calls,
619 using InlineHistoryT = std::optional<size_t>;
620 SmallVector<std::pair<CallGraphNode *, InlineHistoryT>, 8> inlineHistory;
621 std::vector<InlineHistoryT> callHistory(calls.size(), InlineHistoryT{});
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 <<
",";
633 bool inlinedAnyCalls =
false;
634 for (
unsigned i = 0; i < calls.size(); ++i) {
635 if (deadNodes.contains(calls[i].sourceNode))
639 InlineHistoryT inlineHistoryID = callHistory[i];
644 newlyBlockedEdges.insert(edge);
646 !inHistory && !blockedEdges.contains(edge) && shouldInline(it);
647 CallOpInterface call = it.
call;
650 LDBG() <<
"* Inlining call: " << i <<
". " << call;
652 LDBG() <<
"* Not inlining call: " << i <<
". " << call;
657 unsigned prevSize = calls.size();
662 bool inlineInPlace = useList.hasOneUseAndDiscardable(it.
targetNode);
664 LogicalResult inlineResult =
665 inlineCall(inlinerIface, inliner.config.getCloneCallback(), call,
666 cast<CallableOpInterface>(targetRegion->
getParentOp()),
667 targetRegion, !inlineInPlace);
668 if (
failed(inlineResult)) {
669 LDBG() <<
"** Failed to inline";
672 inlinedAnyCalls =
true;
675 InlineHistoryT newInlineHistoryID{inlineHistory.size()};
676 inlineHistory.push_back(std::make_pair(it.
targetNode, inlineHistoryID));
678 auto historyToString = [](InlineHistoryT h) {
679 return h.has_value() ? std::to_string(*h) :
"root";
681 LDBG() <<
"* new inlineHistory entry: " << newInlineHistoryID <<
". ["
682 <<
getNodeName(call) <<
", " << historyToString(inlineHistoryID)
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);
694 useList.dropCallUses(it.
sourceNode, call.getOperation(), cg);
707 for (CallGraphNode *node : deadNodes) {
708 currentSCC.remove(node);
709 inlinerIface.markForDeletion(node);
713 blockedEdges.insert(newlyBlockedEdges.begin(), newlyBlockedEdges.end());
715 return success(inlinedAnyCalls);
719bool Inliner::Impl::shouldInline(
ResolvedCall &resolvedCall) {
722 if (resolvedCall.
call->hasTrait<OpTrait::IsTerminator>())
728 [&](CallGraphNode::Edge
const &edge) ->
bool {
729 return edge.getTarget() == resolvedCall.targetNode ||
730 edge.getTarget() == resolvedCall.sourceNode;
737 if (callableRegion->
isAncestor(resolvedCall.
call->getParentRegion()))
742 if (!inliner.config.getCanHandleMultipleBlocks()) {
743 bool calleeHasMultipleBlocks =
744 llvm::hasNItemsOrMore(*callableRegion, 2);
749 auto callerRegionSupportsMultipleBlocks = [&]() {
751 resolvedCall.
call->getParentOp()->getName() ||
752 !resolvedCall.
call->getParentOp()
753 ->mightHaveTrait<OpTrait::SingleBlock>();
755 if (calleeHasMultipleBlocks && !callerRegionSupportsMultipleBlocks())
759 if (!inliner.isProfitableToInline(resolvedCall))
768 auto *context = op->getContext();
774 InlinerInterfaceImpl inlinerIface(context, cg, symbolTable);
775 CGUseList useList(op, cg, symbolTable);
777 return impl.inlineSCC(inlinerIface, useList, scc, context);
783 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 the elements between [begin, end) asynchronously.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
std::pair< CallGraphNode *, CallGraphNode * > CallGraphEdge
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
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
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