| use std::io::{self, Write}; | |
| use rustc_data_structures::graph::{self, iterate}; | |
| use rustc_graphviz as dot; | |
| use crate::ty::TyCtxt; | |
| pub struct GraphvizWriter< | |
| 'a, | |
| G: graph::DirectedGraph + graph::Successors + graph::StartNode, | |
| NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, | |
| EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, | |
| > { | |
| graph: &'a G, | |
| is_subgraph: bool, | |
| graphviz_name: String, | |
| graph_label: Option<String>, | |
| node_content_fn: NodeContentFn, | |
| edge_labels_fn: EdgeLabelsFn, | |
| } | |
| impl< | |
| 'a, | |
| G: graph::DirectedGraph + graph::Successors + graph::StartNode, | |
| NodeContentFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, | |
| EdgeLabelsFn: Fn(<G as graph::DirectedGraph>::Node) -> Vec<String>, | |
| > GraphvizWriter<'a, G, NodeContentFn, EdgeLabelsFn> | |
| { | |
| pub fn new( | |
| graph: &'a G, | |
| graphviz_name: &str, | |
| node_content_fn: NodeContentFn, | |
| edge_labels_fn: EdgeLabelsFn, | |
| ) -> Self { | |
| Self { | |
| graph, | |
| is_subgraph: false, | |
| graphviz_name: graphviz_name.to_owned(), | |
| graph_label: None, | |
| node_content_fn, | |
| edge_labels_fn, | |
| } | |
| } | |
| pub fn set_graph_label(&mut self, graph_label: &str) { | |
| self.graph_label = Some(graph_label.to_owned()); | |
| } | |
| /// Write a graphviz DOT of the graph | |
| pub fn write_graphviz<'tcx, W>(&self, tcx: TyCtxt<'tcx>, w: &mut W) -> io::Result<()> | |
| where | |
| W: Write, | |
| { | |
| let kind = if self.is_subgraph { "subgraph" } else { "digraph" }; | |
| let cluster = if self.is_subgraph { "cluster_" } else { "" }; // Print border around graph | |
| // FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation, | |
| // prepend "Mir_" to the graphviz_safe_def_name(def_id) | |
| writeln!(w, "{} {}{} {{", kind, cluster, self.graphviz_name)?; | |
| // Global graph properties | |
| let font = format!(r#"fontname="{}""#, tcx.sess.opts.unstable_opts.graphviz_font); | |
| let mut graph_attrs = vec![&font[..]]; | |
| let mut content_attrs = vec![&font[..]]; | |
| let dark_mode = tcx.sess.opts.unstable_opts.graphviz_dark_mode; | |
| if dark_mode { | |
| graph_attrs.push(r#"bgcolor="black""#); | |
| graph_attrs.push(r#"fontcolor="white""#); | |
| content_attrs.push(r#"color="white""#); | |
| content_attrs.push(r#"fontcolor="white""#); | |
| } | |
| writeln!(w, r#" graph [{}];"#, graph_attrs.join(" "))?; | |
| let content_attrs_str = content_attrs.join(" "); | |
| writeln!(w, r#" node [{content_attrs_str}];"#)?; | |
| writeln!(w, r#" edge [{content_attrs_str}];"#)?; | |
| // Graph label | |
| if let Some(graph_label) = &self.graph_label { | |
| self.write_graph_label(graph_label, w)?; | |
| } | |
| // Nodes | |
| for node in iterate::post_order_from(self.graph, self.graph.start_node()) { | |
| self.write_node(node, dark_mode, w)?; | |
| } | |
| // Edges | |
| for source in iterate::post_order_from(self.graph, self.graph.start_node()) { | |
| self.write_edges(source, w)?; | |
| } | |
| writeln!(w, "}}") | |
| } | |
| /// Write a graphviz DOT node for the given node. | |
| pub fn write_node<W>(&self, node: G::Node, dark_mode: bool, w: &mut W) -> io::Result<()> | |
| where | |
| W: Write, | |
| { | |
| // Start a new node with the label to follow, in one of DOT's pseudo-HTML tables. | |
| write!(w, r#" {} [shape="none", label=<"#, self.node(node))?; | |
| write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#)?; | |
| // FIXME(richkadel): If/when migrating the MIR graphviz to this generic implementation, | |
| // we need generic way to know if node header should have a different color. For example, | |
| // for MIR: | |
| // | |
| // let (blk, bgcolor) = if data.is_cleanup { | |
| // let color = if dark_mode { "royalblue" } else { "lightblue" }; | |
| // (format!("{:?} (cleanup)", node), color) | |
| // } else { | |
| // let color = if dark_mode { "dimgray" } else { "gray" }; | |
| // (format!("{:?}", node), color) | |
| // }; | |
| let color = if dark_mode { "dimgray" } else { "gray" }; | |
| let (blk, bgcolor) = (format!("{node:?}"), color); | |
| write!( | |
| w, | |
| r#"<tr><td bgcolor="{bgcolor}" {attrs} colspan="{colspan}">{blk}</td></tr>"#, | |
| attrs = r#"align="center""#, | |
| colspan = 1, | |
| blk = blk, | |
| bgcolor = bgcolor | |
| )?; | |
| for section in (self.node_content_fn)(node) { | |
| write!( | |
| w, | |
| r#"<tr><td align="left" balign="left">{}</td></tr>"#, | |
| dot::escape_html(§ion) | |
| )?; | |
| } | |
| // Close the table | |
| write!(w, "</table>")?; | |
| // Close the node label and the node itself. | |
| writeln!(w, ">];") | |
| } | |
| /// Write graphviz DOT edges with labels between the given node and all of its successors. | |
| fn write_edges<W>(&self, source: G::Node, w: &mut W) -> io::Result<()> | |
| where | |
| W: Write, | |
| { | |
| let edge_labels = (self.edge_labels_fn)(source); | |
| for (index, target) in self.graph.successors(source).enumerate() { | |
| let src = self.node(source); | |
| let trg = self.node(target); | |
| let escaped_edge_label = if let Some(edge_label) = edge_labels.get(index) { | |
| dot::escape_html(edge_label) | |
| } else { | |
| "".to_owned() | |
| }; | |
| writeln!(w, r#" {src} -> {trg} [label=<{escaped_edge_label}>];"#)?; | |
| } | |
| Ok(()) | |
| } | |
| /// Write the graphviz DOT label for the overall graph. This is essentially a block of text that | |
| /// will appear below the graph. | |
| fn write_graph_label<W>(&self, label: &str, w: &mut W) -> io::Result<()> | |
| where | |
| W: Write, | |
| { | |
| let escaped_label = dot::escape_html(label); | |
| writeln!(w, r#" label=<<br/><br/>{escaped_label}<br align="left"/><br/><br/><br/>>;"#) | |
| } | |
| fn node(&self, node: G::Node) -> String { | |
| format!("{:?}__{}", node, self.graphviz_name) | |
| } | |
| } |