P4C
The P4 Compiler
Loading...
Searching...
No Matches
table_flow_graph.h
1
19#ifndef BF_P4C_MAU_TABLE_FLOW_GRAPH_H_
20#define BF_P4C_MAU_TABLE_FLOW_GRAPH_H_
21
22#include <map>
23#include <optional>
24#include <set>
25
26#include <boost/graph/adjacency_list.hpp>
27#include <boost/graph/transitive_closure.hpp>
28
29#include "bf-p4c/ir/control_flow_visitor.h"
30#include "bf-p4c/lib/boost_graph.h"
31#include "bf-p4c/mau/mau_visitor.h"
32
33namespace boost {
34enum vertex_table_t { vertex_table };
35BOOST_INSTALL_PROPERTY(vertex, table);
36
37enum edge_annotation_t { edge_annotation };
38BOOST_INSTALL_PROPERTY(edge, annotation);
39} // namespace boost
40
41using namespace P4;
42
45struct FlowGraph {
46 typedef boost::adjacency_list<boost::vecS, boost::vecS,
47 boost::bidirectionalS, // Directed edges.
48 // Label vertices with tables.
49 boost::property<boost::vertex_table_t, const IR::MAU::Table *>,
50 // Label edges with control annotations.
51 boost::property<boost::edge_annotation_t, cstring>>
52 Graph;
53
55 class BFSPathFinder : public boost::default_bfs_visitor {
58 std::map<typename Graph::vertex_descriptor, typename Graph::vertex_descriptor> *parent =
59 nullptr;
60
62 struct done {};
63
65 const Graph &graph;
66
68 typename Graph::vertex_descriptor dst;
69
70 public:
71 void examine_edge(typename Graph::edge_descriptor e, const Graph &g) {
72 auto src = boost::source(e, g);
73 auto dst = boost::target(e, g);
74
75 if (!parent->count(dst)) (*parent)[dst] = src;
76 if (dst == this->dst) throw done{};
77 }
78
79 explicit BFSPathFinder(const Graph &g) : graph(g) {}
80
81 std::vector<typename Graph::vertex_descriptor> find_path(
82 typename Graph::vertex_descriptor src, typename Graph::vertex_descriptor dst) {
83 std::map<typename Graph::vertex_descriptor, typename Graph::vertex_descriptor> parent;
84 this->parent = &parent;
85
86 this->dst = dst;
87 try {
88 boost::breadth_first_search(graph, src, boost::visitor(*this));
89 } catch (done const &) {
90 }
91
92 std::vector<typename Graph::vertex_descriptor> result;
93 if (parent.count(dst)) {
94 // Search succeeded. Build the path in reverse.
95 auto cur = dst;
96 result.emplace_back(cur);
97 do {
98 cur = parent.at(cur);
99 result.emplace_back(cur);
100 } while (cur != src);
101
102 // Reverse the result so we get a forward path.
103 for (unsigned i = 0; i < result.size() / 2; ++i) {
104 std::swap(result[i], result[result.size() - i - 1]);
105 }
106 }
107
108 // Clean up for the next call.
109 this->parent = nullptr;
110
111 return result;
112 }
113 };
114
116 Graph g;
117
119 typename Graph::vertex_descriptor v_source;
120
122 typename Graph::vertex_descriptor v_sink;
123
124 std::optional<gress_t> gress;
125
126 mutable Reachability<Graph> reachability;
127
130
132 std::set<const IR::MAU::Table *> tables;
133
135 mutable std::optional<std::map<const IR::MAU::Table *, std::set<const IR::MAU::Table *>>>
137
138 // By default, emptyFlowGraph is set to true to indicate that there are no vertices in the
139 // graph. Only when the first actual table is added to the flow graph is this member set to
140 // false.
141 bool emptyFlowGraph = true;
142
144 const cstring get_ctrl_dependency_info(typename Graph::edge_descriptor edge) const {
145 return boost::get(boost::edge_annotation, g)[edge];
146 }
147
148 FlowGraph(void) : reachability(g), path_finder(g) {
149 gress = std::nullopt;
150 dominators = std::nullopt;
151 }
152
153 FlowGraph(FlowGraph &&other)
154 : g(std::move(other.g)),
155 gress(std::move(other.gress)),
156 reachability(g),
158 tables(std::move(other.tables)),
159 dominators(std::move(other.dominators)),
160 emptyFlowGraph(std::move(other.emptyFlowGraph)),
161 tableToVertex(std::move(other.tableToVertex)),
162 path_finder(g) {}
163
164 FlowGraph(const FlowGraph &other)
165 : g(other.g),
166 gress(other.gress),
167 reachability(g),
169 tables(other.tables),
170 dominators(other.dominators),
171 emptyFlowGraph(other.emptyFlowGraph),
173 path_finder(g) {}
174
176 std::map<const IR::MAU::Table *, typename Graph::vertex_descriptor> tableToVertex;
177
179 std::map<typename Graph::vertex_descriptor, const IR::MAU::Table *> vertexToTable;
180
182 bool is_empty() const {
183 auto all_vertices = boost::vertices(g);
184 if (++all_vertices.first == all_vertices.second) {
185 return true;
186 }
187 return false;
188 }
189
191 void clear() {
192 g.clear();
193 gress = std::nullopt;
194 reachability.clear();
195 tableToVertexIndex.clear();
196 tableToVertex.clear();
197 vertexToTable.clear();
198 tables.clear();
199 emptyFlowGraph = true;
200 }
201
202 void add_sink_vertex() {
203 v_sink = add_vertex(nullptr);
204 reachability.setSink(v_sink);
205 }
206
211 bool can_reach(const IR::MAU::Table *t1, const IR::MAU::Table *t2) const {
212 if (t2 == nullptr) return true;
213 if (t1 == nullptr) return false;
214 const auto v1 = get_vertex(t1);
215 const auto v2 = get_vertex(t2);
216 return reachability.canReach(v1, v2);
217 }
218
222 const std::set<const IR::MAU::Table *> get_dominators(const IR::MAU::Table *table) const;
223
225 bool is_always_reached(const IR::MAU::Table *) const;
226
229
233 std::vector<const IR::MAU::Table *> find_path(const IR::MAU::Table *src,
234 const IR::MAU::Table *dst) {
235 auto path = path_finder.find_path(get_vertex(src), get_vertex(dst));
236 std::vector<const IR::MAU::Table *> result;
237 for (auto node : path) {
238 result.emplace_back(get_vertex(node));
239 }
240
241 return result;
242 }
243
245 const IR::MAU::Table *get_vertex(typename Graph::vertex_descriptor v) const {
246 return boost::get(boost::vertex_table, g)[v];
247 }
248
250 typename Graph::vertex_descriptor get_vertex(const IR::MAU::Table *tbl) const {
251 BUG_CHECK(tableToVertexIndex.count(tbl), "Table object not found for %1%",
252 tbl->externalName());
253 return tableToVertexIndex.at(tbl);
254 }
255
257 const std::set<const IR::MAU::Table *> get_tables() const { return tables; }
258
261 typename Graph::vertex_descriptor add_vertex(const IR::MAU::Table *table) {
262 // Initialize gress if needed.
263 if (table != nullptr && gress == std::nullopt) gress = table->gress;
264
265 if (tableToVertex.count(table)) {
266 return tableToVertex.at(table);
267 }
268
269 // Create new vertex.
270 auto v = boost::add_vertex(table, g);
271 tableToVertex[table] = v;
272 vertexToTable[v] = table;
273 if (table) tables.insert(table);
274
275 // If the vertex being added corresponds to a real table (not the sink), then the flow
276 // graph is no longer empty; set the emptyFlowGraph member accordingly.
277 if (table != nullptr) emptyFlowGraph = false;
278
279 return v;
280 }
281
284 std::pair<typename Graph::edge_descriptor, bool> add_edge(const IR::MAU::Table *src,
285 const IR::MAU::Table *dst,
286 const cstring edge_label) {
287 typename Graph::vertex_descriptor src_v, dst_v;
288 src_v = add_vertex(src);
289 dst_v = add_vertex(dst);
290
291 // Look for a pre-existing edge.
292 typename Graph::out_edge_iterator out, end;
293 for (boost::tie(out, end) = boost::out_edges(src_v, g); out != end; ++out) {
294 if (boost::target(*out, g) == dst_v) return {*out, false};
295 }
296
297 // No pre-existing edge, so make one.
298 auto maybe_new_e = boost::add_edge(src_v, dst_v, g);
299 if (!maybe_new_e.second)
300 // A vector-based adjacency_list (i.e. Graph) is a multigraph.
301 // Inserting edges should always create new edges.
302 BUG("Boost Graph Library failed to add edge.");
303 boost::get(boost::edge_annotation, g)[maybe_new_e.first] = edge_label;
304 return {maybe_new_e.first, true};
305 }
306
307 std::vector<const IR::MAU::Table *> topological_sort() const {
308 std::vector<Graph::vertex_descriptor> internal_result;
309 boost::topological_sort(g, std::back_inserter(internal_result));
310
311 // Boost produces a reverse order with internal vertices. Reverse the list while converting
312 // to tables.
313 std::vector<const IR::MAU::Table *> result;
314 for (auto it = internal_result.rbegin(); it != internal_result.rend(); ++it) {
315 result.push_back(get_vertex(*it));
316 }
317
318 return result;
319 }
320
321 friend std::ostream &operator<<(std::ostream &, const FlowGraph &);
322
324 virtual void dump(std::ostream &, const IR::MAU::Table *) const {}
325 };
326
327 static std::string viz_node_name(const IR::MAU::Table *tbl);
328 void dump_viz(std::ostream &out, const DumpTableDetails *details = nullptr);
329};
330
332//
333// FIXME(Jed): This currently only works when gateway conditions are represented as separate table
334// objects. After table placement, gateways and match tables are fused into single table objects.
335// This pass should be fixed at some point to support this fused representation.
336//
337// Here are some thoughts on how to this. We can leverage the call structure in
338// IR::MAU::Table::visit_children; specifically, the calls to flow_clone, visit(Node, label),
339// flow_merge_global_to, and flow_merge. This should give us enough information to track the set of
340// tables that could have been the last one to execute before reaching the node being visited. With
341// this, we should be able to build the flow graph. Effectively, we would piggyback on the existing
342// infrastructure in visit_children for supporting ControlFlowVisitor. We don't actually want to
343// write a ControlFlowVisitor here, however, since in its current form, ControlFlowVisitor will
344// deadlock when there are cycles in the IR; FindFlowGraph is used in part to check that the IR is
345// cycle-free.
347 private:
349 FlowGraph &fg;
350
363 const IR::MAU::Table *next_table;
364
367 std::pair<bool, cstring> next_incomplete(const IR::MAU::Table *t);
368
369 Visitor::profile_t init_apply(const IR::Node *node) override;
370 bool preorder(const IR::MAU::TableSeq *) override;
371 bool preorder(const IR::MAU::Table *) override;
372 bool preorder(const IR::MAU::Action *) override;
373 void end_apply() override;
374
375 public:
376 explicit FindFlowGraph(FlowGraph &out) : fg(out), next_table(nullptr) {
377 // We want to re-visit table nodes here, since the table calls can have different
378 // next_tables in different contexts.
379 visitDagOnce = false;
380 }
381};
382
385 private:
388
389 Visitor::profile_t init_apply(const IR::Node *root) override;
390 bool preorder(const IR::MAU::TableSeq *) override;
391 bool preorder(const IR::BFN::Deparser *) override;
392
393 public:
394 explicit FindFlowGraphs(ordered_map<gress_t, FlowGraph> &out) : flow_graphs(out) {}
395};
396
397#endif /* BF_P4C_MAU_TABLE_FLOW_GRAPH_H_ */
Computes a table control-flow graph for the IR.
Definition table_flow_graph.h:346
Computes a table control-flow graph for each gress in the IR.
Definition table_flow_graph.h:384
Custom BFS visitor for finding a shortest path between two nodes.
Definition table_flow_graph.h:55
Definition mau_visitor.h:29
Definition node.h:95
Definition visitor.h:78
Definition cstring.h:85
Definition ordered_map.h:32
Definition boost_graph.h:39
void setSink(std::optional< Vertex > sink)
Sets the sink node; no nodes will be considered reachable from this node.
Definition boost_graph.h:68
void clear()
Definition boost_graph.h:62
bool canReach(Vertex v1, Vertex v2)
Determines whether vertex v2 is reachable from the vertex v1.
Definition boost_graph.h:47
TODO: this is not really specific to BMV2, it should reside somewhere else.
Definition applyOptionsPragmas.cpp:24
Definition table_flow_graph.h:33
STL namespace.
Definition table_flow_graph.h:323
Definition table_flow_graph.h:45
const std::set< const IR::MAU::Table * > get_dominators(const IR::MAU::Table *table) const
Definition mau/table_flow_graph.cpp:84
Graph::vertex_descriptor v_source
The source node, representing the entry point (i.e., entry from the parser).
Definition table_flow_graph.h:119
bool is_empty() const
Determines whether this graph is empty.
Definition table_flow_graph.h:182
Graph g
The underlying Boost graph backing this FlowGraph.
Definition table_flow_graph.h:116
bool can_reach(const IR::MAU::Table *t1, const IR::MAU::Table *t2) const
Definition table_flow_graph.h:211
const std::set< const IR::MAU::Table * > get_tables() const
Definition table_flow_graph.h:257
Graph::vertex_descriptor get_vertex(const IR::MAU::Table *tbl) const
Definition table_flow_graph.h:250
BFSPathFinder path_finder
Helper for find_path.
Definition table_flow_graph.h:228
void clear()
Clears the state in this FlowGraph.
Definition table_flow_graph.h:191
friend std::ostream & operator<<(std::ostream &, const FlowGraph &)
Definition mau/table_flow_graph.cpp:23
std::map< typename Graph::vertex_descriptor, const IR::MAU::Table * > vertexToTable
Reverse map of above.
Definition table_flow_graph.h:179
ordered_map< const IR::MAU::Table *, int > tableToVertexIndex
Maps each table to its corresponding vertex ID in the Boost graph.
Definition table_flow_graph.h:129
std::optional< std::map< const IR::MAU::Table *, std::set< const IR::MAU::Table * > > > dominators
The dominator set for each table in the graph. Lazily computed.
Definition table_flow_graph.h:136
Graph::vertex_descriptor v_sink
The sink node, representing the exit point (i.e., entry to the deparser).
Definition table_flow_graph.h:122
std::pair< typename Graph::edge_descriptor, bool > add_edge(const IR::MAU::Table *src, const IR::MAU::Table *dst, const cstring edge_label)
Definition table_flow_graph.h:284
std::vector< const IR::MAU::Table * > find_path(const IR::MAU::Table *src, const IR::MAU::Table *dst)
Definition table_flow_graph.h:233
const cstring get_ctrl_dependency_info(typename Graph::edge_descriptor edge) const
Definition table_flow_graph.h:144
const IR::MAU::Table * get_vertex(typename Graph::vertex_descriptor v) const
Definition table_flow_graph.h:245
std::map< const IR::MAU::Table *, typename Graph::vertex_descriptor > tableToVertex
Maps each table to its associated graph vertex.
Definition table_flow_graph.h:176
bool is_always_reached(const IR::MAU::Table *) const
Determines whether the given table is executed on all paths.
Definition mau/table_flow_graph.cpp:163
Graph::vertex_descriptor add_vertex(const IR::MAU::Table *table)
Definition table_flow_graph.h:261
std::set< const IR::MAU::Table * > tables
All tables in this graph, excluding the nullptr table, used to represent the sink node.
Definition table_flow_graph.h:132