diff --git a/BUILD.bazel b/BUILD.bazel index d107e682e65..50875455f7b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -416,12 +416,14 @@ cc_library( srcs = [ "backends/graphs/controls.cpp", "backends/graphs/graphs.cpp", + "backends/graphs/graph_visitor.cpp", "backends/graphs/p4c-graphs.cpp", "backends/graphs/parsers.cpp", ], hdrs = [ "backends/graphs/controls.h", "backends/graphs/graphs.h", + "backends/graphs/graph_visitor.h", "backends/graphs/parsers.h", "backends/graphs/version.h", ], diff --git a/backends/graphs/CMakeLists.txt b/backends/graphs/CMakeLists.txt index dfe7ad4675f..b6143e35d50 100644 --- a/backends/graphs/CMakeLists.txt +++ b/backends/graphs/CMakeLists.txt @@ -19,6 +19,7 @@ set (GRAPHS_SRCS graphs.cpp controls.cpp parsers.cpp + graph_visitor.cpp p4c-graphs.cpp ) @@ -26,6 +27,7 @@ set (GRAPHS_HDRS graphs.h controls.h parsers.h + graph_visitor.h ) add_cpplint_files(${CMAKE_CURRENT_SOURCE_DIR} "${GRAPHS_SRCS};${GRAPHS_HDRS}") diff --git a/backends/graphs/README.md b/backends/graphs/README.md index be2f9ddab11..4c41055d7a1 100644 --- a/backends/graphs/README.md +++ b/backends/graphs/README.md @@ -1,7 +1,9 @@ # Graphs Backend This backend produces visual representations of a P4 program as dot files. For -now it only supports the generation of graphs for top-level control and parser blocks. +now it supports the generation of graphs for top-level control and parser blocks, +generation of fullGraph, which merges graphs for top-level program blocks and +json representation of fullGraph. dot files can be processed by the `dot` command from [Graphviz](http://graphviz.org), which can lay out the nodes and @@ -23,6 +25,43 @@ cd out dot .dot -Tpng > .png ``` +For generation of dot fullGraph, use option `--fullGraph` and +for generation of fullGraph represented in json, use option `--jsonOut`. + +## Format of json output + +Output in json format is an object with fields: + +- `info` (object) + + - `name` - Filename of P4 program. + +- `nodes` (array) - Contains objects, representing program blocks. + + - `type` - Type of program block. Valid values are "parser" and "control". + + - `name` - Name of program block. + + - `nodes` (array) - Contains object, representing nodes of CFG. + + - `node_nmb` - Index of given object in array nodes. + + - `name` - Node label. + + - `type` - Type of node. + + - `type_enum` - Enum value, representing type of node in backend *p4c-graphs*. + + - `transitions` (array) - Contains objects, representing transitions between nodes in `nodes`. + + - `from` - Index to node in `nodes`, from which edge starts. + + - `to` - Index to node in `nodes`, in which edge ends. + + - `cond` - Edge label. + +Objects which represent program blocks are ordered in `nodes`, in the order in which they are defined in the main declaration of P4 program. + ## Example Here is the graph generated for the ingress control block of the @@ -31,13 +70,18 @@ test program: ![Flowlet switching ingress graph](resources/flowlet_switching-bmv2.ingress.png) +Here is the graph generated for the egress control block of the +[flowlet_switching-bmv2.p4](../../testdata/p4_16_samples/flowlet_switching-bmv2.p4) +test program: + +![Flowlet switching egress graph](resources/flowlet_switching-bmv2.egress.png) + Here is the graph generated for the parser block of the [flowlet_switching-bmv2.p4](../../testdata/p4_16_samples/flowlet_switching-bmv2.p4) test program: ![Flowlet switching ingress graph](resources/flowlet_switching-bmv2.parser.png) -For a more complex example, you can look at the control graph generated for the -egress control of -[switch.p4](https://github.com/p4lang/switch/tree/f219b4f4e25c2db581f3b91c8da94a7c3ac701a7/p4src) -[here](http://bmv2.org/switch_egress.png). +Examples of generated fullGraph for [flowlet_switching-bmv2.p4](../../testdata/p4_16_samples/flowlet_switching-bmv2.p4) program +and [firewall.p4](https://github.com/p4lang/tutorials/blob/master/exercises/firewall/solution/firewall.p4) program +can be found in [resources](resources/), also with json fullGraph. \ No newline at end of file diff --git a/backends/graphs/controls.cpp b/backends/graphs/controls.cpp index a2918cbef47..975fc97c5b6 100644 --- a/backends/graphs/controls.cpp +++ b/backends/graphs/controls.cpp @@ -24,7 +24,6 @@ limitations under the License. #include "frontends/p4/tableApply.h" #include "lib/log.h" #include "lib/nullstream.h" -#include "lib/path.h" #include "controls.h" namespace graphs { @@ -33,11 +32,12 @@ using Graph = ControlGraphs::Graph; Graph *ControlGraphs::ControlStack::pushBack(Graph ¤tSubgraph, const cstring &name) { auto &newSubgraph = currentSubgraph.create_subgraph(); - boost::get_property(newSubgraph, boost::graph_name) = "cluster" + getName(name); - boost::get_property(newSubgraph, boost::graph_graph_attribute)["label"] = getName(name); - // Justify the subgraph label to the right as it usually makes the generated - // graph more readable than the default (center). - boost::get_property(newSubgraph, boost::graph_graph_attribute)["labeljust"] = "r"; + auto fullName = getName(name); + boost::get_property(newSubgraph, boost::graph_name) = "cluster" + fullName; + boost::get_property(newSubgraph, boost::graph_graph_attribute)["label"] = + boost::get_property(currentSubgraph, boost::graph_name) + + (fullName != "" ? "." + fullName : fullName); + boost::get_property(newSubgraph, boost::graph_graph_attribute)["fontsize"] = "22pt"; boost::get_property(newSubgraph, boost::graph_graph_attribute)["style"] = "bold"; names.push_back(name); subgraphs.push_back(&newSubgraph); @@ -75,40 +75,30 @@ ControlGraphs::ControlGraphs(P4::ReferenceMap *refMap, P4::TypeMap *typeMap, visitDagOnce = false; } -void ControlGraphs::writeGraphToFile(const Graph &g, const cstring &name) { - auto path = Util::PathName(graphsDir).join(name + ".dot"); - auto out = openFile(path.toString(), false); - if (out == nullptr) { - ::error(ErrorType::ERR_IO, "Failed to open file %1%", path.toString()); - return; - } - // custom label writers not supported with subgraphs, so we populate - // *_attribute_t properties instead using our GraphAttributeSetter class. - boost::write_graphviz(*out, g); -} - bool ControlGraphs::preorder(const IR::PackageBlock *block) { for (auto it : block->constantValue) { if (!it.second) continue; if (it.second->is()) { auto name = it.second->to()->container->name; LOG1("Generating graph for top-level control " << name); - Graph g_; - g = &g_; - BUG_CHECK(controlStack.isEmpty(), "Invalid control stack state"); - g = controlStack.pushBack(g_, ""); + + Graph *g_ = new Graph(); + g = g_; instanceName = boost::none; - boost::get_property(g_, boost::graph_name) = name; + boost::get_property(*g_, boost::graph_name) = name; + BUG_CHECK(controlStack.isEmpty(), "Invalid control stack state"); + g = controlStack.pushBack(*g_, ""); start_v = add_vertex("__START__", VertexType::OTHER); exit_v = add_vertex("__EXIT__", VertexType::OTHER); parents = {{start_v, new EdgeUnconditional()}}; visit(it.second->getNode()); - for (auto parent : parents) + + for (auto parent : parents) { add_edge(parent.first, exit_v, parent.second->label()); - BUG_CHECK(g_.is_root(), "Invalid graph"); + } + BUG_CHECK((*g_).is_root(), "Invalid graph"); controlStack.popBack(); - GraphAttributeSetter()(g_); - writeGraphToFile(g_, name); + controlGraphsArray.push_back(g_); } else if (it.second->is()) { visit(it.second->getNode()); } @@ -131,6 +121,7 @@ bool ControlGraphs::preorder(const IR::P4Control *cont) { return_parents.clear(); visit(cont->body); merge_other_statements_into_vertex(); + parents.insert(parents.end(), return_parents.begin(), return_parents.end()); return_parents.clear(); if (doPop) g = controlStack.popBack(); @@ -141,6 +132,7 @@ bool ControlGraphs::preorder(const IR::BlockStatement *statement) { for (const auto component : statement->components) visit(component); merge_other_statements_into_vertex(); + return false; } @@ -148,10 +140,12 @@ bool ControlGraphs::preorder(const IR::IfStatement *statement) { std::stringstream sstream; statement->condition->dbprint(sstream); auto v = add_and_connect_vertex(cstring(sstream), VertexType::CONDITION); + Parents new_parents; parents = {{v, new EdgeIf(EdgeIf::Branch::TRUE)}}; visit(statement->ifTrue); merge_other_statements_into_vertex(); + new_parents.insert(new_parents.end(), parents.begin(), parents.end()); parents = {{v, new EdgeIf(EdgeIf::Branch::FALSE)}}; if (statement->ifFalse != nullptr) { @@ -207,6 +201,7 @@ bool ControlGraphs::preorder(const IR::SwitchStatement *statement) { bool ControlGraphs::preorder(const IR::MethodCallStatement *statement) { auto instance = P4::MethodInstance::resolve(statement->methodCall, refMap, typeMap); + if (instance->is()) { auto am = instance->to(); if (am->object->is()) { @@ -244,6 +239,7 @@ bool ControlGraphs::preorder(const IR::AssignmentStatement *statement) { bool ControlGraphs::preorder(const IR::ReturnStatement *) { merge_other_statements_into_vertex(); + return_parents.insert(return_parents.end(), parents.begin(), parents.end()); parents.clear(); return false; @@ -251,16 +247,79 @@ bool ControlGraphs::preorder(const IR::ReturnStatement *) { bool ControlGraphs::preorder(const IR::ExitStatement *) { merge_other_statements_into_vertex(); + for (auto parent : parents) add_edge(parent.first, exit_v, parent.second->label()); parents.clear(); return false; } +bool ControlGraphs::preorder(const IR::Key *key) { + std::stringstream sstream; + + // Build key + for (auto elVec : key->keyElements) { + sstream << elVec->matchType->path->name.name << ": "; + sstream << elVec->annotations->annotations.front()->expr.front() << "\\n"; + } + + auto v = add_and_connect_vertex(cstring(sstream), VertexType::KEY); + + parents = {{v, new EdgeUnconditional()}}; + + return false; +} + +bool ControlGraphs::preorder(const IR::P4Action *action) { + visit(action->body); + return false; +} + bool ControlGraphs::preorder(const IR::P4Table *table) { - auto name = controlStack.getName(table->controlPlaneName()); + auto name = table->getName(); + auto v = add_and_connect_vertex(name, VertexType::TABLE); + parents = {{v, new EdgeUnconditional()}}; + + auto key = table->getKey(); + visit(key); + + Parents keyNode; + keyNode.emplace_back(parents.back()); + + Parents new_parents; + + auto actions = table->getActionList()->actionList; + for (auto action : actions){ + parents = keyNode; + + auto v = add_and_connect_vertex(action->getName(), VertexType::ACTION); + + parents = {{v, new EdgeUnconditional()}}; + + if (action->expression->is()) { + auto mce = action->expression->to(); + + // needed for visiting body of P4Action + auto resolved = P4::MethodInstance::resolve(mce, refMap, typeMap); + + if (resolved->is()) { + auto ac = resolved->to(); + if (ac->action->is()) { + visit(ac->action->to()); + } + } + } + + merge_other_statements_into_vertex(); + + new_parents.insert(new_parents.end(), parents.begin(), parents.end()); + parents.clear(); + } + + parents = new_parents; + return false; } diff --git a/backends/graphs/controls.h b/backends/graphs/controls.h index 1953c1b514c..e4a374ef840 100644 --- a/backends/graphs/controls.h +++ b/backends/graphs/controls.h @@ -48,8 +48,10 @@ class ControlGraphs : public Graphs { bool preorder(const IR::ReturnStatement *) override; bool preorder(const IR::ExitStatement *) override; bool preorder(const IR::P4Table *table) override; + bool preorder(const IR::Key *key) override; + bool preorder(const IR::P4Action *action) override; - void writeGraphToFile(const Graph &g, const cstring &name); + std::vector controlGraphsArray{}; private: P4::ReferenceMap *refMap; P4::TypeMap *typeMap; diff --git a/backends/graphs/graph_visitor.cpp b/backends/graphs/graph_visitor.cpp new file mode 100644 index 00000000000..5468c9011e3 --- /dev/null +++ b/backends/graphs/graph_visitor.cpp @@ -0,0 +1,227 @@ +/** + * @author Timotej Ponek, FIT VUT Brno + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graph_visitor.h" + +#include "graphs.h" +#include "lib/nullstream.h" +#include "lib/path.h" + +namespace graphs { + +void Graph_visitor::writeGraphToFile(const Graph &g, const cstring &name) { + auto path = Util::PathName(graphsDir).join(name + ".dot"); + auto out = openFile(path.toString(), false); + if (out == nullptr) { + ::error(ErrorType::ERR_IO, "Failed to open file %1%", path.toString()); + return; + } + // custom label writers not supported with subgraphs, so we populate + // *_attribute_t properties instead using our GraphAttributeSetter class. + boost::write_graphviz(*out, g); +} + +const char* Graph_visitor::getType(const VertexType &v_type) { + switch (v_type) { + case VertexType::TABLE: + return "table"; + break; + case VertexType::KEY: + return "key"; + break; + case VertexType::ACTION: + return "action"; + break; + case VertexType::CONDITION: + return "condition"; + break; + case VertexType::SWITCH: + return "switch"; + break; + case VertexType::STATEMENTS: + return "statements"; + break; + case VertexType::CONTROL: + return "control"; + break; + case VertexType::STATE: + return "state"; + break; + case VertexType::OTHER: + default: + return "other"; + break; + } +} + +const char* Graph_visitor::getPrevType(const PrevType &prev_type) { + switch (prev_type) { + case PrevType::Control: + return "control"; + case PrevType::Parser: + return "parser"; + default: + return "err"; + break; + } +} + +void Graph_visitor::forLoopJson(std::vector &graphsArray, PrevType node_type) { + for (auto g : graphsArray) { + auto block = new Util::JsonObject(); + programBlocks->emplace_back(block); + + block->emplace("type", getPrevType(node_type)); + block->emplace("name", + boost::get_property(*g, boost::graph_name)); + + auto nodesArray = new Util::JsonArray(); + block->emplace("nodes", nodesArray); + + auto parserEdges = new Util::JsonArray(); + block->emplace("transitions", parserEdges); + + auto subg = *g; + + auto vertices = boost::vertices(subg); + for (auto &vit = vertices.first; vit != vertices.second; ++vit) { + auto node = new Util::JsonObject(); + nodesArray->emplace_back(node); + node->emplace("node_nmb", *vit); + + const auto &vinfo = subg[*vit]; + + node->emplace("name", vinfo.name.escapeJson()); + node->emplace("type", getType(vinfo.type)); + node->emplace("type_enum", (unsigned)vinfo.type); + } + + auto edges = boost::edges(subg); + for (auto &eit = edges.first; eit != edges.second; ++eit) { + auto edge = new Util::JsonObject(); + parserEdges->emplace_back(edge); + + // answer https://stackoverflow.com/a/12001149 + auto from = boost::source(*eit, subg); + auto to = boost::target(*eit, subg); + + edge->emplace("from", from); + edge->emplace("to", to); + + edge->emplace("cond", boost::get(boost::edge_name, subg, *eit).escapeJson()); + } + } +} + +void Graph_visitor::forLoopFullGraph(std::vector &graphsArray, fullGraphOpts* opts, + PrevType prev_type) { + unsigned long t_prev_adder = opts->node_i; + + for (auto g_ : graphsArray) { + auto &subfg = opts->fg.create_subgraph(); + g = &subfg; + + // set subg properties + boost::get_property(subfg, boost::graph_name) = "cluster" + + Util::toString(opts->cluster_i++); + boost::get_property(subfg, boost::graph_graph_attribute)["label"] = + boost::get_property(*g_, boost::graph_name); + boost::get_property(subfg, boost::graph_graph_attribute)["style"] = "bold"; + boost::get_property(subfg, boost::graph_graph_attribute)["fontsize"] = "22pt"; + + // no statements in graph, merge "__START__" and "__EXIT__" nodes + if (g_->m_global_vertex.size() == 2) { + add_vertex(cstring("Empty body"), VertexType::EMPTY); + } else { + boost::copy_graph(*g_, subfg, boost::edge_copy(edge_name_copier(*g_, subfg))); + } + + // connect subgraphs + if (opts->cluster_i > 1) { + if (prev_type == PrevType::Parser) + { + add_edge(opts->node_i - 2, opts->node_i, "", opts->cluster_i); + prev_type = PrevType::Control; + } else { + add_edge(t_prev_adder, opts->node_i, "", opts->cluster_i); + } + } + + // if "__START__" and "__EXIT__" nodes merged, increase opts->node_i only by one + if (g_->m_global_vertex.size() == 2) { + t_prev_adder = opts->node_i; + opts->node_i += 1; + } else { + t_prev_adder = opts->node_i + 1; + opts->node_i += g_->m_global_vertex.size(); + } + } +} + +void Graph_visitor::process(std::vector &controlGraphsArray, + std::vector &parserGraphsArray) { + if (graphs) { + for (auto g : controlGraphsArray) { + GraphAttributeSetter()(*g); + writeGraphToFile(*g, boost::get_property(*g, boost::graph_name)); + } + for (auto g : parserGraphsArray) { + GraphAttributeSetter()(*g); + writeGraphToFile(*g, boost::get_property(*g, boost::graph_name)); + } + } + + if (fullGraph) { + fullGraphOpts opts; + + boost::get_property(opts.fg, boost::graph_name) = "fullGraph"; + // enables edges with tails between clusters + boost::get_property(opts.fg, boost::graph_graph_attribute)["compound"] = "true"; + + forLoopFullGraph(parserGraphsArray, &opts, PrevType::Parser); + forLoopFullGraph(controlGraphsArray, &opts, PrevType::Parser); + + GraphAttributeSetter()(opts.fg); + writeGraphToFile(opts.fg, "fullGraph"); + } + + if (jsonOut) { + json = new Util::JsonObject(); + + // remove '.p4' and path from program name + auto file_without_p4 = (filename.findlast('.') == nullptr) ? filename : + filename.before(filename.findlast('.')); + const char* file_without_path = file_without_p4; + if (file_without_p4.findlast('/') != nullptr) { + file_without_path = file_without_p4.findlast('/') + 1; // char* without '/' + } + + json->emplace("name", file_without_path); + programBlocks = new Util::JsonArray(); + json->emplace("nodes", programBlocks); + + forLoopJson(parserGraphsArray, PrevType::Parser); + forLoopJson(controlGraphsArray, PrevType::Control); + + std::ofstream file; + auto path = Util::PathName(graphsDir).join("fullGraph.json"); + file.open(path.toString()); + file << json->toString() << std::endl; + file.close(); + } +} + +} // namespace graphs diff --git a/backends/graphs/graph_visitor.h b/backends/graphs/graph_visitor.h new file mode 100644 index 00000000000..3ba750f49de --- /dev/null +++ b/backends/graphs/graph_visitor.h @@ -0,0 +1,142 @@ +/** + * @author Timotej Ponek, FIT VUT Brno + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "graphs.h" + +#include +#include + +#ifndef _BACKENDS_GRAPHS_GRAPH_VISITOR_H_ +#define _BACKENDS_GRAPHS_GRAPH_VISITOR_H_ + +namespace graphs{ + +/** + * "process" function of class is dependent on previous aplication of visitor + * classes Controls and Parsers onto IR, and can only be run after them + */ +class Graph_visitor : public Graphs { + private: + /** Enum used to create correct connection between subgraphs */ + enum class PrevType{ + Control, + Parser + }; + + /** Stores variables and fullgraph */ + struct fullGraphOpts{ + Graph fg; // fullGraph + + /* variables needed for subgraph connection*/ + unsigned long node_i = 0; // node indexer + unsigned cluster_i = 0; // cluster indexer + }; + + /** Copies only edge name */ + struct edge_name_copier { + /** + * @param from graph that will be copied + * @param to graph to which "from" will be copied + */ + edge_name_copier(const Graph& from, Graph& to) + : from(from), to(to){} + const Graph &from; + Graph &to; + + void operator()(Graph::edge_descriptor input, Graph::edge_descriptor output) const { + auto label = boost::get(boost::edge_name, from, input); + + boost::put(boost::edge_name, to, output, label); + } + }; + + public: + /** + * @param graphsDir directory where graphs will be stored + * @param graphs option to output graph for each function block + * @param fullGraph option to create fullGraph + * @param jsonOut option to create json fullGraph + */ + Graph_visitor(const cstring &graphsDir, const bool &graphs, + const bool &fullGraph, const bool &jsonOut, + const cstring &filename) : + graphsDir(graphsDir), graphs(graphs), + fullGraph(fullGraph), jsonOut(jsonOut), + filename(filename) {} + /** + * @brief Maps VertexType to string + * @param v_type VertexType to map + * @return string representation of v_type + */ + const char* getType(const VertexType &v_type); + /** + * @brief Maps PrevType to string + * @param prev_type PrevType to map + * @return string representation of prev_type + */ + const char* getPrevType(const PrevType &prev_type); + /** + * @brief main function + * + * @details based on the value of boolean class variables "graphs", "fullGraph", "jsonOut" + * executes: + * "graphs" - outputs boost graphs to files + * "fullGraph" - merges boost graphs into one CFG, and outputs to file + * "jsonOut" - iterates over boost graphs, and creates json representation of these graphs + * + * @param controlGraphsArray vector with boost graphs of control blocks + * @param parserGraphsArray vector with boost graphs of control parsers + */ + void process(std::vector &controlGraphsArray, std::vector &parserGraphsArray); + /** + * @brief Writes boost graph "g" in dot format to file given by "name" + * @param g boost graph + * @param name file name + */ + void writeGraphToFile(const Graph &g, const cstring &name); + + private: + /** + * @brief Loops over vector graphsArray with boost graphs, creating json representation of CFG + * @param graphsArray vector containing boost graphs + * @param prev_type represents whether graphs in graphsArray are of type control or parser + * + */ + void forLoopJson(std::vector &graphsArray, PrevType prev_type); + /** + * @brief Loops over vector graphsArray with boost graphs, creating fullgraph + * It basically merges all graphs in graphsArray into one CFG + * @param graphsArray vector containing boost graphs + * @param[in,out] opts stores fullgraph and needed variables used for indexing + * @param prev_type used to correctly connect subgraphs of parser and control type + */ + void forLoopFullGraph(std::vector &graphsArray, fullGraphOpts* opts, + PrevType prev_type); + + Util::JsonObject* json; // stores json that will be outputted + Util::JsonArray* programBlocks; // stores objects in top level array "nodes" + const cstring graphsDir; + // options + const bool &graphs; // output boost graphs to files + const bool &fullGraph; // merge boost graphs into one CFG, and output to file + const bool &jsonOut; // iterate over boost graphs, and create json representation of these + // graphs + const cstring filename; +}; + +} // namespace graphs + +#endif /* _BACKENDS_GRAPHS_GRAPH_VISITOR_H_ */ diff --git a/backends/graphs/graphs.cpp b/backends/graphs/graphs.cpp index 6e2c5d22fbd..bb844791aab 100644 --- a/backends/graphs/graphs.cpp +++ b/backends/graphs/graphs.cpp @@ -37,19 +37,47 @@ void Graphs::add_edge(const vertex_t &from, const vertex_t &to, const cstring &n boost::put(boost::edge_name, g->root(), ep.first, name); } +void Graphs::add_edge(const vertex_t &from, const vertex_t &to, const cstring &name, + unsigned cluster_id) { + auto ep = boost::add_edge(from, to, g->root()); + boost::put(boost::edge_name, g->root(), ep.first, name); + + auto attrs = boost::get(boost::edge_attribute, g->root()); + + attrs[ep.first]["ltail"] = "cluster"+Util::toString(cluster_id-2); + attrs[ep.first]["lhead"] = "cluster"+Util::toString(cluster_id-1); +} + +void Graphs::limitStringSize(std::stringstream &sstream, std::stringstream &helper_sstream){ + if (helper_sstream.str().size() > 25) { + sstream << helper_sstream.str().substr(0, 25) << "..."; + } else { + sstream << helper_sstream.str(); + } + helper_sstream.str(""); + helper_sstream.clear(); +} + boost::optional Graphs::merge_other_statements_into_vertex() { if (statementsStack.empty()) return boost::none; std::stringstream sstream; + std::stringstream helper_sstream; // to limit line width + if (statementsStack.size() == 1) { - statementsStack[0]->dbprint(sstream); + statementsStack[0]->dbprint(helper_sstream); + limitStringSize(sstream, helper_sstream); } else if (statementsStack.size() == 2) { - statementsStack[0]->dbprint(sstream); - sstream << "\n"; - statementsStack[1]->dbprint(sstream); + statementsStack[0]->dbprint(helper_sstream); + limitStringSize(sstream, helper_sstream); + sstream << "\\n"; + statementsStack[1]->dbprint(helper_sstream); + limitStringSize(sstream, helper_sstream); } else { - statementsStack[0]->dbprint(sstream); - sstream << "\n...\n"; - statementsStack.back()->dbprint(sstream); + statementsStack[0]->dbprint(helper_sstream); + limitStringSize(sstream, helper_sstream); + sstream << "\\n...\\n"; + statementsStack.back()->dbprint(helper_sstream); + limitStringSize(sstream, helper_sstream); } auto v = add_vertex(cstring(sstream), VertexType::STATEMENTS); for (auto parent : parents) diff --git a/backends/graphs/graphs.h b/backends/graphs/graphs.h index e97ea50090c..71af0a78259 100644 --- a/backends/graphs/graphs.h +++ b/backends/graphs/graphs.h @@ -96,11 +96,15 @@ class Graphs : public Inspector { public: enum class VertexType { TABLE, + KEY, + ACTION, CONDITION, SWITCH, STATEMENTS, CONTROL, - OTHER + OTHER, + STATE, + EMPTY }; struct Vertex { cstring name; @@ -140,6 +144,15 @@ class Graphs : public Inspector { vertex_t add_vertex(const cstring &name, VertexType type); vertex_t add_and_connect_vertex(const cstring &name, VertexType type); void add_edge(const vertex_t &from, const vertex_t &to, const cstring &name); + /** + * @brief used to connect subgraphs + * @param from node from wich edge will start + * @param to node where edge will end + * @param name used as edge label + * @param cluster_id id of cluster, that will be connected to previous cluster + */ + void add_edge(const vertex_t &from, const vertex_t &to, const cstring &name, + unsigned cluster_id); class GraphAttributeSetter { public: @@ -164,6 +177,7 @@ class Graphs : public Inspector { static cstring vertexTypeGetShape(VertexType type) { switch (type) { case VertexType::TABLE: + case VertexType::ACTION: return "ellipse"; default: return "rectangle"; @@ -176,6 +190,11 @@ class Graphs : public Inspector { switch (type) { case VertexType::CONTROL: return "dashed"; + case VertexType::EMPTY: + return "invis"; + case VertexType::KEY: + case VertexType::CONDITION: + return "rounded"; default: return "solid"; } @@ -197,6 +216,14 @@ class Graphs : public Inspector { vertex_t exit_v{}; Parents parents{}; std::vector statementsStack{}; + + private: + /** + * @brief Limits string size in helper_sstream and resets it + * @param[out] sstream stringstream where trimmed string is stored + * @param helper_sstream contains string, which will be trimmed + */ + void limitStringSize(std::stringstream &sstream, std::stringstream &helper_sstream); }; } // namespace graphs diff --git a/backends/graphs/p4c-graphs.cpp b/backends/graphs/p4c-graphs.cpp index 5fe6f29d1c2..9307b1b1ec8 100644 --- a/backends/graphs/p4c-graphs.cpp +++ b/backends/graphs/p4c-graphs.cpp @@ -30,6 +30,7 @@ limitations under the License. #include "graphs.h" #include "controls.h" #include "parsers.h" +#include "graph_visitor.h" #include "ir/json_loader.h" #include "fstream" @@ -63,8 +64,10 @@ MidEnd::MidEnd(CompilerOptions& options) { class Options : public CompilerOptions { public: cstring graphsDir{"."}; - // read from json - bool loadIRFromJson = false; + bool loadIRFromJson = false; // read from json + bool graphs = true; // default behavior + bool fullGraph = false; + bool jsonOut = false; Options() { registerOption("--graphs-dir", "dir", [this](const char* arg) { graphsDir = arg; return true; }, @@ -72,9 +75,26 @@ class Options : public CompilerOptions { "(default is current working directory)\n"); registerOption("--fromJSON", "file", [this](const char* arg) { loadIRFromJson = true; file = arg; return true; }, - "Use IR representation from JsonFile dumped previously,"\ + "Use IR representation from JsonFile dumped previously, "\ "the compilation starts with reduced midEnd."); + registerOption("--graphs", nullptr, + [this](const char*){ graphs = true; isGraphsSet = true; return true; }, + "Use if you want default behavior - generation of separate graphs "\ + "for each program block (enabled by default, "\ + "if options --fullGraph or --jsonOut are not present)."); + registerOption("--fullGraph", nullptr, + [this](const char*){ fullGraph = true; + if (!isGraphsSet) graphs = false; return true; }, + "Use if you want to generate graph depicting control flow "\ + "through all program blocks (fullGraph)."); + registerOption("--jsonOut", nullptr, + [this](const char*){ jsonOut = true; + if (!isGraphsSet) graphs = false; return true; }, + "Use to generate json output of fullGraph."); } + + private: + bool isGraphsSet = false; }; using GraphsContext = P4CContextWithOptions; @@ -158,5 +178,10 @@ int main(int argc, char *const argv[]) { graphs::ParserGraphs pgg(&midEnd.refMap, &midEnd.typeMap, options.graphsDir); program->apply(pgg); + graphs::Graph_visitor gvs(options.graphsDir, options.graphs, options.fullGraph, + options.jsonOut, options.file); + + gvs.process(cgen.controlGraphsArray, pgg.parserGraphsArray); + return ::errorCount() > 0; } diff --git a/backends/graphs/parsers.cpp b/backends/graphs/parsers.cpp index 42f8b222d7c..4223745b4b6 100644 --- a/backends/graphs/parsers.cpp +++ b/backends/graphs/parsers.cpp @@ -15,13 +15,16 @@ * limitations under the License. */ +#include "parsers.h" + #include "frontends/common/resolveReferences/referenceMap.h" #include "frontends/p4/toP4/toP4.h" #include "lib/nullstream.h" -#include "parsers.h" namespace graphs { +using Graph = ParserGraphs::Graph; + static cstring toString(const IR::Expression* expression) { std::stringstream ss; P4::ToP4 toP4(&ss, false); @@ -30,16 +33,30 @@ static cstring toString(const IR::Expression* expression) { return cstring(ss.str()); } +// we always have only one subgraph +Graph *ParserGraphs::CreateSubGraph(Graph ¤tSubgraph, const cstring &name ){ + auto &newSubgraph = currentSubgraph.create_subgraph(); + boost::get_property(newSubgraph, boost::graph_name) = "cluster" + name; + boost::get_property(newSubgraph, boost::graph_graph_attribute)["label"] = name; + boost::get_property(newSubgraph, boost::graph_graph_attribute)["fontsize"] = "22pt"; + boost::get_property(newSubgraph, boost::graph_graph_attribute)["style"] = "bold"; + return &newSubgraph; +} + +ParserGraphs::ParserGraphs(P4::ReferenceMap *refMap, P4::TypeMap *typeMap, + const cstring &graphsDir) + : refMap(refMap), typeMap(typeMap), graphsDir(graphsDir) { + visitDagOnce = false; +} + void ParserGraphs::postorder(const IR::P4Parser *parser) { - auto path = Util::PathName(graphsDir).join(parser->name + ".dot"); - LOG2("Writing parser graph " << parser->name); - auto out = openFile(path.toString(), false); - if (out == nullptr) { - ::error(ErrorType::ERR_IO, "Failed to open file %1%", path.toString()); - return; - } + Graph *g_ = new Graph(); + g = CreateSubGraph(*g_, parser->name); + boost::get_property(*g_, boost::graph_name) = parser->name; + + std::map nodes; + unsigned int iter = 0; - (*out) << "digraph " << parser->name << "{" << std::endl; for (auto state : states[parser]) { cstring label = state->name; if (state->selectExpression != nullptr && @@ -47,15 +64,17 @@ void ParserGraphs::postorder(const IR::P4Parser *parser) { label += "\n" + toString( state->selectExpression->to()->select); } - (*out) << state->name.name << " [shape=rectangle,label=\"" << - label << "\"]" << std::endl; + add_vertex(label, VertexType::STATE); + nodes.emplace(std::make_pair(state->name.name.c_str(), iter++)); } for (auto edge : transitions[parser]) { - *out << edge->sourceState->name.name << " -> " << edge->destState->name.name << - " [label=\"" << edge->label << "\"]" << std::endl; + auto from = nodes[edge->sourceState->name.name.c_str()]; + auto to = nodes[edge->destState->name.name.c_str()]; + add_edge((vertex_t)from, (vertex_t)to, edge->label); } - *out << "}" << std::endl; + + parserGraphsArray.push_back(g_); } void ParserGraphs::postorder(const IR::ParserState* state) { diff --git a/backends/graphs/parsers.h b/backends/graphs/parsers.h index a38174efc04..a7cc26ce58b 100644 --- a/backends/graphs/parsers.h +++ b/backends/graphs/parsers.h @@ -18,6 +18,8 @@ #ifndef _BACKENDS_GRAPHS_PARSERS_H_ #define _BACKENDS_GRAPHS_PARSERS_H_ +#include "graphs.h" + #include "frontends/common/resolveReferences/referenceMap.h" #include "ir/ir.h" #include "lib/cstring.h" @@ -25,18 +27,9 @@ #include "lib/path.h" #include "lib/safe_vector.h" -namespace P4 { -// Forward declaration to avoid includes -class TypeMap; -class ReferenceMap; -} // end namespace P4 - -namespace graphs { - -class ParserGraphs : public Inspector { - const P4::ReferenceMap* refMap; - const cstring graphsDir; +namespace graphs{ +class ParserGraphs : public Graphs{ protected: struct TransitionEdge { const IR::ParserState* sourceState; @@ -53,17 +46,22 @@ class ParserGraphs : public Inspector { std::map> states; public: - ParserGraphs(P4::ReferenceMap *refMap, P4::TypeMap *, const cstring &graphsDir) : - refMap(refMap), graphsDir(graphsDir) { - CHECK_NULL(refMap); setName("ParserGraphs"); - } + ParserGraphs(P4::ReferenceMap *refMap, P4::TypeMap *typeMap, const cstring &graphsDir); + Graph *CreateSubGraph(Graph ¤tSubgraph, const cstring &name); void postorder(const IR::P4Parser* parser) override; void postorder(const IR::ParserState* state) override; void postorder(const IR::PathExpression* expression) override; void postorder(const IR::SelectExpression* expression) override; + + std::vector parserGraphsArray{}; + + private: + P4::ReferenceMap *refMap; P4::TypeMap *typeMap; + const cstring graphsDir; + boost::optional instanceName{}; }; -} // namespace graphs +} #endif /* _BACKENDS_GRAPHS_PARSERS_H_ */ diff --git a/backends/graphs/resources/firewall.fullGraph.json b/backends/graphs/resources/firewall.fullGraph.json new file mode 100644 index 00000000000..81890ac9b9f --- /dev/null +++ b/backends/graphs/resources/firewall.fullGraph.json @@ -0,0 +1,496 @@ +{ + "name" : "program", + "nodes" : [ + { + "type" : "parser", + "name" : "MyParser", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "start\n(hdr.ethernet.etherType)", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 1, + "name" : "parse_ipv4\n(hdr.ipv4.protocol)", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 2, + "name" : "tcp", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 3, + "name" : "accept", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 4, + "name" : "reject", + "type" : "state", + "type_enum" : 8 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 1, + "cond" : "" + }, + { + "from" : 0, + "to" : 3, + "cond" : "" + }, + { + "from" : 1, + "to" : 2, + "cond" : "" + }, + { + "from" : 1, + "to" : 3, + "cond" : "" + }, + { + "from" : 2, + "to" : 3, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "MyVerifyChecksum", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "MyIngress", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "hdr.ipv4.isValid();", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 3, + "name" : "ipv4_lpm_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 4, + "name" : "lpm: \"hdr.ipv4.dstAddr\"\\n", + "type" : "key", + "type_enum" : 1 + }, + { + "node_nmb" : 5, + "name" : "ipv4_forward", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 6, + "name" : "standard_metadata.egress_spec = port;\n...\nhdr.ipv4.ttl = hdr.ipv4.ttl + 255;", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 7, + "name" : "drop", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 8, + "name" : "mark_to_drop(standard_metadata);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 9, + "name" : "NoAction_1", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 10, + "name" : "hdr.tcp.isValid();", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 11, + "name" : "direction_0/direction = 0;", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 12, + "name" : "check_ports_0/check_ports.apply().hit;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 13, + "name" : "direction_0/direction == 0;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 14, + "name" : "compute_hashes();", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 15, + "name" : "compute_hashes_1/compute_hashes();", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 16, + "name" : "direction_0/direction == 0;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 17, + "name" : "hdr.tcp.syn == 1;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 18, + "name" : "bloom_filter/bloom_filter_1.write(reg_pos_one_0/reg_pos_one, 1);\nbloom_filter_0/bloom_filter_2.write(reg_pos_two_0/reg_pos_two, 1);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 19, + "name" : "direction_0/direction == 1;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 20, + "name" : "bloom_filter/bloom_filter_1.read(reg_val_one_0/reg_val_one, reg_pos_one_0/reg_pos_one);\nbloom_filter_0/bloom_filter_2.read(reg_val_two_0/reg_val_two, reg_pos_two_0/reg_pos_two);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 21, + "name" : "reg_val_one_0/reg_val_one != 1 || reg_val_two_0/reg_val_two != 1;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 22, + "name" : "drop_1/drop();", + "type" : "statements", + "type_enum" : 5 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 3, + "cond" : "" + }, + { + "from" : 2, + "to" : 1, + "cond" : "" + }, + { + "from" : 3, + "to" : 4, + "cond" : "" + }, + { + "from" : 4, + "to" : 5, + "cond" : "" + }, + { + "from" : 4, + "to" : 7, + "cond" : "" + }, + { + "from" : 4, + "to" : 9, + "cond" : "" + }, + { + "from" : 5, + "to" : 6, + "cond" : "" + }, + { + "from" : 6, + "to" : 10, + "cond" : "" + }, + { + "from" : 7, + "to" : 8, + "cond" : "" + }, + { + "from" : 8, + "to" : 10, + "cond" : "" + }, + { + "from" : 9, + "to" : 10, + "cond" : "" + }, + { + "from" : 10, + "to" : 11, + "cond" : "" + }, + { + "from" : 10, + "to" : 1, + "cond" : "" + }, + { + "from" : 11, + "to" : 12, + "cond" : "" + }, + { + "from" : 12, + "to" : 13, + "cond" : "" + }, + { + "from" : 12, + "to" : 1, + "cond" : "" + }, + { + "from" : 13, + "to" : 14, + "cond" : "" + }, + { + "from" : 13, + "to" : 15, + "cond" : "" + }, + { + "from" : 14, + "to" : 16, + "cond" : "" + }, + { + "from" : 15, + "to" : 16, + "cond" : "" + }, + { + "from" : 16, + "to" : 17, + "cond" : "" + }, + { + "from" : 16, + "to" : 19, + "cond" : "" + }, + { + "from" : 17, + "to" : 18, + "cond" : "" + }, + { + "from" : 17, + "to" : 1, + "cond" : "" + }, + { + "from" : 18, + "to" : 1, + "cond" : "" + }, + { + "from" : 19, + "to" : 20, + "cond" : "" + }, + { + "from" : 19, + "to" : 1, + "cond" : "" + }, + { + "from" : 20, + "to" : 21, + "cond" : "" + }, + { + "from" : 21, + "to" : 22, + "cond" : "" + }, + { + "from" : 21, + "to" : 1, + "cond" : "" + }, + { + "from" : 22, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "MyEgress", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "MyComputeChecksum", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "update_checksum, bit<4>, bit<8>, bit<16>, bit<16>, bit<3>, bit<13>, bit<8>, bit<8>, bit<32>, bit<32>>, bit<16>>(hdr.ipv4.isValid(), {hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr}, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);", + "type" : "statements", + "type_enum" : 5 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "MyDeparser", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "packet.emit(hdr.ethernet);\n...\npacket.emit(hdr.tcp);", + "type" : "statements", + "type_enum" : 5 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 1, + "cond" : "" + } + ] + } + ] +} diff --git a/backends/graphs/resources/firewall.fullGraph.png b/backends/graphs/resources/firewall.fullGraph.png new file mode 100644 index 00000000000..ca3363bf6fa Binary files /dev/null and b/backends/graphs/resources/firewall.fullGraph.png differ diff --git a/backends/graphs/resources/flowlet_switching-bmv2.egress.png b/backends/graphs/resources/flowlet_switching-bmv2.egress.png new file mode 100644 index 00000000000..639bcb128b5 Binary files /dev/null and b/backends/graphs/resources/flowlet_switching-bmv2.egress.png differ diff --git a/backends/graphs/resources/flowlet_switching-bmv2.fullGraph.json b/backends/graphs/resources/flowlet_switching-bmv2.fullGraph.json new file mode 100644 index 00000000000..da752503ce6 --- /dev/null +++ b/backends/graphs/resources/flowlet_switching-bmv2.fullGraph.json @@ -0,0 +1,699 @@ +{ + "name" : "program", + "nodes" : [ + { + "type" : "parser", + "name" : "ParserImpl", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "parse_ethernet\n(hdr.ethernet.etherType)", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 1, + "name" : "parse_ipv4\n(hdr.ipv4.protocol)", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 2, + "name" : "parse_tcp", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 3, + "name" : "start", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 4, + "name" : "accept", + "type" : "state", + "type_enum" : 8 + }, + { + "node_nmb" : 5, + "name" : "reject", + "type" : "state", + "type_enum" : 8 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 1, + "cond" : "" + }, + { + "from" : 0, + "to" : 4, + "cond" : "" + }, + { + "from" : 1, + "to" : 2, + "cond" : "" + }, + { + "from" : 1, + "to" : 4, + "cond" : "" + }, + { + "from" : 2, + "to" : 4, + "cond" : "" + }, + { + "from" : 3, + "to" : 0, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "verifyChecksum", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "verify_checksum, bit<4>, bit<8>, bit<16>, bit<16>, bit<3>, bit<13>, bit<8>, bit<8>, bit<32>, bit<32>>, bit<16>>(hdr.ipv4.isValid(), {hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr}, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);", + "type" : "statements", + "type_enum" : 5 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "ingress", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "flowlet_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 3, + "name" : "lookup_flowlet_map", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 4, + "name" : "hash, bit<13>, list, bit<32>, bit<8>, bit<16>, bit<16>>, bit<26>>(meta.ingress_metadata.flowlet_map_index, HashAlgorithm.crc16, 0, {hdr.ipv4.srcAddr, hdr.ipv4.dstAddr, hdr.ipv4.protocol, hdr.tcp.srcPort, hdr.tcp.dstPort}, 13);\n...\nflowlet_lasttime_0/flowlet_lasttime.write((bit<32>)meta.ingress_metadata.flowlet_map_index, (bit<32>)standard_metadata.ingress_global_timestamp);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 5, + "name" : "NoAction_5", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 6, + "name" : "meta.ingress_metadata.flow_ipg > 50000;", + "type" : "condition", + "type_enum" : 3 + }, + { + "node_nmb" : 7, + "name" : "new_flowlet_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 8, + "name" : "update_flowlet_id", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 9, + "name" : "meta.ingress_metadata.flowlet_id = meta.ingress_metadata.flowlet_id + 1;\nflowlet_id_0/flowlet_id.write((bit<32>)meta.ingress_metadata.flowlet_map_index, meta.ingress_metadata.flowlet_id);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 10, + "name" : "NoAction_7", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 11, + "name" : "ecmp_group_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 12, + "name" : "lpm: \"hdr.ipv4.dstAddr\"\\n", + "type" : "key", + "type_enum" : 1 + }, + { + "node_nmb" : 13, + "name" : "_drop_2", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 14, + "name" : "mark_to_drop(standard_metadata);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 15, + "name" : "set_ecmp_select", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 16, + "name" : "hash, bit<10>, list, bit<32>, bit<8>, bit<16>, bit<16>, bit<16>>, bit<20>>(meta.ingress_metadata.ecmp_offset, HashAlgorithm.crc16, (bit<10>)ecmp_base, {hdr.ipv4.srcAddr, hdr.ipv4.dstAddr, hdr.ipv4.protocol, hdr.tcp.srcPort, hdr.tcp.dstPort, meta.ingress_metadata.flowlet_id}, (bit<20>)ecmp_count);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 17, + "name" : "NoAction_3", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 18, + "name" : "ecmp_nhop_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 19, + "name" : "exact: \"meta.ingress_metadata.ecmp_offset\"\\n", + "type" : "key", + "type_enum" : 1 + }, + { + "node_nmb" : 20, + "name" : "_drop_3", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 21, + "name" : "mark_to_drop(standard_metadata);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 22, + "name" : "set_nhop", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 23, + "name" : "meta.ingress_metadata.nhop_ipv4 = nhop_ipv4_1/nhop_ipv4;\n...\nhdr.ipv4.ttl = hdr.ipv4.ttl + 255;", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 24, + "name" : "NoAction_4", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 25, + "name" : "forward_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 26, + "name" : "exact: \"meta.ingress_metadata.nhop_ipv4\"\\n", + "type" : "key", + "type_enum" : 1 + }, + { + "node_nmb" : 27, + "name" : "set_dmac", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 28, + "name" : "hdr.ethernet.dstAddr = dmac;", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 29, + "name" : "_drop_4", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 30, + "name" : "mark_to_drop(standard_metadata);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 31, + "name" : "NoAction_6", + "type" : "action", + "type_enum" : 2 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 3, + "cond" : "" + }, + { + "from" : 2, + "to" : 5, + "cond" : "" + }, + { + "from" : 3, + "to" : 4, + "cond" : "" + }, + { + "from" : 4, + "to" : 6, + "cond" : "" + }, + { + "from" : 5, + "to" : 6, + "cond" : "" + }, + { + "from" : 6, + "to" : 7, + "cond" : "" + }, + { + "from" : 6, + "to" : 11, + "cond" : "" + }, + { + "from" : 7, + "to" : 8, + "cond" : "" + }, + { + "from" : 7, + "to" : 10, + "cond" : "" + }, + { + "from" : 8, + "to" : 9, + "cond" : "" + }, + { + "from" : 9, + "to" : 11, + "cond" : "" + }, + { + "from" : 10, + "to" : 11, + "cond" : "" + }, + { + "from" : 11, + "to" : 12, + "cond" : "" + }, + { + "from" : 12, + "to" : 13, + "cond" : "" + }, + { + "from" : 12, + "to" : 15, + "cond" : "" + }, + { + "from" : 12, + "to" : 17, + "cond" : "" + }, + { + "from" : 13, + "to" : 14, + "cond" : "" + }, + { + "from" : 14, + "to" : 18, + "cond" : "" + }, + { + "from" : 15, + "to" : 16, + "cond" : "" + }, + { + "from" : 16, + "to" : 18, + "cond" : "" + }, + { + "from" : 17, + "to" : 18, + "cond" : "" + }, + { + "from" : 18, + "to" : 19, + "cond" : "" + }, + { + "from" : 19, + "to" : 20, + "cond" : "" + }, + { + "from" : 19, + "to" : 22, + "cond" : "" + }, + { + "from" : 19, + "to" : 24, + "cond" : "" + }, + { + "from" : 20, + "to" : 21, + "cond" : "" + }, + { + "from" : 21, + "to" : 25, + "cond" : "" + }, + { + "from" : 22, + "to" : 23, + "cond" : "" + }, + { + "from" : 23, + "to" : 25, + "cond" : "" + }, + { + "from" : 24, + "to" : 25, + "cond" : "" + }, + { + "from" : 25, + "to" : 26, + "cond" : "" + }, + { + "from" : 26, + "to" : 27, + "cond" : "" + }, + { + "from" : 26, + "to" : 29, + "cond" : "" + }, + { + "from" : 26, + "to" : 31, + "cond" : "" + }, + { + "from" : 27, + "to" : 28, + "cond" : "" + }, + { + "from" : 28, + "to" : 1, + "cond" : "" + }, + { + "from" : 29, + "to" : 30, + "cond" : "" + }, + { + "from" : 30, + "to" : 1, + "cond" : "" + }, + { + "from" : 31, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "egress", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "send_frame_0", + "type" : "table", + "type_enum" : 0 + }, + { + "node_nmb" : 3, + "name" : "exact: \"standard_metadata.egress_port\"\\n", + "type" : "key", + "type_enum" : 1 + }, + { + "node_nmb" : 4, + "name" : "rewrite_mac", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 5, + "name" : "hdr.ethernet.srcAddr = smac;", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 6, + "name" : "_drop", + "type" : "action", + "type_enum" : 2 + }, + { + "node_nmb" : 7, + "name" : "mark_to_drop(standard_metadata);", + "type" : "statements", + "type_enum" : 5 + }, + { + "node_nmb" : 8, + "name" : "NoAction_2", + "type" : "action", + "type_enum" : 2 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 3, + "cond" : "" + }, + { + "from" : 3, + "to" : 4, + "cond" : "" + }, + { + "from" : 3, + "to" : 6, + "cond" : "" + }, + { + "from" : 3, + "to" : 8, + "cond" : "" + }, + { + "from" : 4, + "to" : 5, + "cond" : "" + }, + { + "from" : 5, + "to" : 1, + "cond" : "" + }, + { + "from" : 6, + "to" : 7, + "cond" : "" + }, + { + "from" : 7, + "to" : 1, + "cond" : "" + }, + { + "from" : 8, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "computeChecksum", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "update_checksum, bit<4>, bit<8>, bit<16>, bit<16>, bit<3>, bit<13>, bit<8>, bit<8>, bit<32>, bit<32>>, bit<16>>(hdr.ipv4.isValid(), {hdr.ipv4.version, hdr.ipv4.ihl, hdr.ipv4.diffserv, hdr.ipv4.totalLen, hdr.ipv4.identification, hdr.ipv4.flags, hdr.ipv4.fragOffset, hdr.ipv4.ttl, hdr.ipv4.protocol, hdr.ipv4.srcAddr, hdr.ipv4.dstAddr}, hdr.ipv4.hdrChecksum, HashAlgorithm.csum16);", + "type" : "statements", + "type_enum" : 5 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 1, + "cond" : "" + } + ] + }, + { + "type" : "control", + "name" : "DeparserImpl", + "nodes" : [ + { + "node_nmb" : 0, + "name" : "__START__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 1, + "name" : "__EXIT__", + "type" : "other", + "type_enum" : 7 + }, + { + "node_nmb" : 2, + "name" : "packet.emit(hdr.ethernet);\n...\npacket.emit(hdr.tcp);", + "type" : "statements", + "type_enum" : 5 + } + ], + "transitions" : [ + { + "from" : 0, + "to" : 2, + "cond" : "" + }, + { + "from" : 2, + "to" : 1, + "cond" : "" + } + ] + } + ] +} diff --git a/backends/graphs/resources/flowlet_switching-bmv2.fullGraph.png b/backends/graphs/resources/flowlet_switching-bmv2.fullGraph.png new file mode 100644 index 00000000000..8aca7c4a8b2 Binary files /dev/null and b/backends/graphs/resources/flowlet_switching-bmv2.fullGraph.png differ diff --git a/backends/graphs/resources/flowlet_switching-bmv2.ingress.png b/backends/graphs/resources/flowlet_switching-bmv2.ingress.png index 746b60c3911..51eacb05f7a 100644 Binary files a/backends/graphs/resources/flowlet_switching-bmv2.ingress.png and b/backends/graphs/resources/flowlet_switching-bmv2.ingress.png differ diff --git a/backends/graphs/resources/flowlet_switching-bmv2.parser.png b/backends/graphs/resources/flowlet_switching-bmv2.parser.png old mode 100755 new mode 100644 index e507a72cd15..42c7670edab Binary files a/backends/graphs/resources/flowlet_switching-bmv2.parser.png and b/backends/graphs/resources/flowlet_switching-bmv2.parser.png differ