Branch data Line data Source code
1 : : /* Creating GraphViz .dot files from diagnostic state graphs.
2 : : Copyright (C) 2025 Free Software Foundation, Inc.
3 : : Contributed by David Malcolm <dmalcolm@redhat.com>.
4 : :
5 : : This file is part of GCC.
6 : :
7 : : GCC is free software; you can redistribute it and/or modify it
8 : : under the terms of the GNU General Public License as published by
9 : : the Free Software Foundation; either version 3, or (at your option)
10 : : any later version.
11 : :
12 : : GCC is distributed in the hope that it will be useful, but
13 : : WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 : : General Public License for more details.
16 : :
17 : : You should have received a copy of the GNU General Public License
18 : : along with GCC; see the file COPYING3. If not see
19 : : <http://www.gnu.org/licenses/>. */
20 : :
21 : : #define INCLUDE_ALGORITHM
22 : : #define INCLUDE_MAP
23 : : #define INCLUDE_SET
24 : : #define INCLUDE_STRING
25 : : #define INCLUDE_VECTOR
26 : : #include "config.h"
27 : : #include "system.h"
28 : : #include "coretypes.h"
29 : :
30 : : #include "diagnostics/state-graphs.h"
31 : : #include "graphviz.h"
32 : : #include "xml.h"
33 : : #include "xml-printer.h"
34 : : #include "intl.h"
35 : :
36 : : using namespace diagnostics;
37 : : using namespace diagnostics::state_graphs;
38 : :
39 : : static int
40 : 12 : get_depth (const digraphs::node &n)
41 : : {
42 : 12 : int deepest_child = 0;
43 : 23 : for (size_t i = 0; i < n.get_num_children (); ++i)
44 : 11 : deepest_child = std::max (deepest_child,
45 : 11 : get_depth (n.get_child (i)));
46 : 12 : return deepest_child + 1;
47 : : }
48 : :
49 : : static const char *
50 : 2 : get_color_for_dynalloc_state (enum node_dynalloc_state dynalloc_st)
51 : : {
52 : 2 : switch (dynalloc_st)
53 : : {
54 : 0 : default:
55 : 0 : gcc_unreachable ();
56 : : break;
57 : : case node_dynalloc_state::unknown:
58 : : case node_dynalloc_state::nonnull:
59 : : return nullptr;
60 : :
61 : 0 : case node_dynalloc_state::unchecked:
62 : 0 : return "#ec7a08"; // pf-orange-400
63 : :
64 : 0 : case node_dynalloc_state::freed:
65 : 0 : return "#cc0000"; // pf-red-100
66 : : }
67 : : }
68 : :
69 : : static void
70 : 0 : set_color_for_dynalloc_state (dot::attr_list &attrs,
71 : : enum node_dynalloc_state state)
72 : : {
73 : 0 : if (const char *color = get_color_for_dynalloc_state (state))
74 : 0 : attrs.add (dot::id ("color"), dot::id (color));
75 : 0 : }
76 : :
77 : : class state_diagram : public dot::graph
78 : : {
79 : : public:
80 : 1 : state_diagram (const diagnostics::digraphs::digraph &input_state_graph,
81 : : const logical_locations::manager &logical_loc_mgr)
82 : 1 : : m_logical_loc_mgr (logical_loc_mgr)
83 : : {
84 : : // "node [shape=plaintext]\n"
85 : 1 : {
86 : 1 : auto attr_stmt
87 : 1 : = std::make_unique<dot::attr_stmt> (dot::attr_stmt::kind::node);
88 : 1 : attr_stmt->m_attrs.add (dot::id ("shape"), dot::id ("plaintext"));
89 : 1 : add_stmt (std::move (attr_stmt));
90 : 1 : }
91 : :
92 : : /* Determine which nodes are involved in edges. */
93 : 1 : for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
94 : : {
95 : 0 : auto &edge = input_state_graph.get_edge (i);
96 : 0 : m_src_nodes.insert (&edge.get_src_node ());
97 : 0 : m_dst_nodes.insert (&edge.get_dst_node ());
98 : : }
99 : :
100 : : /* Recurse down the nodes in the state graph, creating subgraphs
101 : : and then eventually creating nodes, and recursively
102 : : creating XML tables, and adding ports for the endpoints of edges
103 : : where needed. */
104 : :
105 : 1 : auto root_cluster
106 : 1 : = std::make_unique<dot::subgraph> (dot::id ("cluster_memory_regions"));
107 : 2 : for (size_t i = 0; i < input_state_graph.get_num_nodes (); ++i)
108 : 1 : on_input_state_node (*root_cluster,
109 : 1 : state_node_ref (input_state_graph.get_node (i)));
110 : 1 : add_stmt (std::move (root_cluster));
111 : :
112 : : /* Now create dot edges for edges in input_stage_graph. */
113 : 1 : for (size_t i = 0; i < input_state_graph.get_num_edges (); ++i)
114 : : {
115 : 0 : auto &edge = input_state_graph.get_edge (i);
116 : 0 : auto &src_node = edge.get_src_node ();
117 : 0 : auto &dst_node = edge.get_dst_node ();
118 : :
119 : 0 : auto src_port_id = m_src_node_to_port_id.find (&src_node);
120 : 0 : if (src_port_id == m_src_node_to_port_id.end ())
121 : 0 : continue;
122 : 0 : auto dst_port_id = m_dst_node_to_port_id.find (&dst_node);
123 : 0 : if (dst_port_id == m_dst_node_to_port_id.end ())
124 : 0 : continue;
125 : :
126 : 0 : auto e = std::make_unique<dot::edge_stmt> (src_port_id->second,
127 : 0 : dst_port_id->second);
128 : 0 : set_color_for_dynalloc_state
129 : 0 : (e->m_attrs, state_node_ref (dst_node).get_dynalloc_state ());
130 : :
131 : 0 : add_stmt (std::move (e));
132 : 0 : }
133 : 1 : }
134 : :
135 : : private:
136 : : struct pending_edge
137 : : {
138 : : dot::node_id m_src_node_id;
139 : : std::string m_dst_region_id;
140 : : };
141 : :
142 : : dot::id
143 : : get_id_for_region (const char *region_id)
144 : : {
145 : : gcc_assert (region_id);
146 : : return std::string ("cluster_region_") + region_id;
147 : : }
148 : :
149 : : dot::id
150 : 2 : make_id (state_node_ref state_node, bool cluster)
151 : : {
152 : 2 : std::string input_node_id = state_node.m_node.get_id ();
153 : 2 : if (cluster)
154 : 1 : return std::string ("cluster_") + input_node_id;
155 : : else
156 : 1 : return input_node_id;
157 : 2 : }
158 : :
159 : : bool
160 : 1 : starts_node_p (state_node_ref state_node)
161 : : {
162 : 1 : switch (state_node.get_node_kind ())
163 : : {
164 : : default:
165 : : return false;
166 : :
167 : 1 : case node_kind::stack:
168 : : /* We want all frames in the stack in the same table,
169 : : so they are grouped. */
170 : 1 : case node_kind::dynalloc_buffer:
171 : 1 : case node_kind::variable:
172 : 1 : return true;
173 : : }
174 : : }
175 : :
176 : : const char *
177 : 0 : get_label_for_node (state_node_ref state_node)
178 : : {
179 : 0 : switch (state_node.get_node_kind ())
180 : : {
181 : : default:
182 : : return nullptr;
183 : :
184 : 0 : case node_kind::globals:
185 : 0 : return _("Globals");
186 : 0 : case node_kind::code:
187 : 0 : return _("Code");
188 : 0 : case node_kind::stack:
189 : 0 : return _("Stack");
190 : 0 : case node_kind::heap_:
191 : 0 : return _("Heap");
192 : : }
193 : : }
194 : :
195 : : void
196 : 1 : on_input_state_node (dot::subgraph &parent_subgraph,
197 : : state_node_ref state_node)
198 : : {
199 : 1 : dot::id sg_id = make_id (state_node, true);
200 : :
201 : 1 : if (starts_node_p (state_node))
202 : : {
203 : : // Create node with table
204 : 1 : xml::element table ("table", false);
205 : 1 : xml::printer xp (table);
206 : 1 : xp.set_attr ("border", "0");
207 : 1 : xp.set_attr ("cellborder", "1");
208 : 1 : xp.set_attr ("cellspacing", "0");
209 : :
210 : 1 : const int max_depth = get_depth (state_node.m_node);
211 : 1 : const int num_columns = max_depth + 2;
212 : :
213 : 1 : dot::id id_of_dot_node = make_id (state_node, false);
214 : 1 : on_node_in_table (id_of_dot_node, xp, state_node,
215 : : max_depth, 0, num_columns);
216 : :
217 : 1 : auto node = std::make_unique<dot::node_stmt> (std::move (id_of_dot_node));
218 : 2 : node->m_attrs.add (dot::id ("shape"),
219 : 2 : dot::id ("plaintext"));
220 : :
221 : : // xml must be done by now
222 : :
223 : 1 : node->m_attrs.add (dot::id ("label"),
224 : 1 : dot::id (table));
225 : :
226 : 1 : parent_subgraph.m_stmt_list.add_stmt (std::move (node));
227 : 1 : }
228 : : else
229 : : {
230 : 0 : auto child_subgraph = std::make_unique<dot::subgraph> (std::move (sg_id));
231 : :
232 : 0 : if (const char *label = get_label_for_node (state_node))
233 : 0 : child_subgraph->add_attr (dot::id ("label"), dot::id (label));
234 : :
235 : : // recurse:
236 : 0 : for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i)
237 : 0 : on_input_state_node (*child_subgraph,
238 : 0 : state_node.m_node.get_child (i));
239 : 0 : parent_subgraph.m_stmt_list.add_stmt (std::move (child_subgraph));
240 : 0 : }
241 : 1 : }
242 : :
243 : : enum class style { h1, h2 };
244 : :
245 : : void
246 : 2 : add_title_tr (const dot::id &id_of_dot_node,
247 : : xml::printer &xp,
248 : : int num_columns,
249 : : state_node_ref state_node,
250 : : std::string heading,
251 : : enum style styl,
252 : : enum node_dynalloc_state dynalloc_state)
253 : : {
254 : 2 : xp.push_tag ("tr", true);
255 : 2 : xp.push_tag ("td", false);
256 : 2 : xp.set_attr ("colspan", std::to_string (num_columns));
257 : 2 : xp.set_attr ("cellpadding", "5");
258 : :
259 : 2 : const char *bgcolor;
260 : 2 : const char *color;
261 : 2 : if (const char *c = get_color_for_dynalloc_state (dynalloc_state))
262 : : {
263 : : bgcolor = c;
264 : : color = "white";
265 : : }
266 : : else
267 : 2 : switch (styl)
268 : : {
269 : 0 : default:
270 : 0 : gcc_unreachable ();
271 : : case style::h1:
272 : : // from diagnostics/html-sink.cc: HTML_STYLE .linenum
273 : : bgcolor = "#0088ce";
274 : : color = "white";
275 : : break;
276 : 1 : case style::h2:
277 : : // from diagnostics/html-sink.cc: HTML_STYLE .events-hdr
278 : 1 : bgcolor = "#393f44"; // pf-black-800
279 : 1 : color = "white";
280 : 1 : break;
281 : : }
282 : :
283 : 2 : xp.set_attr ("bgcolor", bgcolor);
284 : 2 : xp.push_tag ("font", false);
285 : 2 : xp.set_attr ("color", color);
286 : 2 : if (heading == "")
287 : 0 : heading = " ";
288 : 2 : xp.add_text (std::move (heading));
289 : 2 : xp.pop_tag ("font");
290 : :
291 : 2 : maybe_add_dst_port (id_of_dot_node, xp, state_node);
292 : :
293 : 2 : xp.pop_tag ("td");
294 : 2 : xp.pop_tag ("tr");
295 : 2 : }
296 : :
297 : : /* Recursively add <TR> to XP for STATE_NODE and its descendents. */
298 : : void
299 : 11 : on_node_in_table (const dot::id &id_of_dot_node,
300 : : xml::printer &xp,
301 : : state_node_ref state_node,
302 : : int max_depth,
303 : : int depth,
304 : : int num_columns)
305 : : {
306 : 11 : bool recurse = true;
307 : 11 : auto input_node_kind = state_node.get_node_kind ();
308 : :
309 : 11 : switch (input_node_kind)
310 : : {
311 : : case node_kind::padding:
312 : : case node_kind::other:
313 : : return;
314 : :
315 : 1 : case node_kind::stack:
316 : 1 : add_title_tr (id_of_dot_node, xp, num_columns, state_node, "Stack",
317 : : style::h1,
318 : : node_dynalloc_state::unknown);
319 : 1 : break;
320 : 1 : case node_kind::stack_frame:
321 : 1 : if (auto logical_loc = state_node.get_logical_loc ())
322 : 2 : if (const char *function
323 : 1 : = m_logical_loc_mgr.get_short_name (logical_loc))
324 : 2 : add_title_tr (id_of_dot_node, xp, num_columns, state_node,
325 : 3 : std::string ("Frame: ") + function,
326 : : style::h2,
327 : : node_dynalloc_state::unknown);
328 : : break;
329 : 0 : case node_kind::dynalloc_buffer:
330 : 0 : {
331 : 0 : enum node_dynalloc_state dynalloc_st
332 : 0 : = state_node.get_dynalloc_state ();
333 : 0 : const char *extents = state_node.get_dynamic_extents ();
334 : 0 : const char *type = state_node.get_type ();
335 : 0 : pretty_printer pp;
336 : 0 : switch (dynalloc_st)
337 : : {
338 : 0 : default:
339 : 0 : gcc_unreachable ();
340 : :
341 : 0 : case node_dynalloc_state::unknown:
342 : 0 : case node_dynalloc_state::nonnull:
343 : 0 : if (type)
344 : : {
345 : 0 : if (extents)
346 : 0 : pp_printf (&pp, "%s (%s byte allocation)",
347 : : type, extents);
348 : : else
349 : 0 : pp_printf (&pp, "%s", type);
350 : : }
351 : : else
352 : : {
353 : 0 : if (extents)
354 : 0 : pp_printf (&pp, "%s byte allocation",
355 : : extents);
356 : : }
357 : : break;
358 : :
359 : 0 : case node_dynalloc_state::unchecked:
360 : 0 : if (type)
361 : : {
362 : 0 : if (extents)
363 : 0 : pp_printf (&pp, "%s (unchecked %s byte allocation)",
364 : : type, extents);
365 : : }
366 : : else
367 : : {
368 : 0 : if (extents)
369 : 0 : pp_printf (&pp, "Unchecked %s byte allocation",
370 : : extents);
371 : : }
372 : : break;
373 : :
374 : 0 : case node_dynalloc_state::freed:
375 : : // TODO: show deallocator
376 : : // TODO: show deallocation event
377 : 0 : pp_printf (&pp, "Freed buffer");
378 : 0 : break;
379 : : }
380 : 0 : maybe_add_dst_port (id_of_dot_node, xp, state_node);
381 : 0 : add_title_tr (id_of_dot_node, xp, num_columns, state_node,
382 : 0 : pp_formatted_text (&pp),
383 : : style::h2,
384 : : dynalloc_st);
385 : 0 : }
386 : 0 : break;
387 : :
388 : 8 : default:
389 : 8 : {
390 : 8 : xp.push_tag ("tr", true);
391 : :
392 : 8 : maybe_add_dst_port (id_of_dot_node, xp, state_node);
393 : :
394 : 8 : if (depth > 0)
395 : : {
396 : : /* Indent, by create a <td> spanning "depth" columns. */
397 : 8 : xp.push_tag ("td", false);
398 : 8 : xp.set_attr ("colspan", std::to_string (depth));
399 : 8 : xp.add_text (" "); // graphviz doesn't like <td/>
400 : 8 : xp.pop_tag ("td");
401 : : }
402 : :
403 : 8 : switch (input_node_kind)
404 : : {
405 : : default:
406 : : break;
407 : 1 : case node_kind::variable:
408 : 1 : {
409 : 1 : const char *name = state_node.get_name ();
410 : 1 : gcc_assert (name);
411 : 1 : xp.push_tag ("td", false);
412 : 1 : maybe_add_dst_port (id_of_dot_node, xp, state_node);
413 : 1 : push_src_text (xp);
414 : 1 : xp.add_text (name);
415 : 2 : pop_src_text (xp);
416 : 1 : xp.pop_tag ("td");
417 : : }
418 : 1 : break;
419 : 4 : case node_kind::element:
420 : 4 : {
421 : 4 : const char *index = state_node.get_index ();
422 : 4 : gcc_assert (index);
423 : 4 : xp.push_tag ("td", false);
424 : 4 : maybe_add_dst_port (id_of_dot_node, xp, state_node);
425 : 4 : push_src_text (xp);
426 : 4 : xp.add_text ("[");
427 : 4 : xp.add_text (index);
428 : 4 : xp.add_text ("]");
429 : 8 : pop_src_text (xp);
430 : 4 : xp.pop_tag ("td");
431 : : }
432 : 4 : break;
433 : 3 : case node_kind::field:
434 : 3 : {
435 : 3 : const char *name = state_node.get_name ();
436 : 3 : gcc_assert (name);
437 : 3 : xp.push_tag ("td", false);
438 : 3 : maybe_add_dst_port (id_of_dot_node, xp, state_node);
439 : 3 : push_src_text (xp);
440 : 3 : xp.add_text (".");
441 : 3 : xp.add_text (name);
442 : 6 : pop_src_text (xp);
443 : 3 : xp.pop_tag ("td");
444 : : }
445 : 3 : break;
446 : : }
447 : :
448 : 8 : if (const char *type = state_node.get_type ())
449 : : {
450 : 8 : xp.push_tag ("td", false);
451 : 8 : xp.set_attr ("align", "right");
452 : 8 : push_src_text (xp);
453 : 8 : xp.add_text (type);
454 : 16 : pop_src_text (xp);
455 : 8 : xp.pop_tag ("td");
456 : : }
457 : :
458 : 8 : if (const char *value = state_node.get_value ())
459 : : {
460 : 1 : xp.push_tag ("td", false);
461 : 1 : xp.set_attr ("align", "left");
462 : 1 : maybe_add_src_port (id_of_dot_node, xp, state_node);
463 : 1 : push_src_text (xp);
464 : 1 : xp.add_text (value);
465 : 2 : pop_src_text (xp);
466 : 1 : xp.pop_tag ("td");
467 : 1 : recurse = false;
468 : : }
469 : 8 : xp.pop_tag ("tr");
470 : : }
471 : 8 : break;
472 : : }
473 : :
474 : 10 : if (recurse)
475 : 19 : for (size_t i = 0; i < state_node.m_node.get_num_children (); ++i)
476 : 10 : on_node_in_table (id_of_dot_node, xp,
477 : 10 : state_node.m_node.get_child (i),
478 : : max_depth, depth + 1, num_columns);
479 : : }
480 : :
481 : : void
482 : 17 : push_src_text (xml::printer &xp)
483 : : {
484 : 17 : xp.push_tag ("font");
485 : 17 : xp.set_attr ("color", "blue");
486 : 17 : }
487 : :
488 : : void
489 : 17 : pop_src_text (xml::printer &xp)
490 : : {
491 : 17 : xp.pop_tag ("font");
492 : : }
493 : :
494 : : /* If STATE_NODE is in m_src_nodes, add a port to XP for possible
495 : : incoming edges to use. */
496 : :
497 : : void
498 : 1 : maybe_add_src_port (const dot::id &id_of_dot_node,
499 : : xml::printer &xp,
500 : : state_node_ref state_node)
501 : : {
502 : 1 : auto iter = m_src_nodes.find (&state_node.m_node);
503 : 1 : if (iter == m_src_nodes.end ())
504 : 1 : return;
505 : :
506 : 0 : dot::id src_id = make_id (state_node, false);
507 : 0 : dot::node_id node_id (id_of_dot_node,
508 : 0 : dot::port (src_id,
509 : 0 : dot::compass_pt::e));
510 : 0 : m_src_node_to_port_id.insert ({&state_node.m_node, node_id});
511 : 0 : xp.set_attr ("port", src_id.m_str);
512 : 0 : }
513 : :
514 : : /* If STATE_NODE is in m_dst_nodes, add a port to XP for possible
515 : : incoming edges to use. */
516 : :
517 : : void
518 : 18 : maybe_add_dst_port (const dot::id &id_of_dot_node,
519 : : xml::printer &xp,
520 : : state_node_ref state_node)
521 : : {
522 : 18 : auto iter = m_dst_nodes.find (&state_node.m_node);
523 : 18 : if (iter == m_dst_nodes.end ())
524 : 18 : return;
525 : :
526 : 0 : dot::id dst_id = make_id (state_node, false);
527 : 0 : dot::node_id node_id (id_of_dot_node,
528 : 0 : dot::port (dst_id/*,
529 : 0 : dot::compass_pt::w*/));
530 : 0 : m_dst_node_to_port_id.insert ({&state_node.m_node, node_id});
531 : 0 : xp.set_attr ("port", dst_id.m_str);
532 : 0 : }
533 : :
534 : : private:
535 : : const logical_locations::manager &m_logical_loc_mgr;
536 : :
537 : : /* All nodes involved in edges (and thus will need a port). */
538 : : std::set<digraphs::node *> m_src_nodes;
539 : : std::set<digraphs::node *> m_dst_nodes;
540 : :
541 : : std::map<digraphs::node *, dot::node_id> m_src_node_to_port_id;
542 : : std::map<digraphs::node *, dot::node_id> m_dst_node_to_port_id;
543 : : };
544 : :
545 : : std::unique_ptr<dot::graph>
546 : 1 : state_graphs::
547 : : make_dot_graph (const digraphs::digraph &state_graph,
548 : : const logical_locations::manager &logical_loc_mgr)
549 : : {
550 : 1 : return std::make_unique<state_diagram> (state_graph, logical_loc_mgr);
551 : : }
|