rustc_middle/mir/
generic_graphviz.rs

1use std::io::{self, Write};
2
3use rustc_data_structures::graph::{self, iterate};
4use rustc_graphviz as dot;
5
6use crate::ty::TyCtxt;
7
8pub struct GraphvizWriter<
9    'a,
10    G: graph::DirectedGraph + graph::Successors + graph::StartNode,
11    NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
12    EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
13> {
14    graph: &'a G,
15    is_subgraph: bool,
16    graphviz_name: String,
17    graph_label: Option<String>,
18    node_content_fn: NodeContentFn,
19    edge_labels_fn: EdgeLabelsFn,
20}
21
22impl<
23    'a,
24    G: graph::DirectedGraph + graph::Successors + graph::StartNode,
25    NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
26    EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>,
27> GraphvizWriter<'a, G, NodeContentFn, EdgeLabelsFn>
28{
29    pub fn new(
30        graph: &'a G,
31        graphviz_name: &str,
32        node_content_fn: NodeContentFn,
33        edge_labels_fn: EdgeLabelsFn,
34    ) -> Self {
35        Self {
36            graph,
37            is_subgraph: false,
38            graphviz_name: graphviz_name.to_owned(),
39            graph_label: None,
40            node_content_fn,
41            edge_labels_fn,
42        }
43    }
44
45    pub fn set_graph_label(&mut self, graph_label: &str) {
46        self.graph_label = Some(graph_label.to_owned());
47    }
48
49    /// Write a graphviz DOT of the graph
50    pub fn write_graphviz<'tcx, W>(&self, tcx: TyCtxt<'tcx>, w: &mut W) -> io::Result<()>
51    where
52        W: Write,
53    {
54        let kind = if self.is_subgraph { "subgraph" } else { "digraph" };
55        let cluster = if self.is_subgraph { "cluster_" } else { "" }; // Print border around graph
56        // FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation,
57        // prepend "Mir_" to the graphviz_safe_def_name(def_id)
58        writeln!(w, "{} {}{} {{", kind, cluster, self.graphviz_name)?;
59
60        // Global graph properties
61        let font = format!(r#"fontname="{}""#, tcx.sess.opts.unstable_opts.graphviz_font);
62        let mut graph_attrs = vec![&font[..]];
63        let mut content_attrs = vec![&font[..]];
64
65        let dark_mode = tcx.sess.opts.unstable_opts.graphviz_dark_mode;
66        if dark_mode {
67            graph_attrs.push(r#"bgcolor="black""#);
68            graph_attrs.push(r#"fontcolor="white""#);
69            content_attrs.push(r#"color="white""#);
70            content_attrs.push(r#"fontcolor="white""#);
71        }
72
73        writeln!(w, r#"    graph [{}];"#, graph_attrs.join(" "))?;
74        let content_attrs_str = content_attrs.join(" ");
75        writeln!(w, r#"    node [{content_attrs_str}];"#)?;
76        writeln!(w, r#"    edge [{content_attrs_str}];"#)?;
77
78        // Graph label
79        if let Some(graph_label) = &self.graph_label {
80            self.write_graph_label(graph_label, w)?;
81        }
82
83        // Nodes
84        for node in iterate::post_order_from(self.graph, self.graph.start_node()) {
85            self.write_node(node, dark_mode, w)?;
86        }
87
88        // Edges
89        for source in iterate::post_order_from(self.graph, self.graph.start_node()) {
90            self.write_edges(source, w)?;
91        }
92        writeln!(w, "}}")
93    }
94
95    /// Write a graphviz DOT node for the given node.
96    pub fn write_node<W>(&self, node: G::Node, dark_mode: bool, w: &mut W) -> io::Result<()>
97    where
98        W: Write,
99    {
100        // Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
101        write!(w, r#"    {} [shape="none", label=<"#, self.node(node))?;
102
103        write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#)?;
104
105        // FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation,
106        // we need generic way to know if node header should have a different color. For example,
107        // for MIR:
108        //
109        // let (blk, bgcolor) = if data.is_cleanup {
110        //     let color = if dark_mode { "royalblue" } else { "lightblue" };
111        //     (format!("{:?} (cleanup)", node), color)
112        // } else {
113        //     let color = if dark_mode { "dimgray" } else { "gray" };
114        //     (format!("{:?}", node), color)
115        // };
116        let color = if dark_mode { "dimgray" } else { "gray" };
117        let (blk, bgcolor) = (format!("{node:?}"), color);
118        write!(
119            w,
120            r#"<tr><td bgcolor="{bgcolor}" {attrs} colspan="{colspan}">{blk}</td></tr>"#,
121            attrs = r#"align="center""#,
122            colspan = 1,
123            blk = blk,
124            bgcolor = bgcolor
125        )?;
126
127        for section in (self.node_content_fn)(node) {
128            write!(
129                w,
130                r#"<tr><td align="left" balign="left">{}</td></tr>"#,
131                dot::escape_html(&section)
132            )?;
133        }
134
135        // Close the table
136        write!(w, "</table>")?;
137
138        // Close the node label and the node itself.
139        writeln!(w, ">];")
140    }
141
142    /// Write graphviz DOT edges with labels between the given node and all of its successors.
143    fn write_edges<W>(&self, source: G::Node, w: &mut W) -> io::Result<()>
144    where
145        W: Write,
146    {
147        let edge_labels = (self.edge_labels_fn)(source);
148        for (index, target) in self.graph.successors(source).enumerate() {
149            let src = self.node(source);
150            let trg = self.node(target);
151            let escaped_edge_label = if let Some(edge_label) = edge_labels.get(index) {
152                dot::escape_html(edge_label)
153            } else {
154                "".to_owned()
155            };
156            writeln!(w, r#"    {src} -> {trg} [label=<{escaped_edge_label}>];"#)?;
157        }
158        Ok(())
159    }
160
161    /// Write the graphviz DOT label for the overall graph. This is essentially a block of text that
162    /// will appear below the graph.
163    fn write_graph_label<W>(&self, label: &str, w: &mut W) -> io::Result<()>
164    where
165        W: Write,
166    {
167        let escaped_label = dot::escape_html(label);
168        writeln!(w, r#"    label=<<br/><br/>{escaped_label}<br align="left"/><br/><br/><br/>>;"#)
169    }
170
171    fn node(&self, node: G::Node) -> String {
172        format!("{:?}__{}", node, self.graphviz_name)
173    }
174}