MLIR 24.0.0git
CallGraph.cpp
Go to the documentation of this file.
1//===- CallGraph.cpp - CallGraph analysis for MLIR ------------------------===//
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 contains interfaces and analyses for defining a nested callgraph.
10//
11//===----------------------------------------------------------------------===//
12
14#include "mlir/IR/Operation.h"
15#include "mlir/IR/SymbolTable.h"
17#include "mlir/Support/LLVM.h"
18#include "llvm/ADT/SCCIterator.h"
19#include "llvm/ADT/STLExtras.h"
20#include "llvm/ADT/iterator_range.h"
21#include "llvm/Support/raw_ostream.h"
22#include <cassert>
23#include <memory>
24
25using namespace mlir;
26
27//===----------------------------------------------------------------------===//
28// CallGraphNode
29//===----------------------------------------------------------------------===//
30
31/// Returns true if this node refers to the indirect/external node.
32bool CallGraphNode::isExternal() const { return !callableRegion; }
33
34/// Return the callable region this node represents. This can only be called
35/// on non-external nodes.
37 assert(!isExternal() && "the external node has no callable region");
38 return callableRegion;
39}
40
41/// Adds an reference edge to the given node. This is only valid on the
42/// external node.
43void CallGraphNode::addAbstractEdge(CallGraphNode *node) {
44 assert(isExternal() && "abstract edges are only valid on external nodes");
45 addEdge(node, Edge::Kind::Abstract);
46}
47
48/// Add an outgoing call edge from this node.
49void CallGraphNode::addCallEdge(CallGraphNode *node) {
50 addEdge(node, Edge::Kind::Call);
51}
52
53/// Adds a reference edge to the given child node.
54void CallGraphNode::addChildEdge(CallGraphNode *child) {
55 addEdge(child, Edge::Kind::Child);
56}
57
58/// Returns true if this node has any child edges.
60 return llvm::any_of(edges, [](const Edge &edge) { return edge.isChild(); });
61}
62
63/// Add an edge to 'node' with the given kind.
64void CallGraphNode::addEdge(CallGraphNode *node, Edge::Kind kind) {
65 edges.insert({node, kind});
66}
67
68//===----------------------------------------------------------------------===//
69// CallGraph
70//===----------------------------------------------------------------------===//
71
72/// Recursively compute the callgraph edges for the given operation. Computed
73/// edges are placed into the given callgraph object.
75 SymbolTableCollection &symbolTable,
76 CallGraphNode *parentNode, bool resolveCalls) {
77 if (CallOpInterface call = dyn_cast<CallOpInterface>(op)) {
78 // If there is no parent node, we ignore this operation. Even if this
79 // operation was a call, there would be no callgraph node to attribute it
80 // to.
81 if (resolveCalls && parentNode)
82 parentNode->addCallEdge(cg.resolveCallable(call, symbolTable));
83 return;
84 }
85
86 // Compute the callgraph nodes and edges for each of the nested operations.
87 if (CallableOpInterface callable = dyn_cast<CallableOpInterface>(op)) {
88 if (auto *callableRegion = callable.getCallableRegion())
89 parentNode = cg.getOrAddNode(callableRegion, parentNode);
90 else
91 return;
92 }
93
94 for (Region &region : op->getRegions())
95 for (Operation &nested : region.getOps())
96 computeCallGraph(&nested, cg, symbolTable, parentNode, resolveCalls);
97}
98
100 : externalCallerNode(/*callableRegion=*/nullptr),
101 unknownCalleeNode(/*callableRegion=*/nullptr) {
102 // Make two passes over the graph, one to compute the callables and one to
103 // resolve the calls. We split these up as we may have nested callable objects
104 // that need to be reserved before the calls.
105 SymbolTableCollection symbolTable;
106 computeCallGraph(op, *this, symbolTable, /*parentNode=*/nullptr,
107 /*resolveCalls=*/false);
108 computeCallGraph(op, *this, symbolTable, /*parentNode=*/nullptr,
109 /*resolveCalls=*/true);
110}
111
112/// Get or add a call graph node for the given region.
114 CallGraphNode *parentNode) {
115 assert(region && isa<CallableOpInterface>(region->getParentOp()) &&
116 "expected parent operation to be callable");
117 std::unique_ptr<CallGraphNode> &node = nodes[region];
118 if (!node) {
119 node.reset(new CallGraphNode(region));
120
121 // Add this node to the given parent node if necessary.
122 if (parentNode) {
123 parentNode->addChildEdge(node.get());
124 } else {
125 // Otherwise, connect all callable nodes to the external node, this allows
126 // for conservatively including all callable nodes within the graph.
127 // FIXME This isn't correct, this is only necessary for callable nodes
128 // that *could* be called from external sources. This requires extending
129 // the interface for callables to check if they may be referenced
130 // externally.
131 externalCallerNode.addAbstractEdge(node.get());
132 }
133 }
134 return node.get();
135}
136
137/// Lookup a call graph node for the given region, or nullptr if none is
138/// registered.
140 const auto *it = nodes.find(region);
141 return it == nodes.end() ? nullptr : it->second.get();
142}
143
144/// Resolve the callable for given callee to a node in the callgraph, or the
145/// unknown callee node if a valid node was not resolved.
147CallGraph::resolveCallable(CallOpInterface call,
148 SymbolTableCollection &symbolTable) const {
149 Operation *callable = call.resolveCallableInTable(&symbolTable);
150 if (auto callableOp = dyn_cast_or_null<CallableOpInterface>(callable))
151 if (auto *node = lookupNode(callableOp.getCallableRegion()))
152 return node;
153
154 return getUnknownCalleeNode();
155}
156
157/// Erase the given node from the callgraph.
159 // Erase any children of this node first.
160 if (node->hasChildren()) {
161 for (const CallGraphNode::Edge &edge : llvm::make_early_inc_range(*node))
162 if (edge.isChild())
163 eraseNode(edge.getTarget());
164 }
165 // Erase any edges to this node from any other nodes.
166 for (auto &it : nodes) {
167 it.second->edges.remove_if([node](const CallGraphNode::Edge &edge) {
168 return edge.getTarget() == node;
169 });
170 }
171 nodes.erase(node->getCallableRegion());
172}
173
174//===----------------------------------------------------------------------===//
175// Printing
176//===----------------------------------------------------------------------===//
177
178/// Dump the graph in a human readable format.
179void CallGraph::dump() const { print(llvm::errs()); }
181 os << "// ---- CallGraph ----\n";
182
183 // Functor used to output the name for the given node.
184 auto emitNodeName = [&](const CallGraphNode *node) {
185 if (node == getExternalCallerNode()) {
186 os << "<External-Caller-Node>";
187 return;
188 }
189 if (node == getUnknownCalleeNode()) {
190 os << "<Unknown-Callee-Node>";
191 return;
192 }
193
194 auto *callableRegion = node->getCallableRegion();
195 auto *parentOp = callableRegion->getParentOp();
196 os << "'" << callableRegion->getParentOp()->getName() << "' - Region #"
197 << callableRegion->getRegionNumber();
198 NamedAttrList attrs(parentOp->getDiscardableAttrDictionary());
199 parentOp->getName().walkInherentAttrs(
200 parentOp,
201 [&](StringRef name, Attribute &attr) { attrs.append(name, attr); });
202 if (!attrs.empty()) {
203 os << " : { ";
204 llvm::interleaveComma(attrs, os, [&](NamedAttribute attr) {
205 os << attr.getName().getValue() << " = ";
206 attr.getValue().print(os);
207 });
208 os << " }";
209 }
210 };
211
212 for (auto &nodeIt : nodes) {
213 const CallGraphNode *node = nodeIt.second.get();
214
215 // Dump the header for this node.
216 os << "// - Node : ";
217 emitNodeName(node);
218 os << "\n";
219
220 // Emit each of the edges.
221 for (auto &edge : *node) {
222 os << "// -- ";
223 if (edge.isCall())
224 os << "Call";
225 else if (edge.isChild())
226 os << "Child";
227
228 os << "-Edge : ";
229 emitNodeName(edge.getTarget());
230 os << "\n";
231 }
232 os << "//\n";
233 }
234
235 os << "// -- SCCs --\n";
236
237 for (auto &scc : make_range(llvm::scc_begin(this), llvm::scc_end(this))) {
238 os << "// - SCC : \n";
239 for (auto &node : scc) {
240 os << "// -- Node :";
241 emitNodeName(node);
242 os << "\n";
243 }
244 os << "\n";
245 }
246
247 os << "// -------------------\n";
248}
static void computeCallGraph(Operation *op, CallGraph &cg, SymbolTableCollection &symbolTable, CallGraphNode *parentNode, bool resolveCalls)
Recursively compute the callgraph edges for the given operation.
Definition CallGraph.cpp:74
Attributes are known-constant values of operations.
Definition Attributes.h:25
void print(raw_ostream &os, bool elideType=false) const
Print the attribute.
This class represents a directed edge between two nodes in the callgraph.
Definition CallGraph.h:43
bool isChild() const
Returns true if this edge represents a Child edge.
Definition CallGraph.h:70
CallGraphNode * getTarget() const
Returns the target node for this edge.
Definition CallGraph.h:73
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
void addAbstractEdge(CallGraphNode *node)
Adds an abstract reference edge to the given node.
Definition CallGraph.cpp:43
void addChildEdge(CallGraphNode *child)
Adds a reference edge to the given child node.
Definition CallGraph.cpp:54
bool hasChildren() const
Returns true if this node has any child edges.
Definition CallGraph.cpp:59
void addCallEdge(CallGraphNode *node)
Add an outgoing call edge from this node.
Definition CallGraph.cpp:49
iterator end() const
Definition CallGraph.h:112
Region * getCallableRegion() const
Returns the callable region this node represents.
Definition CallGraph.cpp:36
CallGraphNode * getUnknownCalleeNode() const
Return the callgraph node representing an indirect callee.
Definition CallGraph.h:191
void eraseNode(CallGraphNode *node)
Erase the given node from the callgraph.
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 * getExternalCallerNode() const
Return the callgraph node representing an external caller.
Definition CallGraph.h:186
CallGraph(Operation *op)
Definition CallGraph.cpp:99
CallGraphNode * lookupNode(Region *region) const
Lookup a call graph node for the given region, or nullptr if none is registered.
void dump() const
Dump the graph in a human readable format.
CallGraphNode * getOrAddNode(Region *region, CallGraphNode *parentNode)
Get or add a call graph node for the given region.
void print(raw_ostream &os) const
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
void append(StringRef name, Attribute attr)
Add an attribute with the specified name.
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:729
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:213
This class represents a collection of SymbolTables.
Include the generated interface declarations.