aboutsummaryrefslogtreecommitdiff
path: root/gcc/diagnostics/state-graphs-to-dot.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/diagnostics/state-graphs-to-dot.cc')
-rw-r--r--gcc/diagnostics/state-graphs-to-dot.cc551
1 files changed, 551 insertions, 0 deletions
diff --git a/gcc/diagnostics/state-graphs-to-dot.cc b/gcc/diagnostics/state-graphs-to-dot.cc
new file mode 100644
index 0000000..2d80e6b
--- /dev/null
+++ b/gcc/diagnostics/state-graphs-to-dot.cc
@@ -0,0 +1,551 @@
+/* Creating GraphViz .dot files from diagnostic state graphs.
+ Copyright (C) 2025 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@redhat.com>.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it
+under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 3, or (at your option)
+any later version.
+
+GCC is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3. If not see
+<http://www.gnu.org/licenses/>. */
+
+#define INCLUDE_ALGORITHM
+#define INCLUDE_MAP
+#define INCLUDE_SET
+#define INCLUDE_STRING
+#define INCLUDE_VECTOR
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+
+#include "diagnostics/state-graphs.h"
+#include "graphviz.h"
+#include "xml.h"
+#include "xml-printer.h"
+#include "intl.h"
+
+using namespace diagnostics;
+using namespace diagnostics::state_graphs;
+
+static int
+get_depth (const digraphs::node &n)
+{
+ int deepest_child = 0;
+ for (size_t i = 0; i < n.get_num_children (); ++i)
+ deepest_child = std::max (deepest_child,
+ get_depth (n.get_child (i)));
+ return deepest_child + 1;
+}
+
+static const char *
+get_color_for_dynalloc_state (enum node_dynalloc_state dynalloc_st)
+{
+ switch (dynalloc_st)
+ {
+ default:
+ gcc_unreachable ();
+ break;
+ case node_dynalloc_state::unknown:
+ case node_dynalloc_state::nonnull:
+ return nullptr;
+
+ case node_dynalloc_state::unchecked:
+ return "#ec7a08"; // pf-orange-400
+
+ case node_dynalloc_state::freed:
+ return "#cc0000"; // pf-red-100
+ }
+}
+
+static void
+set_color_for_dynalloc_state (dot::attr_list &attrs,
+ enum node_dynalloc_state state)
+{
+ if (const char *color = get_color_for_dynalloc_state (state))
+ attrs.add (dot::id ("color"), dot::id (color));
+}
+
+class state_diagram : public dot::graph
+{
+public:
+ state_diagram (const diagnostics::digraphs::digraph &input_state_graph,
+ const logical_locations::manager &logical_loc_mgr)
+ : m_logical_loc_mgr (logical_loc_mgr)
+ {
+ // "node [shape=plaintext]\n"
+ {
+ auto attr_stmt
+ = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
+ attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
+ add_stmt (std::move (attr_stmt));
+ }
+
+ /* Determine which nodes are involved in edges. */
+ for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
+ {
+ auto &edge = input_state_graph.get_edge (i);
+ m_src_nodes.insert (&edge.get_src_node ());
+ m_dst_nodes.insert (&edge.get_dst_node ());
+ }
+
+ /* Recurse down the nodes in the state graph, creating subgraphs
+ and then eventually creating nodes, and recursively
+ creating XML tables, and adding ports for the endpoints of edges
+ where needed. */
+
+ auto root_cluster
+ = std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions"));
+ for (size_t i = 0; i < input_state_graph.get_num_nodes (); ++i)
+ on_input_state_node (*root_cluster,
+ state_node_ref (input_state_graph.get_node (i)));
+ add_stmt (std::move (root_cluster));
+
+ /* Now create dot edges for edges in input_stage_graph. */
+ for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
+ {
+ auto &edge = input_state_graph.get_edge (i);
+ auto &src_node = edge.get_src_node ();
+ auto &dst_node = edge.get_dst_node ();
+
+ auto src_port_id = m_src_node_to_port_id.find (&src_node);
+ if (src_port_id == m_src_node_to_port_id.end ())
+ continue;
+ auto dst_port_id = m_dst_node_to_port_id.find (&dst_node);
+ if (dst_port_id == m_dst_node_to_port_id.end ())
+ continue;
+
+ auto e = std::make_unique<dot::edge_stmt> (src_port_id->second,
+ dst_port_id->second);
+ set_color_for_dynalloc_state
+ (e->m_attrs, state_node_ref (dst_node).get_dynalloc_state ());
+
+ add_stmt (std::move (e));
+ }
+ }
+
+private:
+ struct pending_edge
+ {
+ dot::node_id m_src_node_id;
+ std::string m_dst_region_id;
+ };
+
+ dot::id
+ get_id_for_region (const char *region_id)
+ {
+ gcc_assert (region_id);
+ return std::string ("cluster_region_") + region_id;
+ }
+
+ dot::id
+ make_id (state_node_ref state_node, bool cluster)
+ {
+ std::string input_node_id = state_node.m_node.get_id ();
+ if (cluster)
+ return std::string ("cluster_") + input_node_id;
+ else
+ return input_node_id;
+ }
+
+ bool
+ starts_node_p (state_node_ref state_node)
+ {
+ switch (state_node.get_node_kind ())
+ {
+ default:
+ return false;
+
+ case node_kind::stack:
+ /* We want all frames in the stack in the same table,
+ so they are grouped. */
+ case node_kind::dynalloc_buffer:
+ case node_kind::variable:
+ return true;
+ }
+ }
+
+ const char *
+ get_label_for_node (state_node_ref state_node)
+ {
+ switch (state_node.get_node_kind ())
+ {
+ default:
+ return nullptr;
+
+ case node_kind::globals:
+ return _("Globals");
+ case node_kind::code:
+ return _("Code");
+ case node_kind::stack:
+ return _("Stack");
+ case node_kind::heap_:
+ return _("Heap");
+ }
+ }
+
+ void
+ on_input_state_node (dot::subgraph &parent_subgraph,
+ state_node_ref state_node)
+ {
+ dot::id sg_id = make_id (state_node, true);
+
+ if (starts_node_p (state_node))
+ {
+ // Create node with table
+ xml::element table ("table", false);
+ xml::printer xp (table);
+ xp.set_attr ("border", "0");
+ xp.set_attr ("cellborder", "1");
+ xp.set_attr ("cellspacing", "0");
+
+ const int max_depth = get_depth (state_node.m_node);
+ const int num_columns = max_depth + 2;
+
+ dot::id id_of_dot_node = make_id (state_node, false);
+ on_node_in_table (id_of_dot_node, xp, state_node,
+ max_depth, 0, num_columns);
+
+ auto node = std::make_unique<dot::node_stmt> (std::move (id_of_dot_node));
+ node->m_attrs.add (dot::id ("shape"),
+ dot::id ("plaintext"));
+
+ // xml must be done by now
+
+ node->m_attrs.add (dot::id ("label"),
+ dot::id (table));
+
+ parent_subgraph.m_stmt_list.add_stmt (std::move (node));
+ }
+ else
+ {
+ auto child_subgraph = std::make_unique<dot::subgraph> (std::move (sg_id));
+
+ if (const char *label = get_label_for_node (state_node))
+ child_subgraph->add_attr (dot::id ("label"), dot::id (label));
+
+ // recurse:
+ for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i)
+ on_input_state_node (*child_subgraph,
+ state_node.m_node.get_child (i));
+ parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph));
+ }
+ }
+
+ enum class style { h1, h2 };
+
+ void
+ add_title_tr (const dot::id &id_of_dot_node,
+ xml::printer &xp,
+ int num_columns,
+ state_node_ref state_node,
+ std::string heading,
+ enum style styl,
+ enum node_dynalloc_state dynalloc_state)
+ {
+ xp.push_tag ("tr", true);
+ xp.push_tag ("td", false);
+ xp.set_attr ("colspan", std::to_string (num_columns));
+ xp.set_attr ("cellpadding", "5");
+
+ const char *bgcolor;
+ const char *color;
+ if (const char *c = get_color_for_dynalloc_state (dynalloc_state))
+ {
+ bgcolor = c;
+ color = "white";
+ }
+ else
+ switch (styl)
+ {
+ default:
+ gcc_unreachable ();
+ case style::h1:
+ // from diagnostics/html-sink.cc: HTML_STYLE .linenum
+ bgcolor = "#0088ce";
+ color = "white";
+ break;
+ case style::h2:
+ // from diagnostics/html-sink.cc: HTML_STYLE .events-hdr
+ bgcolor = "#393f44"; // pf-black-800
+ color = "white";
+ break;
+ }
+
+ xp.set_attr ("bgcolor", bgcolor);
+ xp.push_tag ("font", false);
+ xp.set_attr ("color", color);
+ if (heading == "")
+ heading = " ";
+ xp.add_text (std::move (heading));
+ xp.pop_tag ("font");
+
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+
+ xp.pop_tag ("td");
+ xp.pop_tag ("tr");
+ }
+
+ /* Recursively add <TR> to XP for STATE_NODE and its descendents. */
+ void
+ on_node_in_table (const dot::id &id_of_dot_node,
+ xml::printer &xp,
+ state_node_ref state_node,
+ int max_depth,
+ int depth,
+ int num_columns)
+ {
+ bool recurse = true;
+ auto input_node_kind = state_node.get_node_kind ();
+
+ switch (input_node_kind)
+ {
+ case node_kind::padding:
+ case node_kind::other:
+ return;
+
+ case node_kind::stack:
+ add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack",
+ style::h1,
+ node_dynalloc_state::unknown);
+ break;
+ case node_kind::stack_frame:
+ if (auto logical_loc = state_node.get_logical_loc ())
+ if (const char *function
+ = m_logical_loc_mgr.get_short_name (logical_loc))
+ add_title_tr (id_of_dot_node, xp, num_columns, state_node,
+ std::string ("Frame: ") + function,
+ style::h2,
+ node_dynalloc_state::unknown);
+ break;
+ case node_kind::dynalloc_buffer:
+ {
+ enum node_dynalloc_state dynalloc_st
+ = state_node.get_dynalloc_state ();
+ const char *extents = state_node.get_dynamic_extents ();
+ const char *type = state_node.get_type ();
+ pretty_printer pp;
+ switch (dynalloc_st)
+ {
+ default:
+ gcc_unreachable ();
+
+ case node_dynalloc_state::unknown:
+ case node_dynalloc_state::nonnull:
+ if (type)
+ {
+ if (extents)
+ pp_printf (&pp, "%s (%s byte allocation)",
+ type, extents);
+ else
+ pp_printf (&pp, "%s", type);
+ }
+ else
+ {
+ if (extents)
+ pp_printf (&pp, "%s byte allocation",
+ extents);
+ }
+ break;
+
+ case node_dynalloc_state::unchecked:
+ if (type)
+ {
+ if (extents)
+ pp_printf (&pp, "%s (unchecked %s byte allocation)",
+ type, extents);
+ }
+ else
+ {
+ if (extents)
+ pp_printf (&pp, "Unchecked %s byte allocation",
+ extents);
+ }
+ break;
+
+ case node_dynalloc_state::freed:
+ // TODO: show deallocator
+ // TODO: show deallocation event
+ pp_printf (&pp, "Freed buffer");
+ break;
+ }
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ add_title_tr (id_of_dot_node, xp, num_columns, state_node,
+ pp_formatted_text (&pp),
+ style::h2,
+ dynalloc_st);
+ }
+ break;
+
+ default:
+ {
+ xp.push_tag ("tr", true);
+
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+
+ if (depth > 0)
+ {
+ /* Indent, by create a <td> spanning "depth" columns. */
+ xp.push_tag ("td", false);
+ xp.set_attr ("colspan", std::to_string (depth));
+ xp.add_text (" "); // graphviz doesn't like <td/>
+ xp.pop_tag ("td");
+ }
+
+ switch (input_node_kind)
+ {
+ default:
+ break;
+ case node_kind::variable:
+ {
+ const char *name = state_node.get_name ();
+ gcc_assert (name);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text (name);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ break;
+ case node_kind::element:
+ {
+ const char *index = state_node.get_index ();
+ gcc_assert (index);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text ("[");
+ xp.add_text (index);
+ xp.add_text ("]");
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ break;
+ case node_kind::field:
+ {
+ const char *name = state_node.get_name ();
+ gcc_assert (name);
+ xp.push_tag ("td", false);
+ maybe_add_dst_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text (".");
+ xp.add_text (name);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+ break;
+ }
+
+ if (const char *type = state_node.get_type ())
+ {
+ xp.push_tag ("td", false);
+ xp.set_attr ("align", "right");
+ push_src_text (xp);
+ xp.add_text (type);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ }
+
+ if (const char *value = state_node.get_value ())
+ {
+ xp.push_tag ("td", false);
+ xp.set_attr ("align", "left");
+ maybe_add_src_port (id_of_dot_node, xp, state_node);
+ push_src_text (xp);
+ xp.add_text (value);
+ pop_src_text (xp);
+ xp.pop_tag ("td");
+ recurse = false;
+ }
+ xp.pop_tag ("tr");
+ }
+ break;
+ }
+
+ if (recurse)
+ for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i)
+ on_node_in_table (id_of_dot_node, xp,
+ state_node.m_node.get_child (i),
+ max_depth, depth + 1, num_columns);
+ }
+
+ void
+ push_src_text (xml::printer &xp)
+ {
+ xp.push_tag ("font");
+ xp.set_attr ("color", "blue");
+ }
+
+ void
+ pop_src_text (xml::printer &xp)
+ {
+ xp.pop_tag ("font");
+ }
+
+ /* If STATE_NODE is in m_src_nodes, add a port to XP for possible
+ incoming edges to use. */
+
+ void
+ maybe_add_src_port (const dot::id &id_of_dot_node,
+ xml::printer &xp,
+ state_node_ref state_node)
+ {
+ auto iter = m_src_nodes.find (&state_node.m_node);
+ if (iter == m_src_nodes.end ())
+ return;
+
+ dot::id src_id = make_id (state_node, false);
+ dot::node_id node_id (id_of_dot_node,
+ dot::port (src_id,
+ dot::compass_pt::e));
+ m_src_node_to_port_id.insert ({&state_node.m_node, node_id});
+ xp.set_attr ("port", src_id.m_str);
+ }
+
+ /* If STATE_NODE is in m_dst_nodes, add a port to XP for possible
+ incoming edges to use. */
+
+ void
+ maybe_add_dst_port (const dot::id &id_of_dot_node,
+ xml::printer &xp,
+ state_node_ref state_node)
+ {
+ auto iter = m_dst_nodes.find (&state_node.m_node);
+ if (iter == m_dst_nodes.end ())
+ return;
+
+ dot::id dst_id = make_id (state_node, false);
+ dot::node_id node_id (id_of_dot_node,
+ dot::port (dst_id/*,
+ dot::compass_pt::w*/));
+ m_dst_node_to_port_id.insert ({&state_node.m_node, node_id});
+ xp.set_attr ("port", dst_id.m_str);
+ }
+
+private:
+ const logical_locations::manager &m_logical_loc_mgr;
+
+ /* All nodes involved in edges (and thus will need a port). */
+ std::set<digraphs::node *> m_src_nodes;
+ std::set<digraphs::node *> m_dst_nodes;
+
+ std::map<digraphs::node *, dot::node_id> m_src_node_to_port_id;
+ std::map<digraphs::node *, dot::node_id> m_dst_node_to_port_id;
+};
+
+std::unique_ptr<dot::graph>
+state_graphs::
+make_dot_graph (const digraphs::digraph &state_graph,
+ const logical_locations::manager &logical_loc_mgr)
+{
+ return std::make_unique<state_diagram> (state_graph, logical_loc_mgr);
+}