Jlm
dot-hls.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2021 David Metz <david.c.metz@ntnu.no>
3  * See COPYING for terms of redistribution.
4  */
5 
9 
10 #include <algorithm>
11 #include <set>
12 
13 namespace jlm::hls
14 {
15 
16 std::string
18 {
19  return ".dot";
20 }
21 
22 std::string
24 {
25  return subregion_to_dot(get_hls_lambda(rm)->subregion());
26 }
27 
28 std::string
30 {
31  auto name = get_port_name(port);
32 
33  auto dot =
34  name
35  + " [shape=plaintext label=<\n"
36  " <TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">\n"
37  " <TR>\n"
38  " <TD BORDER=\"1\" CELLPADDING=\"1\"><FONT POINT-SIZE=\"10\">"
39  + name
40  + "</FONT></TD>\n"
41  " </TR>\n"
42  " </TABLE>\n"
43  ">];\n";
44  return dot;
45 }
46 
47 std::string
49 {
50  auto name = get_port_name(port);
51 
52  auto dot =
53  "{rank=sink; " + name
54  + " [shape=plaintext label=<\n"
55  " <TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">\n"
56  " <TR>\n"
57  " <TD BORDER=\"1\" CELLPADDING=\"1\"><FONT POINT-SIZE=\"10\">"
58  + name
59  + "</FONT></TD>\n"
60  " </TR>\n"
61  " </TABLE>\n"
62  ">];}\n";
63  return dot;
64 }
65 
66 std::string
68 {
69  auto SPACER = " <TD WIDTH=\"10\"></TD>\n";
70  auto name = get_node_name(node);
71  auto opname = node->DebugString();
72  std::replace_if(opname.begin(), opname.end(), isForbiddenChar, '_');
73 
74  std::string inputs;
75  for (size_t i = 0; i < node->ninputs(); ++i)
76  {
77  if (i != 0)
78  {
79  inputs += SPACER;
80  }
81  auto in = get_port_name(node->input(i));
82  inputs += " <TD PORT=\"" + in
83  + "\" BORDER=\"1\" CELLPADDING=\"1\"><FONT POINT-SIZE=\"10\">" + in + "</FONT></TD>\n";
84  }
85 
86  std::string outputs;
87  for (size_t i = 0; i < node->noutputs(); ++i)
88  {
89  if (i != 0)
90  {
91  outputs += SPACER;
92  }
93  auto out = get_port_name(node->output(i));
94  outputs += " <TD PORT=\"" + out
95  + "\" BORDER=\"1\" CELLPADDING=\"1\"><FONT POINT-SIZE=\"10\">" + out
96  + "</FONT></TD>\n";
97  }
98 
99  std::string color = "black";
100  if (jlm::rvsdg::is<BufferOperation>(node))
101  {
102  color = "blue";
103  }
104  else if (jlm::rvsdg::is<ForkOperation>(node))
105  {
106  color = "grey";
107  }
108  else if (jlm::rvsdg::is<SinkOperation>(node))
109  {
110  color = "grey";
111  }
112  else if (jlm::rvsdg::is<BranchOperation>(node))
113  {
114  color = "green";
115  }
116  else if (jlm::rvsdg::is<MuxOperation>(node))
117  {
118  color = "darkred";
119  }
120  else if (jlm::rvsdg::is<TriggerOperation>(node) || is_constant(node))
121  {
122  color = "orange";
123  }
124 
125  // dot inspired by
126  // https://stackoverflow.com/questions/42157650/moving-graphviz-edge-out-of-the-way
127  auto dot =
128  name
129  + " [shape=plaintext label=<\n"
130  "<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">\n"
131  // inputs
132  " <TR>\n"
133  " <TD BORDER=\"0\">\n"
134  " <TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">\n"
135  " <TR>\n"
136  " <TD WIDTH=\"20\"></TD>\n"
137  + inputs
138  + " <TD WIDTH=\"20\"></TD>\n"
139  " </TR>\n"
140  " </TABLE>\n"
141  " </TD>\n"
142  " </TR>\n"
143  " <TR>\n"
144  " <TD BORDER=\"3\" STYLE=\"ROUNDED\" CELLPADDING=\"4\">"
145  + opname + "<BR/><FONT POINT-SIZE=\"10\">" + name
146  + "</FONT></TD>\n"
147  " </TR>\n"
148  " <TR>\n"
149  " <TD BORDER=\"0\">\n"
150  " <TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">\n"
151  " <TR>\n"
152  " <TD WIDTH=\"20\"></TD>\n"
153  + outputs
154  + " <TD WIDTH=\"20\"></TD>\n"
155  " </TR>\n"
156  " </TABLE>\n"
157  " </TD>\n"
158  " </TR>\n"
159  "</TABLE>\n"
160  "> fontcolor="
161  + color + " color=" + color + "];\n";
162  return dot;
163 }
164 
165 std::string
166 DotHLS::edge(std::string src, std::string snk, const jlm::rvsdg::Type & type, bool back)
167 {
168  auto color = "black";
169  JLM_ASSERT(src != "" && snk != "");
170  if (dynamic_cast<const rvsdg::ControlType *>(&type))
171  {
172  color = "green";
173  }
174  else if (dynamic_cast<const llvm::MemoryStateType *>(&type))
175  {
176  color = "blue";
177  }
178  if (!back)
179  {
180  return src + " -> " + snk + " [style=\"\", arrowhead=\"normal\", color=" + color
181  + ", headlabel=<>, fontsize=10, labelangle=45, labeldistance=2.0, "
182  "labelfontcolor=black, penwidth=3];\n";
183  }
184  // implement back edges by setting constraint to false
185  return src + " -> " + snk + " [style=\"\", arrowhead=\"normal\", color=" + color
186  + ", headlabel=<>, fontsize=10, labelangle=45, labeldistance=2.0, labelfontcolor=black, "
187  "penwidth=3, constraint=false];\n";
188  // return snk + " -> " + src + " [style=\"\", arrowhead=\"normal\", color=" + color +
189  // ", headlabel=<>, fontsize=10, labelangle=45, labeldistance=2.0, labelfontcolor=black,
190  // dir=back];\n";
191 }
192 
193 std::string
195 {
196  auto sr = ln->subregion();
197  std::ostringstream dot;
198  dot << "subgraph cluster_loop_" << loop_ctr++ << " {\n";
199  dot << "color=\"#ff8080\"\n";
200 
201  std::set<jlm::rvsdg::Output *> back_outputs;
202  std::set<rvsdg::Node *> top_nodes; // no artificial top nodes for now
203  for (size_t i = 0; i < sr->narguments(); ++i)
204  {
205  auto arg = sr->argument(i);
206  // arguments without an input produce a cycle
207  if (arg->input() == nullptr)
208  {
209  back_outputs.insert(arg);
210  }
211  }
212 
213  for (auto node : rvsdg::TopDownTraverser(sr))
214  {
215  if (dynamic_cast<jlm::rvsdg::SimpleNode *>(node))
216  {
217  auto node_dot = node_to_dot(node);
218  if (top_nodes.count(node))
219  {
220  dot << "{rank=source; \n";
221  dot << node_dot;
222  dot << "}\n";
223  }
224  else
225  {
226  dot << node_dot;
227  }
228  }
229  else if (auto ln = dynamic_cast<LoopNode *>(node))
230  {
231  // need to prepare output here again, because inputs might not have been resolved yet, because
232  // nodes in outer loop were not yet processed.
234  dot << loop_to_dot(ln);
235  }
236  else
237  {
238  throw util::Error("Unimplemented op (unexpected structural node) : " + node->DebugString());
239  }
240  }
241 
242  // all loop muxes at one level
243  dot << "{rank=same ";
244  for (auto node : rvsdg::TopDownTraverser(sr))
245  {
246  auto mx = dynamic_cast<const MuxOperation *>(&node->GetOperation());
247  auto lc = dynamic_cast<const LoopConstantBufferOperation *>(&node->GetOperation());
248  if ((mx && !mx->discarding && mx->loop) || lc)
249  {
250  dot << get_node_name(node) << " ";
251  }
252  }
253  dot << "}\n";
254  // all loop branches at one level
255  dot << "{rank=same ";
256  for (auto node : rvsdg::TopDownTraverser(sr))
257  {
258  auto br = dynamic_cast<const BranchOperation *>(&node->GetOperation());
259  if (br && br->loop)
260  {
261  dot << get_node_name(node) << " ";
262  }
263  }
264  dot << "}\n";
265 
266  dot << "}\n";
267  // do edges outside in order not to pull other nodes into the cluster
268  for (auto node : rvsdg::TopDownTraverser(sr))
269  {
270  if (dynamic_cast<jlm::rvsdg::SimpleNode *>(node))
271  {
272  auto mx = dynamic_cast<const MuxOperation *>(&node->GetOperation());
273  auto node_name = get_node_name(node);
274  for (size_t i = 0; i < node->ninputs(); ++i)
275  {
276  auto in_name = node_name + ":" + get_port_name(node->input(i));
277  JLM_ASSERT(output_map.count(node->input(i)->origin()));
278  auto origin = output_map[node->input(i)->origin()];
279  // implement edge as back edge when it produces a cycle
280  bool back = mx && !mx->discarding && mx->loop
281  && (/*i==0||*/ i == 2); // back_outputs.count(node->input(i)->origin());
282  auto origin_out_node = rvsdg::TryGetOwnerNode<rvsdg::SimpleNode>(*node->input(i)->origin());
283  if (origin_out_node
284  && dynamic_cast<const PredicateBufferOperation *>(&origin_out_node->GetOperation()))
285  {
286  //
287  back = true;
288  }
289  dot << edge(origin, in_name, *node->input(i)->Type(), back);
290  }
291  }
292  }
293  return dot.str();
294 }
295 
296 void
298 {
299  // make sure all outputs are translated and available (necessary for argument/result cycles)
300 
301  auto sr = ln->subregion();
302  // just translate outputs
303  for (auto node : rvsdg::TopDownTraverser(sr))
304  {
305  if (dynamic_cast<jlm::rvsdg::SimpleNode *>(node))
306  {
307  auto node_name = get_node_name(node);
308  for (size_t i = 0; i < node->noutputs(); ++i)
309  {
310  output_map[node->output(i)] = node_name + ":" + get_port_name(node->output(i));
311  }
312  }
313  else if (auto oln = dynamic_cast<LoopNode *>(node))
314  {
316  }
317  else
318  {
319  throw util::Error("Unimplemented op (unexpected structural node) : " + node->DebugString());
320  }
321  }
322  for (size_t i = 0; i < sr->narguments(); ++i)
323  {
324  auto arg = sr->argument(i);
325  auto ba = dynamic_cast<BackEdgeArgument *>(arg);
326  if (!ba)
327  {
328  JLM_ASSERT(arg->input() != nullptr);
329  // map to input of loop
330  output_map[arg] = output_map[arg->input()->origin()];
331  }
332  else
333  {
334  auto result = ba->result();
335  JLM_ASSERT(*result->Type() == *arg->Type());
336  // map to end of loop (origin of associated result)
337  output_map[arg] = output_map[result->origin()];
338  }
339  }
340  for (size_t i = 0; i < ln->noutputs(); ++i)
341  {
342  auto out = ln->output(i);
343  JLM_ASSERT(out->results.size() == 1);
344  output_map[out] = output_map[out->results.begin()->origin()];
345  }
346 }
347 
348 std::string
350 {
351  std::ostringstream dot;
352  dot << "digraph G {\n";
353  for (size_t i = 0; i < sr->narguments(); ++i)
354  {
355  dot << argument_to_dot(sr->argument(i));
356  }
357  // order arguments horizontally
358  dot << "{rank=source; ";
359  for (size_t i = 0; i < sr->narguments(); ++i)
360  {
361  if (i > 0)
362  {
363  dot << " -> ";
364  }
365  dot << get_port_name(sr->argument(i));
366  }
367  dot << "[style = invis]}";
368 
369  for (size_t i = 0; i < sr->nresults(); ++i)
370  {
371  dot << result_to_dot(sr->result(i));
372  }
373  dot << "subgraph cluster_sub {\n";
374  dot << "color=\"#80b3ff\"\n";
375  dot << "penwidth=6\n";
376 
377  // process arguments
378  for (size_t i = 0; i < sr->narguments(); ++i)
379  {
380  output_map[sr->argument(i)] = get_port_name(sr->argument(i));
381  }
382  // process nodes
383  for (auto node : rvsdg::TopDownTraverser(sr))
384  {
385  if (dynamic_cast<jlm::rvsdg::SimpleNode *>(node))
386  {
387  auto node_dot = node_to_dot(node);
388  dot << node_dot;
389  auto node_name = get_node_name(node);
390  for (size_t i = 0; i < node->ninputs(); ++i)
391  {
392  auto in_name = node_name + ":" + get_port_name(node->input(i));
393  JLM_ASSERT(output_map.count(node->input(i)->origin()));
394  auto origin = output_map[node->input(i)->origin()];
395  dot << edge(origin, in_name, *node->input(i)->Type());
396  }
397  for (size_t i = 0; i < node->noutputs(); ++i)
398  {
399  output_map[node->output(i)] = node_name + ":" + get_port_name(node->output(i));
400  }
401  }
402  else if (auto ln = dynamic_cast<LoopNode *>(node))
403  {
404  // the only structural nodes left are loop nodes
406  dot << loop_to_dot(ln);
407  }
408  else
409  {
410  throw util::Error("Unimplemented op (unexpected structural node) : " + node->DebugString());
411  }
412  }
413  // process results
414  for (size_t i = 0; i < sr->nresults(); ++i)
415  {
416  auto origin = output_map[sr->result(i)->origin()];
417  auto result = get_port_name(sr->result(i));
418  dot << edge(origin, result, *sr->result(i)->Type());
419  }
420  dot << "}\n";
421  dot << "}\n";
422  return dot.str();
423 }
424 
425 }
static std::string get_port_name(jlm::rvsdg::Input *port)
Definition: base-hls.cpp:62
std::unordered_map< jlm::rvsdg::Output *, std::string > output_map
Definition: base-hls.hpp:45
const rvsdg::LambdaNode * get_hls_lambda(llvm::LlvmRvsdgModule &rm)
Definition: base-hls.cpp:136
std::string get_node_name(const rvsdg::Node *node)
Definition: base-hls.cpp:28
std::string edge(std::string src, std::string snk, const jlm::rvsdg::Type &type, bool back=false)
Definition: dot-hls.cpp:166
std::string loop_to_dot(LoopNode *ln)
Definition: dot-hls.cpp:194
void prepare_loop_out_port(LoopNode *ln)
Definition: dot-hls.cpp:297
std::string result_to_dot(rvsdg::RegionResult *port)
Definition: dot-hls.cpp:48
std::string node_to_dot(const rvsdg::Node *node)
Definition: dot-hls.cpp:67
std::string argument_to_dot(rvsdg::RegionArgument *port)
Definition: dot-hls.cpp:29
std::string subregion_to_dot(rvsdg::Region *sr)
Definition: dot-hls.cpp:349
std::string GetText(llvm::LlvmRvsdgModule &rm) override
Definition: dot-hls.cpp:23
std::string extension() override
Definition: dot-hls.cpp:17
rvsdg::Region * subregion() const noexcept
Definition: hls.hpp:725
Memory state type class.
Definition: types.hpp:466
Output * origin() const noexcept
Definition: node.hpp:58
const std::shared_ptr< const rvsdg::Type > & Type() const noexcept
Definition: node.hpp:67
virtual std::string DebugString() const =0
NodeInput * input(size_t index) const noexcept
Definition: node.hpp:615
NodeOutput * output(size_t index) const noexcept
Definition: node.hpp:650
size_t ninputs() const noexcept
Definition: node.hpp:609
size_t noutputs() const noexcept
Definition: node.hpp:644
Represents the argument of a region.
Definition: region.hpp:41
Represents the result of a region.
Definition: region.hpp:120
Represent acyclic RVSDG subgraphs.
Definition: region.hpp:213
RegionResult * result(size_t index) const noexcept
Definition: region.hpp:471
size_t nresults() const noexcept
Definition: region.hpp:465
RegionArgument * argument(size_t index) const noexcept
Definition: region.hpp:437
size_t narguments() const noexcept
Definition: region.hpp:431
StructuralOutput * output(size_t index) const noexcept
#define JLM_ASSERT(x)
Definition: common.hpp:16
static bool is_constant(const rvsdg::Node *node)
Definition: rvsdg2rhls.hpp:20
bool isForbiddenChar(char c)
Definition: base-hls.cpp:16
static std::string type(const Node *n)
Definition: view.cpp:255
static std::vector< jlm::rvsdg::Output * > outputs(const Node *node)
Definition: node.hpp:1058