Skip to content

Commit

Permalink
Auto merge of #78399 - vn-ki:gsgdt-graphviz, r=oli-obk
Browse files Browse the repository at this point in the history
make MIR graphviz generation use gsgdt

gsgdt [https://crates.io/crates/gsgdt] is a crate which provides an
interface for stringly typed graphs. It also provides generation of
graphviz dot format from said graph.

This is the first in a series of PRs on moving graphviz code out of rustc into normal crates and then implementating graph diffing on top of these crates.

r? `@oli-obk`
  • Loading branch information
bors committed Dec 15, 2020
2 parents f76ecd0 + 6fe31e7 commit 4031f7b
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 136 deletions.
10 changes: 10 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1345,6 +1345,15 @@ dependencies = [
"regex",
]

[[package]]
name = "gsgdt"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d876ce7262df96262a2a19531da6ff9a86048224d49580a585fc5c04617825"
dependencies = [
"serde",
]

[[package]]
name = "handlebars"
version = "3.4.0"
Expand Down Expand Up @@ -3940,6 +3949,7 @@ version = "0.0.0"
dependencies = [
"coverage_test_macros",
"either",
"gsgdt",
"itertools 0.9.0",
"polonius-engine",
"regex",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_mir/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ doctest = false
[dependencies]
either = "1.5.0"
rustc_graphviz = { path = "../rustc_graphviz" }
gsgdt = "0.1.2"
itertools = "0.9"
tracing = "0.1"
polonius-engine = "0.12.0"
Expand Down
70 changes: 70 additions & 0 deletions compiler/rustc_mir/src/util/generic_graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
use gsgdt::{Edge, Graph, Node, NodeStyle};
use rustc_hir::def_id::DefId;
use rustc_index::vec::Idx;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;

/// Convert an MIR function into a gsgdt Graph
pub fn mir_fn_to_generic_graph<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Graph {
let def_id = body.source.def_id();
let def_name = graphviz_safe_def_name(def_id);
let graph_name = format!("Mir_{}", def_name);
let dark_mode = tcx.sess.opts.debugging_opts.graphviz_dark_mode;

// Nodes
let nodes: Vec<Node> = body
.basic_blocks()
.iter_enumerated()
.map(|(block, _)| bb_to_graph_node(block, body, dark_mode))
.collect();

// Edges
let mut edges = Vec::new();
for (source, _) in body.basic_blocks().iter_enumerated() {
let def_id = body.source.def_id();
let terminator = body[source].terminator();
let labels = terminator.kind.fmt_successor_labels();

for (&target, label) in terminator.successors().zip(labels) {
let src = node(def_id, source);
let trg = node(def_id, target);
edges.push(Edge::new(src, trg, label.to_string()));
}
}

Graph::new(graph_name, nodes, edges)
}

fn bb_to_graph_node(block: BasicBlock, body: &Body<'_>, dark_mode: bool) -> Node {
let def_id = body.source.def_id();
let data = &body[block];
let label = node(def_id, block);

let (title, bgcolor) = if data.is_cleanup {
let color = if dark_mode { "royalblue" } else { "lightblue" };
(format!("{} (cleanup)", block.index()), color)
} else {
let color = if dark_mode { "dimgray" } else { "gray" };
(format!("{}", block.index()), color)
};

let style = NodeStyle { title_bg: Some(bgcolor.to_owned()), ..Default::default() };
let mut stmts: Vec<String> = data.statements.iter().map(|x| format!("{:?}", x)).collect();

// add the terminator to the stmts, gsgdt can print it out seperately
let mut terminator_head = String::new();
data.terminator().kind.fmt_head(&mut terminator_head).unwrap();
stmts.push(terminator_head);

Node::new(stmts, label, title, style)
}

// Must match `[0-9A-Za-z_]*`. This does not appear in the rendered graph, so
// it does not have to be user friendly.
pub fn graphviz_safe_def_name(def_id: DefId) -> String {
format!("{}_{}", def_id.krate.index(), def_id.index.index(),)
}

fn node(def_id: DefId, block: BasicBlock) -> String {
format!("bb{}__{}", block.index(), graphviz_safe_def_name(def_id))
}
147 changes: 16 additions & 131 deletions compiler/rustc_mir/src/util/graphviz.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use gsgdt::GraphvizSettings;
use rustc_graphviz as dot;
use rustc_hir::def_id::DefId;
use rustc_index::vec::Idx;
use rustc_middle::mir::*;
use rustc_middle::ty::TyCtxt;
use std::fmt::Debug;
use std::io::{self, Write};

use super::generic_graph::mir_fn_to_generic_graph;
use super::pretty::dump_mir_def_ids;

/// Write a graphviz DOT graph of a list of MIRs.
Expand All @@ -32,12 +33,6 @@ where
Ok(())
}

// Must match `[0-9A-Za-z_]*`. This does not appear in the rendered graph, so
// it does not have to be user friendly.
pub fn graphviz_safe_def_name(def_id: DefId) -> String {
format!("{}_{}", def_id.krate.index(), def_id.index.index(),)
}

/// Write a graphviz DOT graph of the MIR.
pub fn write_mir_fn_graphviz<'tcx, W>(
tcx: TyCtxt<'tcx>,
Expand All @@ -48,12 +43,6 @@ pub fn write_mir_fn_graphviz<'tcx, W>(
where
W: Write,
{
let def_id = body.source.def_id();
let kind = if subgraph { "subgraph" } else { "digraph" };
let cluster = if subgraph { "cluster_" } else { "" }; // Prints a border around MIR
let def_name = graphviz_safe_def_name(def_id);
writeln!(w, "{} {}Mir_{} {{", kind, cluster, def_name)?;

// Global graph properties
let font = format!(r#"fontname="{}""#, tcx.sess.opts.debugging_opts.graphviz_font);
let mut graph_attrs = vec![&font[..]];
Expand All @@ -67,131 +56,31 @@ where
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
write_graph_label(tcx, body, w)?;

// Nodes
for (block, _) in body.basic_blocks().iter_enumerated() {
write_node(block, body, dark_mode, w)?;
}

// Edges
for (source, _) in body.basic_blocks().iter_enumerated() {
write_edges(source, body, w)?;
}
writeln!(w, "}}")
}

/// Write a graphviz HTML-styled label for the given basic block, with
/// all necessary escaping already performed. (This is suitable for
/// emitting directly, as is done in this module, or for use with the
/// LabelText::HtmlStr from librustc_graphviz.)
///
/// `init` and `fini` are callbacks for emitting additional rows of
/// data (using HTML enclosed with `<tr>` in the emitted text).
pub fn write_node_label<W: Write, INIT, FINI>(
block: BasicBlock,
body: &Body<'_>,
dark_mode: bool,
w: &mut W,
num_cols: u32,
init: INIT,
fini: FINI,
) -> io::Result<()>
where
INIT: Fn(&mut W) -> io::Result<()>,
FINI: Fn(&mut W) -> io::Result<()>,
{
let data = &body[block];

write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#)?;

// Basic block number at the top.
let (blk, bgcolor) = if data.is_cleanup {
let color = if dark_mode { "royalblue" } else { "lightblue" };
(format!("{} (cleanup)", block.index()), color)
} else {
let color = if dark_mode { "dimgray" } else { "gray" };
(format!("{}", block.index()), color)
let mut label = String::from("");
// FIXME: remove this unwrap
write_graph_label(tcx, body, &mut label).unwrap();
let g = mir_fn_to_generic_graph(tcx, body);
let settings = GraphvizSettings {
graph_attrs: Some(graph_attrs.join(" ")),
node_attrs: Some(content_attrs.join(" ")),
edge_attrs: Some(content_attrs.join(" ")),
graph_label: Some(label),
};
write!(
w,
r#"<tr><td bgcolor="{bgcolor}" {attrs} colspan="{colspan}">{blk}</td></tr>"#,
attrs = r#"align="center""#,
colspan = num_cols,
blk = blk,
bgcolor = bgcolor
)?;

init(w)?;

// List of statements in the middle.
if !data.statements.is_empty() {
write!(w, r#"<tr><td align="left" balign="left">"#)?;
for statement in &data.statements {
write!(w, "{}<br/>", escape(statement))?;
}
write!(w, "</td></tr>")?;
}

// Terminator head at the bottom, not including the list of successor blocks. Those will be
// displayed as labels on the edges between blocks.
let mut terminator_head = String::new();
data.terminator().kind.fmt_head(&mut terminator_head).unwrap();
write!(w, r#"<tr><td align="left">{}</td></tr>"#, dot::escape_html(&terminator_head))?;

fini(w)?;

// Close the table
write!(w, "</table>")
}

/// Write a graphviz DOT node for the given basic block.
fn write_node<W: Write>(
block: BasicBlock,
body: &Body<'_>,
dark_mode: bool,
w: &mut W,
) -> io::Result<()> {
let def_id = body.source.def_id();
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
write!(w, r#" {} [shape="none", label=<"#, node(def_id, block))?;
write_node_label(block, body, dark_mode, w, 1, |_| Ok(()), |_| Ok(()))?;
// Close the node label and the node itself.
writeln!(w, ">];")
}

/// Write graphviz DOT edges with labels between the given basic block and all of its successors.
fn write_edges<W: Write>(source: BasicBlock, body: &Body<'_>, w: &mut W) -> io::Result<()> {
let def_id = body.source.def_id();
let terminator = body[source].terminator();
let labels = terminator.kind.fmt_successor_labels();

for (&target, label) in terminator.successors().zip(labels) {
let src = node(def_id, source);
let trg = node(def_id, target);
writeln!(w, r#" {} -> {} [label="{}"];"#, src, trg, label)?;
}

Ok(())
g.to_dot(w, &settings, subgraph)
}

/// Write the graphviz DOT label for the overall graph. This is essentially a block of text that
/// will appear below the graph, showing the type of the `fn` this MIR represents and the types of
/// all the variables and temporaries.
fn write_graph_label<'tcx, W: Write>(
fn write_graph_label<'tcx, W: std::fmt::Write>(
tcx: TyCtxt<'tcx>,
body: &Body<'_>,
w: &mut W,
) -> io::Result<()> {
) -> std::fmt::Result {
let def_id = body.source.def_id();

write!(w, " label=<fn {}(", dot::escape_html(&tcx.def_path_str(def_id)))?;
write!(w, "fn {}(", dot::escape_html(&tcx.def_path_str(def_id)))?;

// fn argument types.
for (i, arg) in body.args_iter().enumerate() {
Expand Down Expand Up @@ -224,11 +113,7 @@ fn write_graph_label<'tcx, W: Write>(
)?;
}

writeln!(w, ">;")
}

fn node(def_id: DefId, block: BasicBlock) -> String {
format!("bb{}__{}", block.index(), graphviz_safe_def_name(def_id))
Ok(())
}

fn escape<T: Debug>(t: &T) -> String {
Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_mir/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod storage;
mod alignment;
pub mod collect_writes;
mod find_self_call;
mod generic_graph;
pub(crate) mod generic_graphviz;
mod graphviz;
pub(crate) mod pretty;
Expand All @@ -15,6 +16,6 @@ pub(crate) mod spanview;
pub use self::aggregate::expand_aggregate;
pub use self::alignment::is_disaligned;
pub use self::find_self_call::find_self_call;
pub use self::graphviz::write_node_label as write_graphviz_node_label;
pub use self::graphviz::{graphviz_safe_def_name, write_mir_graphviz};
pub use self::generic_graph::graphviz_safe_def_name;
pub use self::graphviz::write_mir_graphviz;
pub use self::pretty::{dump_enabled, dump_mir, write_mir_pretty, PassWhere};
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#![allow(clippy::useless_attribute)] //issue #2910
// edition:2018

#[macro_use]
extern crate serde_derive;
use serde::Deserialize;

/// Tests that we do not lint for unused underscores in a `MacroAttribute`
/// expansion
Expand Down
1 change: 1 addition & 0 deletions src/tools/tidy/src/deps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ const PERMITTED_DEPENDENCIES: &[&str] = &[
"getopts",
"getrandom",
"gimli",
"gsgdt",
"hashbrown",
"hermit-abi",
"humantime",
Expand Down

0 comments on commit 4031f7b

Please sign in to comment.