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, 9 NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, 10 EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, 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, 23 NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, 24 EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, 25 > GraphvizWriter<'a, G, NodeContentFn, EdgeLabelsFn> 26 { new( graph: &'a G, graphviz_name: &str, node_content_fn: NodeContentFn, edge_labels_fn: EdgeLabelsFn, ) -> Self27 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 set_graph_label(&mut self, graph_label: &str)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 write_graphviz<'tcx, W>(&self, tcx: TyCtxt<'tcx>, w: &mut W) -> io::Result<()> where W: Write,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 59 let font = format!(r#"fontname="{}""#, tcx.sess.opts.unstable_opts.graphviz_font); 60 let mut graph_attrs = vec![&font[..]]; 61 let mut content_attrs = vec![&font[..]]; 62 63 let dark_mode = tcx.sess.opts.unstable_opts.graphviz_dark_mode; 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. write_node<W>(&self, node: G::Node, dark_mode: bool, w: &mut W) -> io::Result<()> where W: Write,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 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 // 107 // let (blk, bgcolor) = if data.is_cleanup { 108 // let color = if dark_mode { "royalblue" } else { "lightblue" }; 109 // (format!("{:?} (cleanup)", node), color) 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>"#, 129 dot::escape_html(§ion) 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. write_edges<W>(&self, source: G::Node, w: &mut W) -> io::Result<()> where W: Write,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) { 150 dot::escape_html(edge_label) 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. write_graph_label<W>(&self, label: &str, w: &mut W) -> io::Result<()> where W: Write,161 fn write_graph_label<W>(&self, label: &str, w: &mut W) -> io::Result<()> 162 where 163 W: Write, 164 { 165 let escaped_label = dot::escape_html(label); 166 writeln!(w, r#" label=<<br/><br/>{}<br align="left"/><br/><br/><br/>>;"#, escaped_label) 167 } 168 node(&self, node: G::Node) -> String169 fn node(&self, node: G::Node) -> String { 170 format!("{:?}__{}", node, self.graphviz_name) 171 } 172 } 173