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