Jlm
GraphWriter.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2024 HÃ¥vard Krogstie <krogstie.havard@gmail.com>
3  * See COPYING for terms of redistribution.
4  */
5 
7 
8 #include <jlm/util/strfmt.hpp>
9 
10 namespace jlm::util::graph
11 {
12 // All GraphElements with an associated ProgramObject get this attribute added
13 static const char * const TOOLTIP_ATTRIBUTE = "tooltip";
14 // Edges are not named in dot, so use an attribute to assign id instead
15 static const char * const EDGE_ID_ATTRIBUTE = "id";
16 
22 static bool
23 LooksLikeIdentifier(std::string_view string)
24 {
25  if (string.empty())
26  return false;
27 
28  // We avoid C's isalpha, as it is locale dependent
29  auto isAlpha = [](char c)
30  {
31  return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
32  };
33  auto isDigit = [](char c)
34  {
35  return (c >= '0' && c <= '9');
36  };
37 
38  char firstChar = string[0];
39  if (!isAlpha(firstChar) && firstChar != '_')
40  return false;
41 
42  for (char c : string)
43  if (!isAlpha(c) && !isDigit(c) && c != '_')
44  return false;
45 
46  return true;
47 }
48 
53 static void
54 PrintIdentifierSafe(std::ostream & out, std::string_view string)
55 {
56  bool quoted = !LooksLikeIdentifier(string);
57 
58  if (quoted)
59  out << '"';
60  for (char c : string)
61  {
62  if (c == '"')
63  out << "\\\"";
64  else if (c == '\n')
65  out << "\\n";
66  else if (c == '\r')
67  out << "\\r";
68  else if (c == '\t')
69  out << "\\t";
70  else if (c < ' ' || c >= 127)
71  {
72  // Print all other special chars as \x escaped hex.
73  char tmpStr[3];
74  snprintf(tmpStr, sizeof(tmpStr), "%02X", c);
75  out << "\\x" << tmpStr;
76  }
77  else
78  out << c;
79  }
80  if (quoted)
81  out << '"';
82 }
83 
89 static void
90 PrintStringAsHtmlText(std::ostream & out, std::string_view string, bool replaceNewlines)
91 {
92  for (char c : string)
93  {
94  if (c == '&')
95  out << "&amp;";
96  else if (c == '"')
97  out << "&quot;";
98  else if (c == '<')
99  out << "&lt;";
100  else if (c == '>')
101  out << "&gt;";
102  else if (c == '\n' && replaceNewlines)
103  out << "<BR/>";
104  else
105  out << c;
106  }
107 }
108 
113 static void
114 PrintStringAsHtmlAttributeName(std::ostream & out, std::string_view string)
115 {
116  for (char c : string)
117  {
118  if (c <= ' ' || c >= 127 || c == '<' || c == '>' || c == '"' || c == '\'' || c == '/'
119  || c == '=')
120  out << '-';
121  else
122  out << c;
123  }
124 }
125 
132 [[nodiscard]] static const char *
133 Indent(size_t indent)
134 {
135  static constexpr size_t SPACE_PER_INDENT = 2;
136  static constexpr size_t MAX_SPACES = 128;
137  static thread_local char indentation[MAX_SPACES + 1];
138 
139  size_t spaces = indent * SPACE_PER_INDENT;
140  if (spaces > MAX_SPACES)
141  spaces = MAX_SPACES;
142 
143  for (size_t i = 0; i < spaces; i++)
144  indentation[i] = ' ';
145  indentation[spaces] = '\0';
146  return indentation;
147 }
148 
150  : Label_(),
151  UniqueIdSuffix_(std::nullopt),
152  ProgramObject_(0),
153  AttributeMap_()
154 {}
155 
156 std::string
158 {
160  std::ostringstream ss;
161  ss << GetIdPrefix() << GetUniqueIdSuffix();
162  return ss.str();
163 }
164 
165 const Graph &
167 {
168  return const_cast<GraphElement *>(this)->GetGraph();
169 }
170 
171 void
172 GraphElement::SetLabel(std::string label)
173 {
174  Label_ = std::move(label);
175 }
176 
177 void
178 GraphElement::AppendToLabel(std::string_view text, std::string_view sep)
179 {
180  if (HasLabel())
181  {
182  Label_.append(sep).append(text);
183  }
184  else
185  {
186  Label_ = text;
187  }
188 }
189 
190 bool
192 {
193  return !Label_.empty();
194 }
195 
196 const std::string &
198 {
199  return Label_;
200 }
201 
202 std::string_view
203 GraphElement::GetLabelOr(std::string_view otherwise) const
204 {
205  if (HasLabel())
206  return Label_;
207  return otherwise;
208 }
209 
210 size_t
212 {
214  return UniqueIdSuffix_.value();
215 }
216 
217 void
219 {
220  JLM_ASSERT(object);
221  if (ProgramObject_ != 0)
223  ProgramObject_ = object;
224  if (ProgramObject_ != 0)
226 }
227 
228 void
230 {
232 }
233 
234 bool
236 {
237  return ProgramObject_ != 0;
238 }
239 
240 uintptr_t
242 {
243  return ProgramObject_;
244 }
245 
246 void
247 GraphElement::SetAttribute(const std::string & attribute, std::string value)
248 {
249  AttributeMap_[attribute] = std::move(value);
250 }
251 
252 void
253 GraphElement::SetAttributeObject(const std::string & attribute, uintptr_t object)
254 {
255  JLM_ASSERT(object);
256  AttributeMap_[attribute] = object;
257 }
258 
259 void
260 GraphElement::SetAttributeGraphElement(const std::string & attribute, const GraphElement & element)
261 {
262  JLM_ASSERT(&GetGraph().GetWriter() == &element.GetGraph().GetWriter());
263  AttributeMap_[attribute] = &element;
264 }
265 
266 bool
267 GraphElement::HasAttribute(const std::string & attribute) const
268 {
269  return AttributeMap_.find(attribute) != AttributeMap_.end();
270 }
271 
272 std::optional<std::string_view>
273 GraphElement::GetAttributeString(const std::string & attribute) const
274 {
275  if (auto it = AttributeMap_.find(attribute); it != AttributeMap_.end())
276  {
277  if (auto stringValue = std::get_if<std::string>(&it->second))
278  {
279  return *stringValue;
280  }
281  }
282  return std::nullopt;
283 }
284 
285 std::optional<uintptr_t>
286 GraphElement::GetAttributeObject(const std::string & attribute) const
287 {
288  if (auto it = AttributeMap_.find(attribute); it != AttributeMap_.end())
289  {
290  if (auto uintptrValue = std::get_if<uintptr_t>(&it->second))
291  {
292  return *uintptrValue;
293  }
294  }
295  return std::nullopt;
296 }
297 
298 const GraphElement *
299 GraphElement::GetAttributeGraphElement(const std::string & attribute) const
300 {
301  if (auto it = AttributeMap_.find(attribute); it != AttributeMap_.end())
302  {
303  if (auto graphElementValue = std::get_if<const GraphElement *>(&it->second))
304  {
305  return *graphElementValue;
306  }
307 
308  // Otherwise, check if this attribute holds a program object that is represented by a
309  // GraphElement in this graph, or in any graph in the GraphWriter.
310  if (auto ptr = std::get_if<uintptr_t>(&it->second))
311  {
312  if (auto gElement = GetGraph().GetElementFromProgramObject(*ptr))
313  {
314  return gElement;
315  }
316  if (auto gwElement = GetGraph().GetWriter().GetElementFromProgramObject(*ptr))
317  {
318  return gwElement;
319  }
320  }
321  }
322  return nullptr;
323 }
324 
325 bool
326 GraphElement::RemoveAttribute(const std::string & attribute)
327 {
328  return AttributeMap_.erase(attribute);
329 }
330 
331 void
333 {
334  if (IsFinalized())
335  return;
336 
337  auto & writer = GetGraph().GetWriter();
338  UniqueIdSuffix_ = writer.GetNextUniqueIdStubSuffix(GetIdPrefix());
339 }
340 
341 bool
343 {
344  return UniqueIdSuffix_.has_value();
345 }
346 
347 void
348 GraphElement::OutputAttributes(std::ostream & out, AttributeOutputFormat format) const
349 {
350  auto FormatAttribute = [&](std::string_view name, std::string_view value)
351  {
353  {
354  PrintIdentifierSafe(out, name);
355  out << "=";
356  PrintIdentifierSafe(out, value);
357  }
358  else if (format == AttributeOutputFormat::HTMLAttributes)
359  {
361  out << "=\""; // HTML attributes must be quoted
362  PrintStringAsHtmlText(out, value, false);
363  out << '"'; // Closing quote
364  }
365  else
366  {
367  JLM_UNREACHABLE("Unknown AttributeOutputFormat");
368  }
369 
370  out << " "; // Attributes are space separated in both formats
371  };
372 
373  auto OutputAttribute = [&](const std::string & name)
374  {
375  if (auto string = GetAttributeString(name))
376  {
377  FormatAttribute(name, *string);
378  }
379  else if (auto graphElement = GetAttributeGraphElement(name))
380  {
381  FormatAttribute(name, graphElement->GetFullId());
382  }
383  else if (auto object = GetAttributeObject(name))
384  {
385  FormatAttribute(name, strfmt("ptr", std::hex, *object));
386  }
387  else
388  {
389  JLM_UNREACHABLE("Unknown attribute type");
390  }
391  };
392 
393  for (const auto & [name, _] : AttributeMap_)
394  {
395  OutputAttribute(name);
396  }
397 
398  // If no other tooltip is set, print the address of the associated program object
400  FormatAttribute(TOOLTIP_ATTRIBUTE, strfmt(std::hex, GetProgramObject()));
401 }
402 
404  : GraphElement()
405 {}
406 
407 Graph &
409 {
410  return GetNode().GetGraph();
411 }
412 
413 bool
415 {
416  return true;
417 }
418 
419 bool
421 {
422  return true;
423 }
424 
425 const std::vector<Edge *> &
427 {
428  return Connections_;
429 }
430 
431 void
433 {
434  if (this == &edge.GetFrom())
435  JLM_ASSERT(CanBeEdgeTail() || !edge.IsDirected());
436  else if (this == &edge.GetTo())
437  JLM_ASSERT(CanBeEdgeHead() || !edge.IsDirected());
438  else
439  JLM_UNREACHABLE("Port was informed about unrelated edge");
440 
441  Connections_.push_back(&edge);
442 }
443 
444 bool
446 {
447  for (auto & edge : Connections_)
448  {
449  if (&edge->GetFrom() == this || !edge->IsDirected())
450  return true;
451  }
452  return false;
453 }
454 
455 bool
457 {
458  for (auto & edge : Connections_)
459  {
460  if (&edge->GetTo() == this || !edge->IsDirected())
461  return true;
462  }
463  return false;
464 }
465 
466 void
467 Port::OutputIncomingEdgesASCII(std::ostream & out) const
468 {
469  std::ostringstream text;
470  size_t numIncomingEdges = 0;
471 
472  for (auto & edge : Connections_)
473  {
474  if (&edge->GetTo() != this && edge->IsDirected())
475  continue;
476 
477  Port & otherEnd = edge->GetOtherEnd(*this);
478  if (numIncomingEdges == 0)
479  text << otherEnd.GetFullId();
480  else
481  text << ", " << otherEnd.GetFullId();
482 
483  numIncomingEdges++;
484  }
485 
486  if (numIncomingEdges == 1)
487  out << text.str();
488  else
489  out << "[" << text.str() << "]";
490 }
491 
493  : Port(),
494  Graph_(graph)
495 {}
496 
497 const char *
499 {
500  return "node";
501 }
502 
503 Node &
505 {
506  return *this;
507 }
508 
509 Graph &
511 {
512  return Graph_;
513 }
514 
515 void
516 Node::SetShape(std::string shape)
517 {
518  SetAttribute("shape", std::move(shape));
519 }
520 
521 void
522 Node::SetFillColor(std::string color)
523 {
524  // The dot output gives all nodes style=filled by default, so we only need to set the color
525  SetAttribute("fillcolor", std::move(color));
526 }
527 
528 void
529 Node::OutputDotPortId(std::ostream & out) const
530 {
531  out << GetFullId();
532 }
533 
534 void
535 Node::Output(std::ostream & out, OutputFormat format, size_t indent) const
536 {
537  switch (format)
538  {
539  case OutputFormat::ASCII:
540  OutputASCII(out, indent);
541  break;
542  case OutputFormat::Dot:
543  OutputDot(out, indent);
544  break;
545  default:
546  JLM_UNREACHABLE("Unknown graph::OutputFormat");
547  }
548 }
549 
550 void
551 Node::OutputASCII(std::ostream & out, size_t indent) const
552 {
553  out << Indent(indent);
554  if (HasOutgoingEdges())
555  {
556  out << GetFullId() << ":";
557  }
558  PrintIdentifierSafe(out, GetLabelOr("NODE"));
559  if (HasIncomingEdges())
560  {
561  out << "<-";
563  }
564  out << std::endl;
565 }
566 
567 void
568 Node::OutputDot(std::ostream & out, size_t indent) const
569 {
570  out << Indent(indent) << GetFullId() << " [";
571  out << "label=";
572  if (HasLabel())
573  {
575  }
576  else
577  {
579  }
580  out << " ";
582  out << "];" << std::endl;
583 }
584 
585 void
586 Node::OutputSubgraphs(std::ostream & out, OutputFormat format, size_t indent) const
587 {
588  // Regular nodes do not have sub graphs
589 }
590 
592  : Node_(node)
593 {
595 }
596 
597 const char *
599 {
600  return "in";
601 }
602 
603 Node &
605 {
606  return Node_;
607 }
608 
609 bool
611 {
612  return false;
613 }
614 
615 void
616 InputPort::SetFillColor(std::string color)
617 {
618  // Attribute on the <TD> tag used by the dot output
619  SetAttribute("BGCOLOR", std::move(color));
620 }
621 
622 void
623 InputPort::OutputDotPortId(std::ostream & out) const
624 {
625  out << Node_.GetFullId() << ":" << GetFullId() << ":n";
626 }
627 
629  : Node_(node)
630 {
632 }
633 
634 const char *
636 {
637  return "out";
638 }
639 
640 Node &
642 {
643  return Node_;
644 }
645 
646 bool
648 {
649  return false;
650 }
651 
652 void
653 OutputPort::SetFillColor(std::string color)
654 {
655  // Attribute on the <TD> tag used by the dot output
656  SetAttribute("BGCOLOR", std::move(color));
657 }
658 
659 void
660 OutputPort::OutputDotPortId(std::ostream & out) const
661 {
662  out << Node_.GetFullId() << ":" << GetFullId() << ":s";
663 }
664 
665 InOutNode::InOutNode(Graph & graph, size_t inputPorts, size_t outputPorts)
666  : Node(graph)
667 {
668  for (size_t i = 0; i < inputPorts; i++)
669  CreateInputPort();
670 
671  for (size_t i = 0; i < outputPorts; i++)
673 
675 }
676 
677 void
679 {
680  throw Error("InOutNodes can not have custom shapes set");
681 }
682 
683 InputPort &
685 {
686  auto inputPort = new InputPort(*this);
687  InputPorts_.emplace_back(inputPort);
688  return *inputPort;
689 }
690 
691 size_t
693 {
694  return InputPorts_.size();
695 }
696 
697 InputPort &
699 {
700  JLM_ASSERT(index < InputPorts_.size());
701  return *InputPorts_[index];
702 }
703 
704 OutputPort &
706 {
707  auto outputPort = new OutputPort(*this);
708  OutputPorts_.emplace_back(outputPort);
709  return *outputPort;
710 }
711 
712 size_t
714 {
715  return OutputPorts_.size();
716 }
717 
718 OutputPort &
720 {
721  JLM_ASSERT(index < OutputPorts_.size());
722  return *OutputPorts_[index];
723 }
724 
725 Graph &
727 {
728  auto & graph = GetGraph().GetWriter().CreateSubGraph(*this);
729  SubGraphs_.push_back(&graph);
730  return graph;
731 }
732 
733 size_t
735 {
736  return SubGraphs_.size();
737 }
738 
739 Graph &
741 {
742  JLM_ASSERT(index < SubGraphs_.size());
743  return *SubGraphs_[index];
744 }
745 
746 void
747 InOutNode::SetHtmlTableAttribute(std::string name, std::string value)
748 {
749  HtmlTableAttributes_[name] = std::move(value);
750 }
751 
752 void
753 InOutNode::SetFillColor(std::string color)
754 {
755  SetHtmlTableAttribute("BGCOLOR", std::move(color));
756 }
757 
758 void
760 {
761  Node::Finalize();
762 
763  for (auto & inputPort : InputPorts_)
764  inputPort->Finalize();
765  for (auto & outputPort : OutputPorts_)
766  outputPort->Finalize();
767  for (auto & graph : SubGraphs_)
768  graph->Finalize();
769 }
770 
771 void
772 InOutNode::OutputSubgraphs(std::ostream & out, OutputFormat format, size_t indent) const
773 {
774  for (auto & graph : SubGraphs_)
775  graph->Output(out, format, indent);
776 }
777 
778 void
779 InOutNode::OutputASCII(std::ostream & out, size_t indent) const
780 {
781  out << Indent(indent);
782 
783  // output the names of all output ports
784  for (size_t i = 0; i < NumOutputPorts(); i++)
785  {
786  if (i != 0)
787  out << ", ";
788  out << OutputPorts_[i]->GetFullId();
789  }
790  if (NumOutputPorts() != 0)
791  out << " := ";
792 
793  // If the node itself is used as a tail port, we must include its name
795  {
796  out << GetFullId() << ":";
797  }
798  PrintIdentifierSafe(out, GetLabelOr("NODE"));
800  {
801  out << "<-";
803  }
804  out << " ";
805 
806  // Now output the origins of all input ports
807  for (size_t i = 0; i < NumInputPorts(); i++)
808  {
809  if (i != 0)
810  out << ", ";
811  InputPorts_[i]->OutputIncomingEdgesASCII(out);
812  }
813  out << std::endl;
814 
815  // Output all sub graphs, if we have any
817 }
818 
819 void
820 InOutNode::OutputDot(std::ostream & out, size_t indent) const
821 {
822  out << Indent(indent) << GetFullId() << " [shape=plain style=solid ";
823  out << "label=<" << std::endl;
824 
825  // InOutNodes are printed as html tables
826  out << "<TABLE BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\">" << std::endl;
827 
828  // Used to create rows of boxes above and below the node
829  auto PrintPortList = [&out](auto & ports)
830  {
831  if (ports.empty())
832  return;
833 
834  out << "\t<TR><TD>" << std::endl;
835  out << "\t\t<TABLE BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"0\"><TR>" << std::endl;
836  out << "\t\t\t<TD WIDTH=\"20\"></TD>" << std::endl;
837  for (size_t i = 0; i < ports.size(); i++)
838  {
839  // Spacing
840  if (i != 0)
841  out << "\t\t\t<TD WIDTH=\"10\"></TD>" << std::endl;
842 
843  auto & port = *ports[i];
844  out << "\t\t\t<TD BORDER=\"1\" CELLPADDING=\"1\" ";
845  out << "PORT=\"" << port.GetFullId() << "\" ";
846  port.OutputAttributes(out, AttributeOutputFormat::HTMLAttributes);
847  if (port.HasLabel())
848  {
849  out << "><FONT POINT-SIZE=\"10\">";
850  PrintStringAsHtmlText(out, port.GetLabel(), true);
851  out << "</FONT>";
852  }
853  else
854  {
855  // ports without labels have a fixed size
856  out << " WIDTH=\"8\" HEIGHT=\"5\" FIXEDSIZE=\"true\">";
857  }
858  out << "</TD>" << std::endl;
859  }
860  out << "\t\t\t<TD WIDTH=\"20\"></TD>" << std::endl;
861  out << "\t\t</TR></TABLE>" << std::endl;
862  out << "\t</TD></TR>" << std::endl;
863  };
864 
865  // Inputs
866  PrintPortList(InputPorts_);
867 
868  // The main body of the node: a rounded rectangle
869  out << "\t<TR><TD>" << std::endl;
870  out << "\t\t<TABLE BORDER=\"1\" STYLE=\"ROUNDED\" CELLBORDER=\"0\" ";
871  out << "CELLSPACING=\"0\" CELLPADDING=\"0\" ";
872  for (auto & [name, value] : HtmlTableAttributes_)
873  {
875  out << "=\"";
876  PrintStringAsHtmlText(out, value, false);
877  out << "\" ";
878  }
879  out << ">" << std::endl;
880  out << "\t\t\t<TR><TD CELLPADDING=\"1\">";
882  out << "</TD></TR>" << std::endl;
883 
884  // Subgraphs
885  if (!SubGraphs_.empty())
886  {
887  out << "\t\t\t<TR><TD>" << std::endl;
888  out << "\t\t\t\t<TABLE BORDER=\"0\" CELLSPACING=\"4\" CELLPADDING=\"2\"><TR>" << std::endl;
889  for (auto & graph : SubGraphs_)
890  {
891  out << "\t\t\t\t\t<TD BORDER=\"1\" STYLE=\"ROUNDED\" WIDTH=\"40\" BGCOLOR=\"white\" ";
892  out << "_SUBGRAPH=\"" << graph->GetFullId() << "\">";
893  PrintStringAsHtmlText(out, graph->GetFullId(), true);
894  out << "</TD>" << std::endl;
895  }
896  out << "\t\t\t\t</TR></TABLE>" << std::endl;
897  out << "\t\t\t</TD></TR>" << std::endl;
898  }
899 
900  // End of the rounded rectangle
901  out << "\t\t</TABLE>" << std::endl;
902  out << "\t</TD></TR>" << std::endl;
903 
904  PrintPortList(OutputPorts_);
905 
906  out << "</TABLE>" << std::endl;
907  out << Indent(indent) << "> ";
909  out << "];" << std::endl;
910 }
911 
913  : Node(graph),
914  OutsideSource_(nullptr)
915 {}
916 
917 const char *
919 {
920  return "arg";
921 }
922 
923 bool
925 {
926  return false;
927 }
928 
929 void
930 ArgumentNode::SetOutsideSource(const Port & outsideSource)
931 {
932  OutsideSource_ = &outsideSource;
933  SetAttributeGraphElement("outsideSource", outsideSource);
934 }
935 
936 void
937 ArgumentNode::OutputASCII(std::ostream & out, size_t) const
938 {
939  // In ASCII the argument is printed as part of an ARG line
940  out << GetFullId();
941  if (HasLabel())
942  {
943  out << ":";
945  }
946  if (OutsideSource_ != nullptr)
947  {
948  out << " <= ";
950  }
951 }
952 
954  : Node(graph),
955  OutsideDestination_(nullptr)
956 {}
957 
958 const char *
960 {
961  return "res";
962 }
963 
964 bool
966 {
967  return false;
968 }
969 
970 void
971 ResultNode::SetOutsideDestination(const Port & outsideDestination)
972 {
973  OutsideDestination_ = &outsideDestination;
974  SetAttributeGraphElement("outsideDest", outsideDestination);
975 }
976 
977 void
978 ResultNode::OutputASCII(std::ostream & out, size_t) const
979 {
980  // In ASCII the result is printed as part of an RES line
982  if (HasLabel())
983  {
984  out << ":";
986  }
987  if (OutsideDestination_ != nullptr)
988  out << " => " << OutsideDestination_->GetFullId();
989 }
990 
991 Edge::Edge(Port & from, Port & to, bool directed)
992  : From_(from),
993  To_(to),
994  Directed_(directed)
995 {
996  from.OnEdgeAdded(*this);
997  to.OnEdgeAdded(*this);
998 }
999 
1000 const char *
1002 {
1003  return "edge";
1004 }
1005 
1006 Graph &
1008 {
1009  // from and to have the same graph, return either
1010  return From_.GetGraph();
1011 }
1012 
1013 Port &
1015 {
1016  return From_;
1017 }
1018 
1019 Port &
1021 {
1022  return To_;
1023 }
1024 
1025 bool
1027 {
1028  return Directed_;
1029 }
1030 
1031 Port &
1033 {
1034  if (&end == &From_)
1035  return To_;
1036  else if (&end == &To_)
1037  return From_;
1038 
1039  JLM_UNREACHABLE("GetOtherEnd called with neither end");
1040 }
1041 
1042 void
1043 Edge::SetStyle(std::string style)
1044 {
1045  SetAttribute("style", std::move(style));
1046 }
1047 
1048 void
1049 Edge::SetArrowHead(std::string arrow)
1050 {
1051  SetAttribute("arrowhead", std::move(arrow));
1052 }
1053 
1054 void
1055 Edge::SetArrowTail(std::string arrow)
1056 {
1057  // When outputting dot, the "dir" attribute will be automatically changed to make the tail visible
1058  SetAttribute("arrowtail", std::move(arrow));
1059 }
1060 
1061 void
1062 Edge::OutputDot(std::ostream & out, size_t indent) const
1063 {
1064  out << Indent(indent);
1065  From_.OutputDotPortId(out);
1066  out << " -> ";
1067  To_.OutputDotPortId(out);
1068  out << "[";
1069 
1070  const bool hasHeadArrow = HasAttribute("arrowhead") || Directed_;
1071  const bool hasTailArrow = HasAttribute("arrowtail");
1072  if (hasHeadArrow && hasTailArrow)
1073  out << "dir=both ";
1074  else if (hasHeadArrow)
1075  {
1076  // dir=forward is the default in digraphs
1077  }
1078  else if (hasTailArrow)
1079  out << "dir=back ";
1080  else
1081  out << "dir=none ";
1082 
1083  if (HasLabel())
1084  {
1085  out << "label=";
1086  PrintIdentifierSafe(out, GetLabel());
1087  out << " ";
1088  }
1089 
1090  // Edges are not normally named, so use the id attribute to include the edge's id
1092  {
1093  out << EDGE_ID_ATTRIBUTE << "=";
1095  out << " ";
1096  }
1097 
1099  out << "];" << std::endl;
1100 }
1101 
1103  : GraphElement(),
1104  Writer_(writer),
1105  ParentNode_(nullptr)
1106 {}
1107 
1108 Graph::Graph(Writer & writer, Node & parentNode)
1109  : GraphElement(),
1110  Writer_(writer),
1111  ParentNode_(&parentNode)
1112 {}
1113 
1114 const char *
1116 {
1117  return "graph";
1118 }
1119 
1120 Graph &
1122 {
1123  return *this;
1124 }
1125 
1126 Writer &
1128 {
1129  return Writer_;
1130 }
1131 
1132 const Writer &
1134 {
1135  return Writer_;
1136 }
1137 
1138 bool
1140 {
1141  return ParentNode_ != nullptr;
1142 }
1143 
1144 Node &
1146 {
1147  auto node = new Node(*this);
1148  Nodes_.emplace_back(node);
1149  return *node;
1150 }
1151 
1152 InOutNode &
1153 Graph::CreateInOutNode(size_t inputPorts, size_t outputPorts)
1154 {
1155  auto node = new InOutNode(*this, inputPorts, outputPorts);
1156  Nodes_.emplace_back(node);
1157  return *node;
1158 }
1159 
1160 size_t
1161 Graph::NumNodes() const noexcept
1162 {
1163  return Nodes_.size();
1164 }
1165 
1166 Node &
1167 Graph::GetNode(size_t index)
1168 {
1169  JLM_ASSERT(index < NumNodes());
1170  return *Nodes_[index];
1171 }
1172 
1173 ArgumentNode &
1175 {
1176  auto node = new ArgumentNode(*this);
1177  ArgumentNodes_.emplace_back(node);
1178  return *node;
1179 }
1180 
1181 size_t
1182 Graph::NumArgumentNodes() const noexcept
1183 {
1184  return ArgumentNodes_.size();
1185 }
1186 
1187 Node &
1189 {
1190  JLM_ASSERT(index < NumArgumentNodes());
1191  return *ArgumentNodes_[index];
1192 }
1193 
1194 ResultNode &
1196 {
1197  auto node = new ResultNode(*this);
1198  ResultNodes_.emplace_back(node);
1199  return *node;
1200 }
1201 
1202 size_t
1203 Graph::NumResultNodes() const noexcept
1204 {
1205  return ResultNodes_.size();
1206 }
1207 
1208 Node &
1210 {
1211  JLM_ASSERT(index < NumResultNodes());
1212  return *ResultNodes_[index];
1213 }
1214 
1215 Edge &
1216 Graph::CreateEdge(Port & from, Port & to, bool directed)
1217 {
1218  // Edges must be between ports in this graph
1219  JLM_ASSERT(&from.GetGraph() == this);
1220  JLM_ASSERT(&to.GetGraph() == this);
1221 
1222  // Edge's constructor informs the ports about the edge
1223  auto edge = new Edge(from, to, directed);
1224  Edges_.emplace_back(edge);
1225  return *edge;
1226 }
1227 
1228 size_t
1229 Graph::NumEdges() const noexcept
1230 {
1231  return Edges_.size();
1232 }
1233 
1234 Edge &
1235 Graph::GetEdge(size_t index)
1236 {
1237  JLM_ASSERT(index < NumEdges());
1238  return *Edges_[index];
1239 }
1240 
1241 Edge *
1243 {
1244  for (auto edge : a.GetConnections())
1245  {
1246  if (edge->IsDirected() && &edge->GetFrom() != &a)
1247  continue;
1248  if (&edge->GetOtherEnd(a) == &b)
1249  return edge;
1250  }
1251  return nullptr;
1252 }
1253 
1254 GraphElement *
1255 Graph::GetElementFromProgramObject(uintptr_t object) const
1256 {
1257  if (auto it = ProgramObjectMapping_.find(object); it != ProgramObjectMapping_.end())
1258  return it->second;
1259  return nullptr;
1260 }
1261 
1262 void
1264 {
1265  JLM_ASSERT(&element.GetGraph() == this);
1266 
1267  uintptr_t object = element.GetProgramObject();
1268  JLM_ASSERT(object != 0);
1269 
1270  auto & slot = ProgramObjectMapping_[object];
1271  JLM_ASSERT(slot == nullptr && "Trying to map a GraphElement to an already mapped program object");
1272  slot = &element;
1273 }
1274 
1275 void
1277 {
1278  size_t erased = ProgramObjectMapping_.erase(object);
1279  JLM_ASSERT(erased == 1);
1280 }
1281 
1282 void
1284 {
1286 
1287  for (auto & arg : ArgumentNodes_)
1288  arg->Finalize();
1289  // Nodes with sub graphs also finalize them
1290  for (auto & node : Nodes_)
1291  node->Finalize();
1292  for (auto & res : ResultNodes_)
1293  res->Finalize();
1294  for (auto & edge : Edges_)
1295  edge->Finalize();
1296 }
1297 
1298 void
1299 Graph::OutputASCII(std::ostream & out, size_t indent) const
1300 {
1301  out << Indent(indent) << "{" << std::endl;
1302  indent++;
1303 
1304  // Use a single ARG line for all graph arguments
1305  bool anyArguments = false;
1306  for (auto & arg : ArgumentNodes_)
1307  {
1308  if (!anyArguments)
1309  out << Indent(indent) << "ARG ";
1310  else
1311  out << ", ";
1312  anyArguments = true;
1313  arg->OutputASCII(out, indent);
1314  }
1315  if (anyArguments)
1316  out << std::endl;
1317 
1318  // Print all other nodes in order
1319  for (auto & node : Nodes_)
1320  {
1321  // Will also print sub graphs recursively
1322  node->OutputASCII(out, indent);
1323  }
1324 
1325  // Use a single RES line for all graph results
1326  bool anyResults = false;
1327  for (auto & res : ResultNodes_)
1328  {
1329  if (!anyResults)
1330  out << Indent(indent) << "RES ";
1331  else
1332  out << ", ";
1333  anyResults = true;
1334  res->OutputASCII(out, indent);
1335  }
1336  if (anyResults)
1337  out << std::endl;
1338 
1339  indent--;
1340  out << Indent(indent) << "}" << std::endl;
1341 }
1342 
1343 void
1344 Graph::OutputDot(std::ostream & out, size_t indent) const
1345 {
1346  out << Indent(indent) << "digraph " << GetFullId() << " {" << std::endl;
1347  indent++;
1348 
1349  // Default node attributes. Filling nodes by default makes them easier to click
1350  out << Indent(indent)
1351  << "node[shape=box style=filled fillcolor=white width=0.1 height=0.1 margin=0.05];"
1352  << std::endl;
1353  out << Indent(indent) << "penwidth=6;" << std::endl;
1354  if (HasLabel())
1355  {
1356  out << Indent(indent) << "label=";
1357  PrintIdentifierSafe(out, GetLabel());
1358  out << std::endl;
1359  }
1360  out << Indent(indent);
1362  out << std::endl;
1363 
1364  // Helper function used to print argument nodes and result nodes
1365  auto PrintOrderedSubgraph = [&out](auto & nodes, const char * rank, size_t indent)
1366  {
1367  if (nodes.empty())
1368  return;
1369  out << Indent(indent++) << "{" << std::endl;
1370  out << Indent(indent) << "rank=" << rank << ";" << std::endl;
1371  for (size_t i = 0; i < nodes.size(); i++)
1372  {
1373  nodes[i]->OutputDot(out, indent);
1374 
1375  // Use invisible edges to order nodes in the subgraph
1376  if (i != 0)
1377  out << Indent(indent) << nodes[i - 1]->GetFullId() << " -> " << nodes[i]->GetFullId()
1378  << "[style=invis];" << std::endl;
1379  }
1380  out << Indent(--indent) << "}" << std::endl;
1381  };
1382 
1383  PrintOrderedSubgraph(ArgumentNodes_, "source", indent);
1384 
1385  for (auto & node : Nodes_)
1386  {
1387  node->OutputDot(out, indent);
1388  }
1389 
1390  PrintOrderedSubgraph(ResultNodes_, "sink", indent);
1391 
1392  for (auto & edge : Edges_)
1393  {
1394  edge->OutputDot(out, indent);
1395  }
1396 
1397  indent--;
1398  out << Indent(indent) << "}" << std::endl;
1399 
1400  // After fully printing this graph, print any sub graphs it may have
1401  for (auto & node : Nodes_)
1402  {
1403  node->OutputSubgraphs(out, OutputFormat::Dot, indent);
1404  }
1405 }
1406 
1407 void
1408 Graph::Output(std::ostream & out, OutputFormat format, size_t indent) const
1409 {
1411 
1412  switch (format)
1413  {
1414  case OutputFormat::ASCII:
1415  OutputASCII(out, indent);
1416  break;
1417  case OutputFormat::Dot:
1418  OutputDot(out, indent);
1419  break;
1420  default:
1421  JLM_UNREACHABLE("Unknown output format");
1422  }
1423 }
1424 
1425 Graph &
1427 {
1428  auto graph = new Graph(*this);
1429  Graphs_.emplace_back(graph);
1430  return *graph;
1431 }
1432 
1433 size_t
1434 Writer::NumGraphs() const noexcept
1435 {
1436  return Graphs_.size();
1437 }
1438 
1439 Graph &
1440 Writer::GetGraph(size_t index)
1441 {
1442  JLM_ASSERT(index < NumGraphs());
1443  return *Graphs_[index];
1444 }
1445 
1446 Graph &
1448 {
1449  auto graph = new Graph(*this, parentNode);
1450  Graphs_.emplace_back(graph);
1451  return *graph;
1452 }
1453 
1454 GraphElement *
1456 {
1457  for (auto & graph : Graphs_)
1458  if (auto found = graph->GetElementFromProgramObject(object))
1459  return found;
1460 
1461  return nullptr;
1462 }
1463 
1464 size_t
1466 {
1467  size_t & nextValue = NextUniqueIdStubSuffix_[idStub];
1468  return nextValue++;
1469 }
1470 
1471 void
1473 {
1474  for (auto & graph : Graphs_)
1475  if (!graph->IsSubgraph())
1476  graph->Finalize();
1477 }
1478 
1479 void
1480 Writer::outputAllGraphs(std::ostream & out, OutputFormat format)
1481 {
1482  Finalize();
1483 
1484  for (auto & graph : Graphs_)
1485  if (!graph->IsSubgraph())
1486  graph->Output(out, format);
1487 }
1488 
1489 }
bool CanBeEdgeHead() const override
void SetOutsideSource(const Port &outsideSource)
const char * GetIdPrefix() const override
void OutputASCII(std::ostream &out, size_t indent) const override
void SetStyle(std::string style)
void SetArrowTail(std::string arrow)
Port & GetOtherEnd(const Port &end)
const char * GetIdPrefix() const override
Graph & GetGraph() override
Edge(Port &from, Port &to, bool directed)
void OutputDot(std::ostream &out, size_t indent) const
void SetArrowHead(std::string arrow)
virtual Graph & GetGraph()=0
void SetLabel(std::string label)
uintptr_t GetProgramObject() const noexcept
std::string GetFullId() const
void SetAttributeObject(const std::string &attribute, uintptr_t object)
void AppendToLabel(std::string_view text, std::string_view sep="\n")
void SetAttribute(const std::string &attribute, std::string value)
virtual const char * GetIdPrefix() const =0
std::string_view GetLabelOr(std::string_view otherwise) const
const std::string & GetLabel() const
bool RemoveAttribute(const std::string &attribute)
std::optional< uintptr_t > GetAttributeObject(const std::string &attribute) const
std::unordered_map< std::string, AttributeValue > AttributeMap_
void OutputAttributes(std::ostream &out, AttributeOutputFormat format) const
bool HasAttribute(const std::string &attribute) const
const GraphElement * GetAttributeGraphElement(const std::string &attribute) const
std::optional< size_t > UniqueIdSuffix_
std::optional< std::string_view > GetAttributeString(const std::string &attribute) const
void SetProgramObjectUintptr(uintptr_t object)
void SetAttributeGraphElement(const std::string &attribute, const GraphElement &element)
bool HasProgramObject() const noexcept
std::vector< std::unique_ptr< ArgumentNode > > ArgumentNodes_
ArgumentNode & CreateArgumentNode()
Node & GetNode(size_t index)
GraphElement * GetElementFromProgramObject(uintptr_t object) const
std::unordered_map< uintptr_t, GraphElement * > ProgramObjectMapping_
Node & GetResultNode(size_t index)
Edge & GetEdge(size_t index)
Edge * GetEdgeBetween(Port &a, Port &b)
void OutputASCII(std::ostream &out, size_t indent) const
const char * GetIdPrefix() const override
void RemoveProgramObjectMapping(uintptr_t object)
std::vector< std::unique_ptr< Edge > > Edges_
Graph & GetGraph() override
ResultNode & CreateResultNode()
size_t NumResultNodes() const noexcept
void Output(std::ostream &out, OutputFormat format, size_t indent=0) const
std::vector< std::unique_ptr< ResultNode > > ResultNodes_
void Finalize() override
void OutputDot(std::ostream &out, size_t indent) const
size_t NumEdges() const noexcept
size_t NumNodes() const noexcept
Node & GetArgumentNode(size_t index)
InOutNode & CreateInOutNode(size_t inputPorts, size_t outputPorts)
size_t NumArgumentNodes() const noexcept
std::vector< std::unique_ptr< Node > > Nodes_
Edge & CreateEdge(Port &from, Port &to, bool directed)
void MapProgramObjectToElement(GraphElement &element)
void OutputSubgraphs(std::ostream &out, OutputFormat format, size_t indent) const override
std::unordered_map< std::string, std::string > HtmlTableAttributes_
std::vector< std::unique_ptr< OutputPort > > OutputPorts_
void OutputDot(std::ostream &out, size_t indent) const override
InOutNode(Graph &graph, size_t inputPorts, size_t outputPorts)
void SetFillColor(std::string color) override
OutputPort & GetOutputPort(size_t index)
std::vector< Graph * > SubGraphs_
std::vector< std::unique_ptr< InputPort > > InputPorts_
void SetShape(std::string) override
InputPort & GetInputPort(size_t index)
Graph & GetSubgraph(size_t index)
void OutputASCII(std::ostream &out, size_t indent) const override
void SetHtmlTableAttribute(std::string name, std::string value)
OutputPort & CreateOutputPort()
const char * GetIdPrefix() const override
bool CanBeEdgeTail() const override
void SetFillColor(std::string color) override
void OutputDotPortId(std::ostream &out) const override
InputPort(InOutNode &node)
Node & GetNode() override
Node & GetNode() override
const char * GetIdPrefix() const override
Graph & GetGraph() override
virtual void OutputDot(std::ostream &out, size_t indent) const
virtual void OutputSubgraphs(std::ostream &out, OutputFormat format, size_t indent) const
void OutputDotPortId(std::ostream &out) const override
void Output(std::ostream &out, OutputFormat format, size_t indent) const
virtual void OutputASCII(std::ostream &out, size_t indent) const
virtual void SetShape(std::string shape)
void SetFillColor(std::string color) override
void SetFillColor(std::string color) override
bool CanBeEdgeHead() const override
void OutputDotPortId(std::ostream &out) const override
const char * GetIdPrefix() const override
const std::vector< Edge * > & GetConnections() const
virtual bool CanBeEdgeTail() const
virtual bool CanBeEdgeHead() const
std::vector< Edge * > Connections_
bool HasIncomingEdges() const
void OutputIncomingEdgesASCII(std::ostream &out) const
Graph & GetGraph() override
void OnEdgeAdded(Edge &edge)
bool HasOutgoingEdges() const
virtual void OutputDotPortId(std::ostream &out) const =0
virtual Node & GetNode()=0
void SetOutsideDestination(const Port &outsideSource)
void OutputASCII(std::ostream &out, size_t indent) const override
const char * GetIdPrefix() const override
bool CanBeEdgeTail() const override
GraphElement * GetElementFromProgramObject(uintptr_t object) const
size_t GetNextUniqueIdStubSuffix(const char *idStub)
std::unordered_map< std::string, size_t > NextUniqueIdStubSuffix_
size_t NumGraphs() const noexcept
Graph & CreateSubGraph(Node &parentNode)
std::vector< std::unique_ptr< Graph > > Graphs_
void outputAllGraphs(std::ostream &out, OutputFormat format)
Graph & GetGraph(size_t index)
#define JLM_ASSERT(x)
Definition: common.hpp:16
#define JLM_UNREACHABLE(msg)
Definition: common.hpp:43
std::string Edge(jlm::rvsdg::Output *output, jlm::rvsdg::Input *input, std::unordered_map< rvsdg::Output *, ViewColors > &tailLabel, bool back_edge=false)
Definition: view.cpp:172
static std::string hex(size_t i)
Definition: view.cpp:45
static bool empty(const rvsdg::GammaNode *gamma)
Definition: pull.cpp:48
static std::string indent(size_t depth)
Definition: view.cpp:22
const char *const White
static const char *const EDGE_ID_ATTRIBUTE
Definition: GraphWriter.cpp:15
static bool LooksLikeIdentifier(std::string_view string)
Definition: GraphWriter.cpp:23
static const char * Indent(size_t indent)
static void PrintStringAsHtmlAttributeName(std::ostream &out, std::string_view string)
static const char *const TOOLTIP_ATTRIBUTE
Definition: GraphWriter.cpp:13
static void PrintStringAsHtmlText(std::ostream &out, std::string_view string, bool replaceNewlines)
Definition: GraphWriter.cpp:90
static void PrintIdentifierSafe(std::ostream &out, std::string_view string)
Definition: GraphWriter.cpp:54
static std::string strfmt(Args... args)
Definition: strfmt.hpp:35