/* Creating GraphViz .dot files from diagnostic state graphs. Copyright (C) 2025 Free Software Foundation, Inc. Contributed by David Malcolm . 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 . */ #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::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::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 (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 (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 (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 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 spanning "depth" columns. */ xp.push_tag ("td", false); xp.set_attr ("colspan", std::to_string (depth)); xp.add_text (" "); // graphviz doesn't like 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 m_src_nodes; std::set m_dst_nodes; std::map m_src_node_to_port_id; std::map m_dst_node_to_port_id; }; std::unique_ptr state_graphs:: make_dot_graph (const digraphs::digraph &state_graph, const logical_locations::manager &logical_loc_mgr) { return std::make_unique (state_graph, logical_loc_mgr); }