Skip to content

Commit

Permalink
parser graph generation support for graphs backend (#969)
Browse files Browse the repository at this point in the history
* parser graph generation support for graphs backend

* cpplint fix- tab2whitespace

* header guard style fix

* create graph base class, using rectangles in graphs

* main func and Graph methods are separed to different files:p4c-graphs.cpp,graphs.cpp
  • Loading branch information
c3m3gyanesh authored and antoninbas committed Oct 30, 2017
1 parent a4dc20a commit 4ab8482
Show file tree
Hide file tree
Showing 8 changed files with 577 additions and 232 deletions.
4 changes: 3 additions & 1 deletion backends/graphs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
set (GRAPHS_SRCS
graphs.cpp
controls.cpp
parser.cpp
p4c-graphs.cpp
)

set (GRAPHS_HDRS
controls.h
graphs.h
)

add_cpplint_files(${CMAKE_CURRENT_SOURCE_DIR} "${GRAPHS_SRCS};${GRAPHS_HDRS}")
Expand Down
8 changes: 7 additions & 1 deletion backends/graphs/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 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 blocks.
now it only supports the generation of graphs for top-level control and parser blocks.

## Dependencies

Expand All @@ -26,6 +26,12 @@ test program:

![Flowlet switching ingress graph](resources/flowlet_switching-bmv2.ingress.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)
Expand Down
127 changes: 2 additions & 125 deletions backends/graphs/controls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

#include "controls.h"

#include <boost/graph/graphviz.hpp>

#include <iostream>

#include "graphs.h"

#include "frontends/p4/methodInstance.h"
#include "frontends/p4/tableApply.h"
#include "lib/log.h"
Expand All @@ -28,45 +28,6 @@ limitations under the License.

namespace graphs {

class EdgeUnconditional : public EdgeTypeIface {
public:
EdgeUnconditional() = default;
cstring label() const override { return ""; };
};

class EdgeIf : public EdgeTypeIface {
public:
enum class Branch { TRUE, FALSE };
explicit EdgeIf(Branch branch) : branch(branch) { }
cstring label() const override {
switch (branch) {
case Branch::TRUE:
return "TRUE";
case Branch::FALSE:
return "FALSE";
}
BUG("unreachable");
return "";
};

private:
Branch branch;
};

class EdgeSwitch : public EdgeTypeIface {
public:
explicit EdgeSwitch(const IR::Expression *labelExpr)
: labelExpr(labelExpr) { }
cstring label() const override {
std::stringstream sstream;
labelExpr->dbprint(sstream);
return cstring(sstream);
};

private:
const IR::Expression *labelExpr;
};

using Graph = ControlGraphs::Graph;

Graph *ControlGraphs::ControlStack::pushBack(Graph &currentSubgraph, const cstring &name) {
Expand Down Expand Up @@ -113,90 +74,6 @@ ControlGraphs::ControlGraphs(P4::ReferenceMap *refMap, P4::TypeMap *typeMap,
visitDagOnce = false;
}

vertex_t ControlGraphs::add_vertex(const cstring &name, VertexType type) {
auto v = boost::add_vertex(*g);
boost::put(&Vertex::name, *g, v, name);
boost::put(&Vertex::type, *g, v, type);
return g->local_to_global(v);
}

void ControlGraphs::add_edge(const vertex_t &from, const vertex_t &to, const cstring &name) {
auto ep = boost::add_edge(from, to, g->root());
boost::put(boost::edge_name, g->root(), ep.first, name);
}

boost::optional<vertex_t> ControlGraphs::merge_other_statements_into_vertex() {
if (statementsStack.empty()) return boost::none;
std::stringstream sstream;
if (statementsStack.size() == 1) {
statementsStack[0]->dbprint(sstream);
} else if (statementsStack.size() == 2) {
statementsStack[0]->dbprint(sstream);
sstream << "\n";
statementsStack[1]->dbprint(sstream);
} else {
statementsStack[0]->dbprint(sstream);
sstream << "\n...\n";
statementsStack.back()->dbprint(sstream);
}
auto v = add_vertex(cstring(sstream), VertexType::STATEMENTS);
for (auto parent : parents)
add_edge(parent.first, v, parent.second->label());
parents = {{v, new EdgeUnconditional()}};
statementsStack.clear();
return v;
}

vertex_t ControlGraphs::add_and_connect_vertex(const cstring &name, VertexType type) {
merge_other_statements_into_vertex();
auto v = add_vertex(name, type);
for (auto parent : parents)
add_edge(parent.first, v, parent.second->label());
return v;
}

class ControlGraphs::GraphAttributeSetter {
public:
void operator()(Graph &g) const {
auto vertices = boost::vertices(g);
for (auto vit = vertices.first; vit != vertices.second; ++vit) {
const auto &vinfo = g[*vit];
auto attrs = boost::get(boost::vertex_attribute, g);
attrs[*vit]["label"] = vinfo.name;
attrs[*vit]["style"] = vertexTypeGetStyle(vinfo.type);
attrs[*vit]["shape"] = vertexTypeGetShape(vinfo.type);
}
auto edges = boost::edges(g);
for (auto eit = edges.first; eit != edges.second; ++eit) {
auto attrs = boost::get(boost::edge_attribute, g);
attrs[*eit]["label"] = boost::get(boost::edge_name, g, *eit);
}
}

private:
static cstring vertexTypeGetShape(VertexType type) {
switch (type) {
case VertexType::TABLE:
return "ellipse";
default:
return "rectangle";
}
BUG("unreachable");
return "";
}

static cstring vertexTypeGetStyle(VertexType type) {
switch (type) {
case VertexType::CONTROL:
return "dashed";
default:
return "solid";
}
BUG("unreachable");
return "";
}
};

void ControlGraphs::writeGraphToFile(const Graph &g, const cstring &name) {
auto path = Util::PathName(graphsDir).join(name + ".dot");
auto out = openFile(path.toString(), false);
Expand Down
186 changes: 102 additions & 84 deletions backends/graphs/graphs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,108 +14,126 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

#include "ir/ir.h"
#include "ir/json_loader.h"
#include "lib/log.h"
#include "lib/error.h"
#include "lib/exceptions.h"
#include "lib/gc.h"
#include "lib/crash.h"
#include "lib/nullstream.h"
#include "frontends/common/parseInput.h"
#include "frontends/p4/evaluator/evaluator.h"
#include "frontends/p4/frontend.h"

#include "controls.h"
#include "graphs.h"

namespace graphs {

class MidEnd : public PassManager {
class Graphs::GraphAttributeSetter {
public:
P4::ReferenceMap refMap;
P4::TypeMap typeMap;
IR::ToplevelBlock *toplevel = nullptr;

explicit MidEnd(CompilerOptions& options);
IR::ToplevelBlock* process(const IR::P4Program *&program) {
program = program->apply(*this);
return toplevel;
void operator()(Graph &g) const {
auto vertices = boost::vertices(g);
for (auto vit = vertices.first; vit != vertices.second; ++vit) {
const auto &vinfo = g[*vit];
auto attrs = boost::get(boost::vertex_attribute, g);
attrs[*vit]["label"] = vinfo.name;
attrs[*vit]["style"] = vertexTypeGetStyle(vinfo.type);
attrs[*vit]["shape"] = vertexTypeGetShape(vinfo.type);
attrs[*vit]["margin"] = vertexTypeGetMargin(vinfo.type);
}
auto edges = boost::edges(g);
for (auto eit = edges.first; eit != edges.second; ++eit) {
auto attrs = boost::get(boost::edge_attribute, g);
attrs[*eit]["label"] = boost::get(boost::edge_name, g, *eit);
}
}

private:
static cstring vertexTypeGetShape(VertexType type) {
switch (type) {
case VertexType::TABLE:
return "ellipse";
case VertexType::HEADER:
return "box";
case VertexType::START:
return "circle";
case VertexType::DEFAULT:
return "doublecircle";
default:
return "rectangle";
}
BUG("unreachable");
return "";
}
};

MidEnd::MidEnd(CompilerOptions& options) {
bool isv1 = options.langVersion == CompilerOptions::FrontendVersion::P4_14;
refMap.setIsV1(isv1);
auto evaluator = new P4::EvaluatorPass(&refMap, &typeMap);
setName("MidEnd");

addPasses({
evaluator,
new VisitFunctor([this, evaluator]() { toplevel = evaluator->getToplevelBlock(); }),
});
}
static cstring vertexTypeGetStyle(VertexType type) {
switch (type) {
case VertexType::CONTROL:
return "dashed";
case VertexType::HEADER:
return "solid";
case VertexType::START:
return "solid";
case VertexType::DEFAULT:
return "solid";
default:
return "solid";
}
BUG("unreachable");
return "";
}

class Options : public CompilerOptions {
public:
cstring graphsDir{"."};
Options() {
registerOption("--graphs-dir", "dir",
[this](const char* arg) { graphsDir = arg; return true; },
"Use this directory to dump graphs in dot format "
"(default is current working directory)\n");
}
static cstring vertexTypeGetMargin(VertexType type) {
switch (type) {
case VertexType::HEADER:
return "0.05,0";
case VertexType::START:
return "0,0";
case VertexType::DEFAULT:
return "0,0";
default:
return "";
}
}
};

} // namespace graphs

int main(int argc, char *const argv[]) {
setup_gc_logging();
setup_signals();

graphs::Options options;
options.langVersion = CompilerOptions::FrontendVersion::P4_16;
options.compilerVersion = "0.0.5";

if (options.process(argc, argv) != nullptr)
options.setInputFile();
if (::errorCount() > 0)
return 1;

auto hook = options.getDebugHook();
Graphs::vertex_t Graphs::add_vertex(const cstring &name, VertexType type) {
auto v = boost::add_vertex(*g);
boost::put(&Vertex::name, *g, v, name);
boost::put(&Vertex::type, *g, v, type);
return g->local_to_global(v);
}

auto program = P4::parseP4File(options);
if (program == nullptr || ::errorCount() > 0)
return 1;
void Graphs::add_edge(const vertex_t &from, const vertex_t &to, const cstring &name) {
auto ep = boost::add_edge(from, to, g->root());
boost::put(boost::edge_name, g->root(), ep.first, name);
}

try {
P4::FrontEnd fe;
fe.addDebugHook(hook);
program = fe.run(options, program);
} catch (const Util::P4CExceptionBase &bug) {
std::cerr << bug.what() << std::endl;
return 1;
boost::optional<Graphs::vertex_t> Graphs::merge_other_statements_into_vertex() {
if (statementsStack.empty()) return boost::none;
std::stringstream sstream;
if (statementsStack.size() == 1) {
statementsStack[0]->dbprint(sstream);
} else if (statementsStack.size() == 2) {
statementsStack[0]->dbprint(sstream);
sstream << "\n";
statementsStack[1]->dbprint(sstream);
} else {
statementsStack[0]->dbprint(sstream);
sstream << "\n...\n";
statementsStack.back()->dbprint(sstream);
}
if (program == nullptr || ::errorCount() > 0)
return 1;

graphs::MidEnd midEnd(options);
midEnd.addDebugHook(hook);
const IR::ToplevelBlock *top = nullptr;
try {
top = midEnd.process(program);
if (options.dumpJsonFile)
JSONGenerator(*openFile(options.dumpJsonFile, true)) << program << std::endl;
} catch (const Util::P4CExceptionBase &bug) {
std::cerr << bug.what() << std::endl;
return 1;
}
if (::errorCount() > 0)
return 1;

LOG1("Generating graphs under " << options.graphsDir);
LOG1("Generating control graphs");
graphs::ControlGraphs cgen(&midEnd.refMap, &midEnd.typeMap, options.graphsDir);
top->getMain()->apply(cgen);
auto v = add_vertex(cstring(sstream), VertexType::STATEMENTS);
for (auto parent : parents)
add_edge(parent.first, v, parent.second->label());
parents = {{v, new EdgeUnconditional()}};
statementsStack.clear();
return v;
}

return ::errorCount() > 0;
Graphs::vertex_t Graphs::add_and_connect_vertex(const cstring &name, VertexType type) {
merge_other_statements_into_vertex();
auto v = add_vertex(name, type);
for (auto parent : parents)
add_edge(parent.first, v, parent.second->label());
return v;
}

} // namespace graphs

Loading

0 comments on commit 4ab8482

Please sign in to comment.