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