P4C
The P4 Compiler
Loading...
Searching...
No Matches
callGraph.h
1/*
2 * SPDX-FileCopyrightText: 2013 Barefoot Networks, Inc.
3 * Copyright 2013-present Barefoot Networks, Inc.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8#ifndef FRONTENDS_P4_CALLGRAPH_H_
9#define FRONTENDS_P4_CALLGRAPH_H_
10
11#include <algorithm>
12#include <unordered_set>
13#include <vector>
14
15#include "ir/ir.h"
16#include "lib/exceptions.h"
17#include "lib/log.h"
18#include "lib/map.h" // IWYU pragma: keep
19#include "lib/null.h"
20#include "lib/ordered_map.h"
21#include "lib/ordered_set.h"
22
23namespace P4 {
24
25// I could not get Util::toString() to work properly
26cstring cgMakeString(cstring s);
27cstring cgMakeString(char c);
28cstring cgMakeString(const IR::Node *node);
29cstring cgMakeString(const IR::INode *node);
30
31template <class T>
32class CallGraph {
33 protected:
34 cstring name;
35 // Use an ordered map to make this deterministic
36 ordered_map<T, std::vector<T> *> out_edges; // map caller to list of callees
38
39 public:
40 ordered_set<T> nodes; // all nodes; do not modify this directly
41 using const_iterator = typename ordered_map<T, std::vector<T> *>::const_iterator;
42
43 explicit CallGraph(std::string_view name) : name(name) {}
44
46 [[nodiscard]] const cstring &getName() const { return name; }
47
49 [[nodiscard]] bool empty() const { return nodes.empty(); }
50
52 [[nodiscard]] size_t size() const { return nodes.size(); }
53
55 [[nodiscard]] const ordered_map<T, std::vector<T> *> &getOutEdges() const { return out_edges; }
56
58 [[nodiscard]] const ordered_map<T, std::vector<T> *> &getInEdges() const { return in_edges; }
59
61 [[nodiscard]] const ordered_set<T> &getNodes() const { return nodes; }
62
63 // Graph construction.
64
65 // node that may call no-one
66 void add(T caller) {
67 if (nodes.find(caller) != nodes.end()) return;
68 LOG1(name << ": " << cgMakeString(caller));
69 out_edges[caller] = new std::vector<T>();
70 in_edges[caller] = new std::vector<T>();
71 nodes.emplace(caller);
72 }
73 void calls(T caller, T callee) {
74 LOG1(name << ": " << cgMakeString(callee) << " is called by " << cgMakeString(caller));
75 add(caller);
76 add(callee);
77 out_edges[caller]->push_back(callee);
78 in_edges[callee]->push_back(caller);
79 }
80 void remove(T node) {
81 auto n = nodes.find(node);
82 BUG_CHECK(n != nodes.end(), "%1%: Node not in graph", node);
83 nodes.erase(n);
84 auto in = in_edges.find(node);
85 if (in != in_edges.end()) {
86 // remove all edges pointing to this node
87 for (auto n : *in->second) {
88 auto out = out_edges[n];
89 CHECK_NULL(out);
90 auto oe = std::find(out->begin(), out->end(), node);
91 out->erase(oe);
92 }
93 in_edges.erase(in);
94 }
95 auto it = out_edges.find(node);
96 if (it != out_edges.end()) {
97 // Remove all edges that point from this node
98 for (auto n : *it->second) {
99 auto in = in_edges[n];
100 CHECK_NULL(in);
101 auto ie = std::find(in->begin(), in->end(), node);
102 in->erase(ie);
103 }
104 out_edges.erase(it);
105 }
106 }
107
108 // Graph querying
109
110 bool isCallee(T callee) const {
111 auto callees = ::P4::get(in_edges, callee);
112 return callees != nullptr && !callees->empty();
113 }
114 bool isCaller(T caller) const {
115 auto edges = ::P4::get(out_edges, caller);
116 if (edges == nullptr) return false;
117 return !edges->empty();
118 }
119 // Iterators over the out_edges
120 const_iterator begin() const { return out_edges.cbegin(); }
121 const_iterator end() const { return out_edges.cend(); }
122 std::vector<T> *getCallees(T caller) { return out_edges[caller]; }
123 std::vector<T> *getCallers(T callee) { return in_edges[callee]; }
124 // Callees are appended to 'toAppend'
125 void getCallees(T caller, std::set<T> &toAppend) {
126 if (isCaller(caller)) toAppend.insert(out_edges[caller]->begin(), out_edges[caller]->end());
127 }
128 // out will contain all nodes reachable from start
129 void reachable(T start, std::set<T> &out) const {
130 std::set<T> work;
131 work.emplace(start);
132 while (!work.empty()) {
133 T node = *work.begin();
134 work.erase(node);
135 if (out.find(node) != out.end()) continue;
136 out.emplace(node);
137 auto edges = out_edges.find(node);
138 if (edges == out_edges.end()) continue;
139 for (auto c : *(edges->second)) work.emplace(c);
140 }
141 }
142 // remove all nodes not in 'to'
143 void restrict(const std::set<T> &to) {
144 std::vector<T> toRemove;
145 for (auto n : nodes)
146 if (to.find(n) == to.end()) toRemove.push_back(n);
147 for (auto n : toRemove) remove(n);
148 }
149
150 using Set = std::unordered_set<T>;
151
152 // Compute for each node the set of dominators with the indicated start node.
153 // Node d dominates node n if all paths from the start to n go through d
154 // Result is deposited in 'dominators'.
155 // 'dominators' should be empty when calling this function.
156 void dominators(T start, std::map<T, Set> &dominators) {
157 // initialize
158 for (auto n : nodes) {
159 if (n == start)
160 dominators[n].emplace(start);
161 else
162 dominators[n].insert(nodes.begin(), nodes.end());
163 }
164
165 // There are faster but more complicated algorithms.
166 bool changes = true;
167 while (changes) {
168 changes = false;
169 for (auto node : nodes) {
170 auto vec = in_edges[node];
171 if (vec == nullptr) continue;
172 auto size = dominators[node].size();
173 for (auto c : *vec) insersectWith(dominators[node], dominators[c]);
174 dominators[node].emplace(node);
175 if (dominators[node].size() != size) changes = true;
176 }
177 }
178 }
179
180 class Loop {
181 public:
182 T entry;
183 std::set<T> body;
184 // multiple back-edges could go to the same loop head
185 std::set<T> back_edge_heads;
186
187 explicit Loop(T entry) : entry(entry) {}
188 };
189
190 // All natural loops in a call-graph.
191 struct Loops {
192 std::vector<Loop *> loops;
193
194 // Return loop index if 'node' is a loop entry point
195 // else return -1
196 int isLoopEntryPoint(T node) const {
197 for (size_t i = 0; i < loops.size(); i++) {
198 auto loop = loops.at(i);
199 if (loop->entry == node) return i;
200 }
201 return -1;
202 }
203 bool isInLoop(int loopIndex, T node) const {
204 if (loopIndex == -1) return false;
205 auto loop = loops.at(loopIndex);
206 return (loop->body.find(node) != loop->body.end());
207 }
208 };
209
210 Loops *compute_loops(T start) {
211 auto result = new Loops();
212 std::map<T, Set> dom;
213 dominators(start, dom);
214
215 std::map<T, Loop *> entryToLoop;
216
217 for (auto e : nodes) {
218 auto next = out_edges[e];
219 auto dome = dom[e];
220 for (auto n : *next) {
221 if (dome.find(n) != dome.end()) {
222 // n is a loop head
223 auto loop = get(entryToLoop, n);
224 if (loop == nullptr) {
225 loop = new Loop(n);
226 entryToLoop[n] = loop;
227 result->loops.push_back(loop);
228 }
229 loop->back_edge_heads.emplace(e);
230 // reverse DFS from e to n
231
232 std::set<T> work;
233 work.emplace(e);
234 while (!work.empty()) {
235 auto crt = *work.begin();
236 work.erase(crt);
237 loop->body.emplace(crt);
238 if (crt == n) continue;
239 for (auto i : *in_edges[crt])
240 if (loop->body.find(i) == loop->body.end()) work.emplace(i);
241 }
242 }
243 }
244 }
245 return result;
246 }
247
248 protected:
249 // intersect in place
250 static void insersectWith(Set &set, Set &with) {
251 std::vector<T> toRemove;
252 for (auto e : set)
253 if (with.find(e) == with.end()) toRemove.push_back(e);
254 for (auto e : toRemove) set.erase(e);
255 }
256
257 // Helper for computing strongly-connected components
258 // using Tarjan's algorithm.
259 struct sccInfo {
260 unsigned crtIndex;
261 std::vector<T> stack;
262 std::set<T> onStack;
263 std::map<T, unsigned> index;
264 std::map<T, unsigned> lowlink;
265
266 sccInfo() : crtIndex(0) {}
267 void push(T node) {
268 stack.push_back(node);
269 onStack.emplace(node);
270 }
271 bool isOnStack(T node) { return onStack.count(node) != 0; }
272 bool unknown(T node) { return index.count(node) == 0; }
273 void setLowLink(T node, unsigned value) {
274 lowlink[node] = value;
275 LOG1(node << ".lowlink = " << value << " = " << get(lowlink, node));
276 }
277 void setLowLink(T node, T successor) {
278 unsigned nlink = get(lowlink, node);
279 unsigned slink = get(lowlink, successor);
280 if (slink < nlink) setLowLink(node, slink);
281 }
282 T pop() {
283 T result = stack.back();
284 stack.pop_back();
285 onStack.erase(result);
286 return result;
287 }
288 };
289
290 // helper for scSort
291 bool strongConnect(T node, sccInfo &helper, std::vector<T> &out) {
292 bool loop = false;
293
294 LOG1("scc " << cgMakeString(node));
295 helper.index.emplace(node, helper.crtIndex);
296 helper.setLowLink(node, helper.crtIndex);
297 helper.crtIndex++;
298 helper.push(node);
299 auto oe = out_edges[node];
300 if (oe != nullptr) {
301 for (auto next : *oe) {
302 LOG1(cgMakeString(node) << " => " << cgMakeString(next));
303 if (helper.unknown(next)) {
304 bool l = strongConnect(next, helper, out);
305 loop = loop | l;
306 helper.setLowLink(node, next);
307 } else if (helper.isOnStack(next)) {
308 helper.setLowLink(node, next);
309 if (next == node)
310 // the check below does not find self-loops
311 loop = true;
312 }
313 }
314 }
315
316 if (get(helper.lowlink, node) == get(helper.index, node)) {
317 LOG1(cgMakeString(node) << " index=" << get(helper.index, node)
318 << " lowlink=" << get(helper.lowlink, node));
319 while (true) {
320 T sccMember = helper.pop();
321 LOG1("Scc order " << cgMakeString(sccMember) << "[" << cgMakeString(node) << "]");
322 out.push_back(sccMember);
323 if (sccMember == node) break;
324 loop = true;
325 }
326 }
327
328 return loop;
329 }
330
331 public:
332 // Sort that computes strongly-connected components - all nodes in
333 // a strongly-connected components will be consecutive in the
334 // sort. Returns true if the graph contains at least one
335 // cycle. Ignores nodes not reachable from 'start'.
336 bool sccSort(T start, std::vector<T> &out) {
337 sccInfo helper;
338 return strongConnect(start, helper, out);
339 }
340 bool sort(std::vector<T> &start, std::vector<T> &out) {
341 sccInfo helper;
342 bool cycles = false;
343 for (auto n : start) {
344 if (std::find(out.begin(), out.end(), n) == out.end()) {
345 bool c = strongConnect(n, helper, out);
346 cycles = cycles || c;
347 }
348 }
349 return cycles;
350 }
351 bool sort(std::vector<T> &out) {
352 sccInfo helper;
353 bool cycles = false;
354 for (auto n : nodes) {
355 if (std::find(out.begin(), out.end(), n) == out.end()) {
356 bool c = strongConnect(n, helper, out);
357 cycles = cycles || c;
358 }
359 }
360 return cycles;
361 }
362};
363
364} // namespace P4
365
366#endif /* FRONTENDS_P4_CALLGRAPH_H_ */
Definition callGraph.h:180
const ordered_set< T > & getNodes() const
Return the set of nodes.
Definition callGraph.h:61
size_t size() const
Return the number of nodes.
Definition callGraph.h:52
const ordered_map< T, std::vector< T > * > & getOutEdges() const
Return the number of outgoing edges.
Definition callGraph.h:55
bool empty() const
Return true if the graph is empty.
Definition callGraph.h:49
const cstring & getName() const
Get the name of the graph.
Definition callGraph.h:46
const ordered_map< T, std::vector< T > * > & getInEdges() const
Return the number of incoming edges.
Definition callGraph.h:58
Definition inode.h:42
Definition node.h:44
Definition cstring.h:76
Definition ordered_map.h:23
Definition ordered_set.h:23
TODO: this is not really specific to BMV2, it should reside somewhere else.
Definition applyOptionsPragmas.cpp:13
Definition callGraph.h:191
Definition callGraph.h:259