Skip to content

Commit badc23b

Browse files
committed
Auto merge of #30602 - tsion:mir-graphviz-display, r=nikomatsakis
r? @nikomatsakis cc @eddyb @nagisa This PR changes most of the MIR graphviz debug output, making it smaller and more consistent. Also, it changes all fonts to monospace and adds a graph label containing the type of the `fn` the MIR is for and all the values (arguments, named bindings, and compiler temporaries). I chose to re-write the graphviz output code instead of using the existing libgraphviz API because I found it much easier to prototype usage of various graphviz features when I had full control of the text output. It also makes the code simpler, I think. Below are a bunch of example functions and links to their output images on the current nightly vs. this PR. File names starting with numbers (e.g. `80-factorial_fold-new.png`) are for closures. There's still a bunch of low hanging fruit to make it even better, particularly around aggregates and references. I also imagine the textual output for MIR will be able to closely match the graphviz output. The list of statements should look identical and the terminators will be the same except that the text form will have a list of target blocks (potentially using the same edge labels as the graphviz does). I can PR a simple text output right after this PR. This is my first large change to the compiler, so if anything should be reorganized/renamed/etc, let me know! Also, feel free to bikeshed the details of the output, though any minor changes can come in future PRs. ```rust fn empty() {} ``` http://vps.solson.me/mir-graphviz/empty-new.png http://vps.solson.me/mir-graphviz/empty-old.png ```rust fn constant() -> i32 { 42 } ``` http://vps.solson.me/mir-graphviz/constant-new.png http://vps.solson.me/mir-graphviz/constant-old.png ```rust fn increment(x: i32) -> i32 { x + 1 } ``` http://vps.solson.me/mir-graphviz/increment-new.png http://vps.solson.me/mir-graphviz/increment-old.png ```rust fn factorial_recursive(n: usize) -> usize { if n == 0 { 1 } else { n * factorial_recursive(n - 1) } } ``` http://vps.solson.me/mir-graphviz/factorial_recursive-new.png http://vps.solson.me/mir-graphviz/factorial_recursive-old.png ```rust fn factorial_iterative(n: usize) -> usize { let mut prod = 1; for x in 1..n { prod *= x; } prod } ``` http://vps.solson.me/mir-graphviz/factorial_iterative-new.png http://vps.solson.me/mir-graphviz/factorial_iterative-old.png ```rust fn factorial_fold(n: usize) -> usize { (1..n).fold(1, |prod, x| prod * x) } ``` http://vps.solson.me/mir-graphviz/factorial_fold-new.png http://vps.solson.me/mir-graphviz/factorial_fold-old.png http://vps.solson.me/mir-graphviz/80-factorial_fold-new.png http://vps.solson.me/mir-graphviz/80-factorial_fold-old.png ```rust fn collatz(mut n: usize) { while n != 1 { if n % 2 == 0 { n /= 2; } else { n = 3 * n + 1; } } } ``` http://vps.solson.me/mir-graphviz/collatz-new.png http://vps.solson.me/mir-graphviz/collatz-old.png ```rust fn multi_switch(n: usize) -> usize { match n { 5 | 10 | 15 => 3, 20 | 30 => 2, _ => 1, } } ``` http://vps.solson.me/mir-graphviz/multi_switch-new.png http://vps.solson.me/mir-graphviz/multi_switch-old.png
2 parents 5e8cb38 + 56343cd commit badc23b

File tree

5 files changed

+254
-200
lines changed

5 files changed

+254
-200
lines changed

src/librustc/mir/repr.rs

+117-31
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@
1111
use middle::const_eval::ConstVal;
1212
use middle::def_id::DefId;
1313
use middle::subst::Substs;
14-
use middle::ty::{AdtDef, ClosureSubsts, FnOutput, Region, Ty};
14+
use middle::ty::{self, AdtDef, ClosureSubsts, FnOutput, Region, Ty};
1515
use rustc_back::slice;
1616
use rustc_data_structures::tuple_slice::TupleSlice;
1717
use rustc_front::hir::InlineAsm;
1818
use syntax::ast::Name;
1919
use syntax::codemap::Span;
20-
use std::fmt::{Debug, Formatter, Error};
21-
use std::u32;
20+
use std::borrow::{Cow, IntoCow};
21+
use std::fmt::{Debug, Formatter, Error, Write};
22+
use std::{iter, u32};
2223

2324
/// Lowered representation of a single function.
2425
#[derive(RustcEncodable, RustcDecodable)]
@@ -317,31 +318,81 @@ impl<'tcx> BasicBlockData<'tcx> {
317318

318319
impl<'tcx> Debug for Terminator<'tcx> {
319320
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
321+
try!(self.fmt_head(fmt));
322+
let successors = self.successors();
323+
let labels = self.fmt_successor_labels();
324+
assert_eq!(successors.len(), labels.len());
325+
326+
match successors.len() {
327+
0 => Ok(()),
328+
329+
1 => write!(fmt, " -> {:?}", successors[0]),
330+
331+
_ => {
332+
try!(write!(fmt, " -> ["));
333+
for (i, target) in successors.iter().enumerate() {
334+
if i > 0 {
335+
try!(write!(fmt, ", "));
336+
}
337+
try!(write!(fmt, "{}: {:?}", labels[i], target));
338+
}
339+
write!(fmt, "]")
340+
}
341+
342+
}
343+
}
344+
}
345+
346+
impl<'tcx> Terminator<'tcx> {
347+
/// Write the "head" part of the terminator; that is, its name and the data it uses to pick the
348+
/// successor basic block, if any. The only information not inlcuded is the list of possible
349+
/// successors, which may be rendered differently between the text and the graphviz format.
350+
pub fn fmt_head<W: Write>(&self, fmt: &mut W) -> Result<(), Error> {
320351
use self::Terminator::*;
321352
match *self {
322-
Goto { target } =>
323-
write!(fmt, "goto -> {:?}", target),
324-
Panic { target } =>
325-
write!(fmt, "panic -> {:?}", target),
326-
If { cond: ref lv, ref targets } =>
327-
write!(fmt, "if({:?}) -> {:?}", lv, targets),
328-
Switch { discr: ref lv, adt_def: _, ref targets } =>
329-
write!(fmt, "switch({:?}) -> {:?}", lv, targets),
330-
SwitchInt { discr: ref lv, switch_ty: _, ref values, ref targets } =>
331-
write!(fmt, "switchInt({:?}, {:?}) -> {:?}", lv, values, targets),
332-
Diverge =>
333-
write!(fmt, "diverge"),
334-
Return =>
335-
write!(fmt, "return"),
336-
Call { data: ref c, targets } => {
353+
Goto { .. } => write!(fmt, "goto"),
354+
Panic { .. } => write!(fmt, "panic"),
355+
If { cond: ref lv, .. } => write!(fmt, "if({:?})", lv),
356+
Switch { discr: ref lv, .. } => write!(fmt, "switch({:?})", lv),
357+
SwitchInt { discr: ref lv, .. } => write!(fmt, "switchInt({:?})", lv),
358+
Diverge => write!(fmt, "diverge"),
359+
Return => write!(fmt, "return"),
360+
Call { data: ref c, .. } => {
337361
try!(write!(fmt, "{:?} = {:?}(", c.destination, c.func));
338362
for (index, arg) in c.args.iter().enumerate() {
339363
if index > 0 {
340364
try!(write!(fmt, ", "));
341365
}
342366
try!(write!(fmt, "{:?}", arg));
343367
}
344-
write!(fmt, ") -> {:?}", targets)
368+
write!(fmt, ")")
369+
}
370+
}
371+
}
372+
373+
/// Return the list of labels for the edges to the successor basic blocks.
374+
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
375+
use self::Terminator::*;
376+
match *self {
377+
Diverge | Return => vec![],
378+
Goto { .. } | Panic { .. } => vec!["".into_cow()],
379+
If { .. } => vec!["true".into_cow(), "false".into_cow()],
380+
Call { .. } => vec!["return".into_cow(), "unwind".into_cow()],
381+
Switch { ref adt_def, .. } => {
382+
adt_def.variants
383+
.iter()
384+
.map(|variant| variant.name.to_string().into_cow())
385+
.collect()
386+
}
387+
SwitchInt { ref values, .. } => {
388+
values.iter()
389+
.map(|const_val| {
390+
let mut buf = String::new();
391+
fmt_const_val(&mut buf, const_val).unwrap();
392+
buf.into_cow()
393+
})
394+
.chain(iter::once(String::from("otherwise").into_cow()))
395+
.collect()
345396
}
346397
}
347398
}
@@ -495,19 +546,19 @@ impl<'tcx> Debug for Lvalue<'tcx> {
495546

496547
match *self {
497548
Var(id) =>
498-
write!(fmt,"Var({:?})", id),
549+
write!(fmt,"var{:?}", id),
499550
Arg(id) =>
500-
write!(fmt,"Arg({:?})", id),
551+
write!(fmt,"arg{:?}", id),
501552
Temp(id) =>
502-
write!(fmt,"Temp({:?})", id),
553+
write!(fmt,"tmp{:?}", id),
503554
Static(id) =>
504555
write!(fmt,"Static({:?})", id),
505556
ReturnPointer =>
506557
write!(fmt,"ReturnPointer"),
507558
Projection(ref data) =>
508559
match data.elem {
509-
ProjectionElem::Downcast(_, variant_index) =>
510-
write!(fmt,"({:?} as {:?})", data.base, variant_index),
560+
ProjectionElem::Downcast(ref adt_def, index) =>
561+
write!(fmt,"({:?} as {})", data.base, adt_def.variants[index].name),
511562
ProjectionElem::Deref =>
512563
write!(fmt,"(*{:?})", data.base),
513564
ProjectionElem::Field(field) =>
@@ -671,12 +722,12 @@ impl<'tcx> Debug for Rvalue<'tcx> {
671722
Use(ref lvalue) => write!(fmt, "{:?}", lvalue),
672723
Repeat(ref a, ref b) => write!(fmt, "[{:?}; {:?}]", a, b),
673724
Ref(ref a, bk, ref b) => write!(fmt, "&{:?} {:?} {:?}", a, bk, b),
674-
Len(ref a) => write!(fmt, "LEN({:?})", a),
675-
Cast(ref kind, ref lv, ref ty) => write!(fmt, "{:?} as {:?} ({:?}", lv, ty, kind),
676-
BinaryOp(ref op, ref a, ref b) => write!(fmt, "{:?}({:?},{:?})", op, a, b),
725+
Len(ref a) => write!(fmt, "Len({:?})", a),
726+
Cast(ref kind, ref lv, ref ty) => write!(fmt, "{:?} as {:?} ({:?})", lv, ty, kind),
727+
BinaryOp(ref op, ref a, ref b) => write!(fmt, "{:?}({:?}, {:?})", op, a, b),
677728
UnaryOp(ref op, ref a) => write!(fmt, "{:?}({:?})", op, a),
678-
Box(ref t) => write!(fmt, "Box {:?}", t),
679-
Aggregate(ref kind, ref lvs) => write!(fmt, "Aggregate<{:?}>({:?})", kind, lvs),
729+
Box(ref t) => write!(fmt, "Box({:?})", t),
730+
Aggregate(ref kind, ref lvs) => write!(fmt, "Aggregate<{:?}>{:?}", kind, lvs),
680731
InlineAsm(ref asm) => write!(fmt, "InlineAsm({:?})", asm),
681732
Slice { ref input, from_start, from_end } =>
682733
write!(fmt, "{:?}[{:?}..-{:?}]", input, from_start, from_end),
@@ -691,7 +742,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
691742
// this does not necessarily mean that they are "==" in Rust -- in
692743
// particular one must be wary of `NaN`!
693744

694-
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
745+
#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable)]
695746
pub struct Constant<'tcx> {
696747
pub span: Span,
697748
pub ty: Ty<'tcx>,
@@ -707,7 +758,7 @@ pub enum ItemKind {
707758
Method,
708759
}
709760

710-
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
761+
#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable)]
711762
pub enum Literal<'tcx> {
712763
Item {
713764
def_id: DefId,
@@ -718,3 +769,38 @@ pub enum Literal<'tcx> {
718769
value: ConstVal,
719770
},
720771
}
772+
773+
impl<'tcx> Debug for Constant<'tcx> {
774+
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
775+
write!(fmt, "{:?}", self.literal)
776+
}
777+
}
778+
779+
impl<'tcx> Debug for Literal<'tcx> {
780+
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
781+
use self::Literal::*;
782+
match *self {
783+
Item { def_id, .. } =>
784+
write!(fmt, "{}", ty::tls::with(|tcx| tcx.item_path_str(def_id))),
785+
Value { ref value } => fmt_const_val(fmt, value),
786+
}
787+
}
788+
}
789+
790+
/// Write a `ConstVal` in a way closer to the original source code than the `Debug` output.
791+
pub fn fmt_const_val<W: Write>(fmt: &mut W, const_val: &ConstVal) -> Result<(), Error> {
792+
use middle::const_eval::ConstVal::*;
793+
match *const_val {
794+
Float(f) => write!(fmt, "{:?}", f),
795+
Int(n) => write!(fmt, "{:?}", n),
796+
Uint(n) => write!(fmt, "{:?}", n),
797+
Str(ref s) => write!(fmt, "Str({:?})", s),
798+
ByteStr(ref bytes) => write!(fmt, "ByteStr{:?}", bytes),
799+
Bool(b) => write!(fmt, "{:?}", b),
800+
Struct(id) => write!(fmt, "Struct({:?})", id),
801+
Tuple(id) => write!(fmt, "Tuple({:?})", id),
802+
Function(def_id) => write!(fmt, "Function({:?})", def_id),
803+
Array(id, n) => write!(fmt, "Array({:?}, {:?})", id, n),
804+
Repeat(id, n) => write!(fmt, "Repeat({:?}, {:?})", id, n),
805+
}
806+
}

src/librustc_mir/graphviz.rs

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use dot;
12+
use rustc::mir::repr::*;
13+
use rustc::middle::ty;
14+
use std::fmt::Debug;
15+
use std::io::{self, Write};
16+
17+
/// Write a graphviz DOT graph for the given MIR.
18+
pub fn write_mir_graphviz<W: Write>(mir: &Mir, w: &mut W) -> io::Result<()> {
19+
try!(writeln!(w, "digraph Mir {{"));
20+
21+
// Global graph properties
22+
try!(writeln!(w, r#" graph [fontname="monospace"];"#));
23+
try!(writeln!(w, r#" node [fontname="monospace"];"#));
24+
try!(writeln!(w, r#" edge [fontname="monospace"];"#));
25+
26+
// Graph label
27+
try!(write_graph_label(mir, w));
28+
29+
// Nodes
30+
for block in mir.all_basic_blocks() {
31+
try!(write_node(block, mir, w));
32+
}
33+
34+
// Edges
35+
for source in mir.all_basic_blocks() {
36+
try!(write_edges(source, mir, w));
37+
}
38+
39+
writeln!(w, "}}")
40+
}
41+
42+
/// Write a graphviz DOT node for the given basic block.
43+
fn write_node<W: Write>(block: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<()> {
44+
let data = mir.basic_block_data(block);
45+
46+
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
47+
try!(write!(w, r#" {} [shape="none", label=<"#, node(block)));
48+
try!(write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#));
49+
50+
// Basic block number at the top.
51+
try!(write!(w, r#"<tr><td bgcolor="gray" align="center">{}</td></tr>"#, block.index()));
52+
53+
// List of statements in the middle.
54+
if !data.statements.is_empty() {
55+
try!(write!(w, r#"<tr><td align="left" balign="left">"#));
56+
for statement in &data.statements {
57+
try!(write!(w, "{}<br/>", escape(statement)));
58+
}
59+
try!(write!(w, "</td></tr>"));
60+
}
61+
62+
// Terminator head at the bottom, not including the list of successor blocks. Those will be
63+
// displayed as labels on the edges between blocks.
64+
let mut terminator_head = String::new();
65+
data.terminator.fmt_head(&mut terminator_head).unwrap();
66+
try!(write!(w, r#"<tr><td align="left">{}</td></tr>"#, dot::escape_html(&terminator_head)));
67+
68+
// Close the table, node label, and the node itself.
69+
writeln!(w, "</table>>];")
70+
}
71+
72+
/// Write graphviz DOT edges with labels between the given basic block and all of its successors.
73+
fn write_edges<W: Write>(source: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<()> {
74+
let terminator = &mir.basic_block_data(source).terminator;
75+
let labels = terminator.fmt_successor_labels();
76+
77+
for (&target, label) in terminator.successors().iter().zip(labels) {
78+
try!(writeln!(w, r#" {} -> {} [label="{}"];"#, node(source), node(target), label));
79+
}
80+
81+
Ok(())
82+
}
83+
84+
/// Write the graphviz DOT label for the overall graph. This is essentially a block of text that
85+
/// will appear below the graph, showing the type of the `fn` this MIR represents and the types of
86+
/// all the variables and temporaries.
87+
fn write_graph_label<W: Write>(mir: &Mir, w: &mut W) -> io::Result<()> {
88+
try!(write!(w, " label=<fn("));
89+
90+
// fn argument types.
91+
for (i, arg) in mir.arg_decls.iter().enumerate() {
92+
if i > 0 {
93+
try!(write!(w, ", "));
94+
}
95+
try!(write!(w, "{:?}: {}", Lvalue::Arg(i as u32), escape(&arg.ty)));
96+
}
97+
98+
try!(write!(w, ") -&gt; "));
99+
100+
// fn return type.
101+
match mir.return_ty {
102+
ty::FnOutput::FnConverging(ty) => try!(write!(w, "{}", escape(ty))),
103+
ty::FnOutput::FnDiverging => try!(write!(w, "!")),
104+
}
105+
106+
try!(write!(w, r#"<br align="left"/>"#));
107+
108+
// User variable types (including the user's name in a comment).
109+
for (i, var) in mir.var_decls.iter().enumerate() {
110+
try!(write!(w, "let "));
111+
if var.mutability == Mutability::Mut {
112+
try!(write!(w, "mut "));
113+
}
114+
try!(write!(w, r#"{:?}: {}; // {}<br align="left"/>"#,
115+
Lvalue::Var(i as u32), escape(&var.ty), var.name));
116+
}
117+
118+
// Compiler-introduced temporary types.
119+
for (i, temp) in mir.temp_decls.iter().enumerate() {
120+
try!(write!(w, r#"let mut {:?}: {};<br align="left"/>"#,
121+
Lvalue::Temp(i as u32), escape(&temp.ty)));
122+
}
123+
124+
writeln!(w, ">;")
125+
}
126+
127+
fn node(block: BasicBlock) -> String {
128+
format!("bb{}", block.index())
129+
}
130+
131+
fn escape<T: Debug>(t: &T) -> String {
132+
dot::escape_html(&format!("{:?}", t))
133+
}

0 commit comments

Comments
 (0)