diff options
Diffstat (limited to 'gcc/diagnostics/state-graphs-to-dot.cc')
-rw-r--r-- | gcc/diagnostics/state-graphs-to-dot.cc | 551 |
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); +} |