提交 badc23b6 编写于 作者: B bors

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
......@@ -11,14 +11,15 @@
use middle::const_eval::ConstVal;
use middle::def_id::DefId;
use middle::subst::Substs;
use middle::ty::{AdtDef, ClosureSubsts, FnOutput, Region, Ty};
use middle::ty::{self, AdtDef, ClosureSubsts, FnOutput, Region, Ty};
use rustc_back::slice;
use rustc_data_structures::tuple_slice::TupleSlice;
use rustc_front::hir::InlineAsm;
use syntax::ast::Name;
use syntax::codemap::Span;
use std::fmt::{Debug, Formatter, Error};
use std::u32;
use std::borrow::{Cow, IntoCow};
use std::fmt::{Debug, Formatter, Error, Write};
use std::{iter, u32};
/// Lowered representation of a single function.
#[derive(RustcEncodable, RustcDecodable)]
......@@ -317,23 +318,46 @@ pub fn new(terminator: Terminator<'tcx>) -> BasicBlockData<'tcx> {
impl<'tcx> Debug for Terminator<'tcx> {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
try!(self.fmt_head(fmt));
let successors = self.successors();
let labels = self.fmt_successor_labels();
assert_eq!(successors.len(), labels.len());
match successors.len() {
0 => Ok(()),
1 => write!(fmt, " -> {:?}", successors[0]),
_ => {
try!(write!(fmt, " -> ["));
for (i, target) in successors.iter().enumerate() {
if i > 0 {
try!(write!(fmt, ", "));
}
try!(write!(fmt, "{}: {:?}", labels[i], target));
}
write!(fmt, "]")
}
}
}
}
impl<'tcx> Terminator<'tcx> {
/// Write the "head" part of the terminator; that is, its name and the data it uses to pick the
/// successor basic block, if any. The only information not inlcuded is the list of possible
/// successors, which may be rendered differently between the text and the graphviz format.
pub fn fmt_head<W: Write>(&self, fmt: &mut W) -> Result<(), Error> {
use self::Terminator::*;
match *self {
Goto { target } =>
write!(fmt, "goto -> {:?}", target),
Panic { target } =>
write!(fmt, "panic -> {:?}", target),
If { cond: ref lv, ref targets } =>
write!(fmt, "if({:?}) -> {:?}", lv, targets),
Switch { discr: ref lv, adt_def: _, ref targets } =>
write!(fmt, "switch({:?}) -> {:?}", lv, targets),
SwitchInt { discr: ref lv, switch_ty: _, ref values, ref targets } =>
write!(fmt, "switchInt({:?}, {:?}) -> {:?}", lv, values, targets),
Diverge =>
write!(fmt, "diverge"),
Return =>
write!(fmt, "return"),
Call { data: ref c, targets } => {
Goto { .. } => write!(fmt, "goto"),
Panic { .. } => write!(fmt, "panic"),
If { cond: ref lv, .. } => write!(fmt, "if({:?})", lv),
Switch { discr: ref lv, .. } => write!(fmt, "switch({:?})", lv),
SwitchInt { discr: ref lv, .. } => write!(fmt, "switchInt({:?})", lv),
Diverge => write!(fmt, "diverge"),
Return => write!(fmt, "return"),
Call { data: ref c, .. } => {
try!(write!(fmt, "{:?} = {:?}(", c.destination, c.func));
for (index, arg) in c.args.iter().enumerate() {
if index > 0 {
......@@ -341,7 +365,34 @@ fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
}
try!(write!(fmt, "{:?}", arg));
}
write!(fmt, ") -> {:?}", targets)
write!(fmt, ")")
}
}
}
/// Return the list of labels for the edges to the successor basic blocks.
pub fn fmt_successor_labels(&self) -> Vec<Cow<'static, str>> {
use self::Terminator::*;
match *self {
Diverge | Return => vec![],
Goto { .. } | Panic { .. } => vec!["".into_cow()],
If { .. } => vec!["true".into_cow(), "false".into_cow()],
Call { .. } => vec!["return".into_cow(), "unwind".into_cow()],
Switch { ref adt_def, .. } => {
adt_def.variants
.iter()
.map(|variant| variant.name.to_string().into_cow())
.collect()
}
SwitchInt { ref values, .. } => {
values.iter()
.map(|const_val| {
let mut buf = String::new();
fmt_const_val(&mut buf, const_val).unwrap();
buf.into_cow()
})
.chain(iter::once(String::from("otherwise").into_cow()))
.collect()
}
}
}
......@@ -495,19 +546,19 @@ fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
match *self {
Var(id) =>
write!(fmt,"Var({:?})", id),
write!(fmt,"var{:?}", id),
Arg(id) =>
write!(fmt,"Arg({:?})", id),
write!(fmt,"arg{:?}", id),
Temp(id) =>
write!(fmt,"Temp({:?})", id),
write!(fmt,"tmp{:?}", id),
Static(id) =>
write!(fmt,"Static({:?})", id),
ReturnPointer =>
write!(fmt,"ReturnPointer"),
Projection(ref data) =>
match data.elem {
ProjectionElem::Downcast(_, variant_index) =>
write!(fmt,"({:?} as {:?})", data.base, variant_index),
ProjectionElem::Downcast(ref adt_def, index) =>
write!(fmt,"({:?} as {})", data.base, adt_def.variants[index].name),
ProjectionElem::Deref =>
write!(fmt,"(*{:?})", data.base),
ProjectionElem::Field(field) =>
......@@ -671,12 +722,12 @@ fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
Use(ref lvalue) => write!(fmt, "{:?}", lvalue),
Repeat(ref a, ref b) => write!(fmt, "[{:?}; {:?}]", a, b),
Ref(ref a, bk, ref b) => write!(fmt, "&{:?} {:?} {:?}", a, bk, b),
Len(ref a) => write!(fmt, "LEN({:?})", a),
Cast(ref kind, ref lv, ref ty) => write!(fmt, "{:?} as {:?} ({:?}", lv, ty, kind),
BinaryOp(ref op, ref a, ref b) => write!(fmt, "{:?}({:?},{:?})", op, a, b),
Len(ref a) => write!(fmt, "Len({:?})", a),
Cast(ref kind, ref lv, ref ty) => write!(fmt, "{:?} as {:?} ({:?})", lv, ty, kind),
BinaryOp(ref op, ref a, ref b) => write!(fmt, "{:?}({:?}, {:?})", op, a, b),
UnaryOp(ref op, ref a) => write!(fmt, "{:?}({:?})", op, a),
Box(ref t) => write!(fmt, "Box {:?}", t),
Aggregate(ref kind, ref lvs) => write!(fmt, "Aggregate<{:?}>({:?})", kind, lvs),
Box(ref t) => write!(fmt, "Box({:?})", t),
Aggregate(ref kind, ref lvs) => write!(fmt, "Aggregate<{:?}>{:?}", kind, lvs),
InlineAsm(ref asm) => write!(fmt, "InlineAsm({:?})", asm),
Slice { ref input, from_start, from_end } =>
write!(fmt, "{:?}[{:?}..-{:?}]", input, from_start, from_end),
......@@ -691,7 +742,7 @@ fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
// this does not necessarily mean that they are "==" in Rust -- in
// particular one must be wary of `NaN`!
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable)]
pub struct Constant<'tcx> {
pub span: Span,
pub ty: Ty<'tcx>,
......@@ -707,7 +758,7 @@ pub enum ItemKind {
Method,
}
#[derive(Clone, Debug, PartialEq, RustcEncodable, RustcDecodable)]
#[derive(Clone, PartialEq, RustcEncodable, RustcDecodable)]
pub enum Literal<'tcx> {
Item {
def_id: DefId,
......@@ -718,3 +769,38 @@ pub enum Literal<'tcx> {
value: ConstVal,
},
}
impl<'tcx> Debug for Constant<'tcx> {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
write!(fmt, "{:?}", self.literal)
}
}
impl<'tcx> Debug for Literal<'tcx> {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
use self::Literal::*;
match *self {
Item { def_id, .. } =>
write!(fmt, "{}", ty::tls::with(|tcx| tcx.item_path_str(def_id))),
Value { ref value } => fmt_const_val(fmt, value),
}
}
}
/// Write a `ConstVal` in a way closer to the original source code than the `Debug` output.
pub fn fmt_const_val<W: Write>(fmt: &mut W, const_val: &ConstVal) -> Result<(), Error> {
use middle::const_eval::ConstVal::*;
match *const_val {
Float(f) => write!(fmt, "{:?}", f),
Int(n) => write!(fmt, "{:?}", n),
Uint(n) => write!(fmt, "{:?}", n),
Str(ref s) => write!(fmt, "Str({:?})", s),
ByteStr(ref bytes) => write!(fmt, "ByteStr{:?}", bytes),
Bool(b) => write!(fmt, "{:?}", b),
Struct(id) => write!(fmt, "Struct({:?})", id),
Tuple(id) => write!(fmt, "Tuple({:?})", id),
Function(def_id) => write!(fmt, "Function({:?})", def_id),
Array(id, n) => write!(fmt, "Array({:?}, {:?})", id, n),
Repeat(id, n) => write!(fmt, "Repeat({:?}, {:?})", id, n),
}
}
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use dot;
use rustc::mir::repr::*;
use rustc::middle::ty;
use std::fmt::Debug;
use std::io::{self, Write};
/// Write a graphviz DOT graph for the given MIR.
pub fn write_mir_graphviz<W: Write>(mir: &Mir, w: &mut W) -> io::Result<()> {
try!(writeln!(w, "digraph Mir {{"));
// Global graph properties
try!(writeln!(w, r#" graph [fontname="monospace"];"#));
try!(writeln!(w, r#" node [fontname="monospace"];"#));
try!(writeln!(w, r#" edge [fontname="monospace"];"#));
// Graph label
try!(write_graph_label(mir, w));
// Nodes
for block in mir.all_basic_blocks() {
try!(write_node(block, mir, w));
}
// Edges
for source in mir.all_basic_blocks() {
try!(write_edges(source, mir, w));
}
writeln!(w, "}}")
}
/// Write a graphviz DOT node for the given basic block.
fn write_node<W: Write>(block: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<()> {
let data = mir.basic_block_data(block);
// Start a new node with the label to follow, in one of DOT's pseudo-HTML tables.
try!(write!(w, r#" {} [shape="none", label=<"#, node(block)));
try!(write!(w, r#"<table border="0" cellborder="1" cellspacing="0">"#));
// Basic block number at the top.
try!(write!(w, r#"<tr><td bgcolor="gray" align="center">{}</td></tr>"#, block.index()));
// List of statements in the middle.
if !data.statements.is_empty() {
try!(write!(w, r#"<tr><td align="left" balign="left">"#));
for statement in &data.statements {
try!(write!(w, "{}<br/>", escape(statement)));
}
try!(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.fmt_head(&mut terminator_head).unwrap();
try!(write!(w, r#"<tr><td align="left">{}</td></tr>"#, dot::escape_html(&terminator_head)));
// Close the table, node label, and the node itself.
writeln!(w, "</table>>];")
}
/// Write graphviz DOT edges with labels between the given basic block and all of its successors.
fn write_edges<W: Write>(source: BasicBlock, mir: &Mir, w: &mut W) -> io::Result<()> {
let terminator = &mir.basic_block_data(source).terminator;
let labels = terminator.fmt_successor_labels();
for (&target, label) in terminator.successors().iter().zip(labels) {
try!(writeln!(w, r#" {} -> {} [label="{}"];"#, node(source), node(target), label));
}
Ok(())
}
/// 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<W: Write>(mir: &Mir, w: &mut W) -> io::Result<()> {
try!(write!(w, " label=<fn("));
// fn argument types.
for (i, arg) in mir.arg_decls.iter().enumerate() {
if i > 0 {
try!(write!(w, ", "));
}
try!(write!(w, "{:?}: {}", Lvalue::Arg(i as u32), escape(&arg.ty)));
}
try!(write!(w, ") -&gt; "));
// fn return type.
match mir.return_ty {
ty::FnOutput::FnConverging(ty) => try!(write!(w, "{}", escape(ty))),
ty::FnOutput::FnDiverging => try!(write!(w, "!")),
}
try!(write!(w, r#"<br align="left"/>"#));
// User variable types (including the user's name in a comment).
for (i, var) in mir.var_decls.iter().enumerate() {
try!(write!(w, "let "));
if var.mutability == Mutability::Mut {
try!(write!(w, "mut "));
}
try!(write!(w, r#"{:?}: {}; // {}<br align="left"/>"#,
Lvalue::Var(i as u32), escape(&var.ty), var.name));
}
// Compiler-introduced temporary types.
for (i, temp) in mir.temp_decls.iter().enumerate() {
try!(write!(w, r#"let mut {:?}: {};<br align="left"/>"#,
Lvalue::Temp(i as u32), escape(&temp.ty)));
}
writeln!(w, ">;")
}
fn node(block: BasicBlock) -> String {
format!("bb{}", block.index())
}
fn escape<T: Debug>(t: &T) -> String {
dot::escape_html(&format!("{:?}", t))
}
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use dot;
use rustc::mir::repr::*;
use std::borrow::IntoCow;
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct EdgeIndex {
source: BasicBlock,
target: BasicBlock,
index: usize,
}
impl<'a,'tcx> dot::Labeller<'a, BasicBlock, EdgeIndex> for Mir<'tcx> {
fn graph_id(&'a self) -> dot::Id<'a> {
dot::Id::new("Mir").unwrap()
}
fn node_id(&'a self, n: &BasicBlock) -> dot::Id<'a> {
dot::Id::new(format!("BB{}", n.index())).unwrap()
}
fn node_shape(&'a self, _: &BasicBlock) -> Option<dot::LabelText<'a>> {
Some(dot::LabelText::label("none"))
}
fn node_label(&'a self, &n: &BasicBlock) -> dot::LabelText<'a> {
let mut buffer = String::new();
buffer.push_str("<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">");
buffer.push_str("<tr><td><b>");
buffer.push_str(&escape(format!("{:?}", n)));
buffer.push_str("</b></td></tr>");
let data = self.basic_block_data(n);
for statement in &data.statements {
buffer.push_str("<tr><td align=\"left\">");
buffer.push_str(&escape(format!("{:?}", statement)));
buffer.push_str("</td></tr>");
}
buffer.push_str("<tr><td align=\"left\" bgcolor=\"cornsilk\">");
buffer.push_str(&escape(format!("{:?}", &data.terminator)));
buffer.push_str("</td></tr>");
buffer.push_str("</table>");
dot::LabelText::html(buffer)
}
fn edge_label(&'a self, edge: &EdgeIndex) -> dot::LabelText<'a> {
dot::LabelText::label(format!("{}", edge.index))
}
}
impl<'a,'tcx> dot::GraphWalk<'a, BasicBlock, EdgeIndex> for Mir<'tcx> {
fn nodes(&'a self) -> dot::Nodes<'a, BasicBlock> {
self.all_basic_blocks().into_cow()
}
fn edges(&'a self) -> dot::Edges<'a, EdgeIndex> {
self.all_basic_blocks()
.into_iter()
.flat_map(|source| {
self.basic_block_data(source)
.terminator
.successors()
.iter()
.enumerate()
.map(move |(index, &target)| {
EdgeIndex {
source: source,
target: target,
index: index,
}
})
})
.collect::<Vec<_>>()
.into_cow()
}
fn source(&'a self, edge: &EdgeIndex) -> BasicBlock {
edge.source
}
fn target(&'a self, edge: &EdgeIndex) -> BasicBlock {
edge.target
}
}
fn escape(text: String) -> String {
let text = dot::escape_html(&text);
let text = all_to_subscript("Temp", text);
let text = all_to_subscript("Var", text);
let text = all_to_subscript("Arg", text);
let text = all_to_subscript("BB", text);
text
}
/// A call like `all_to_subscript("Temp", "Temp(123)")` will convert
/// to `Temp₁₂₃`.
fn all_to_subscript(header: &str, mut text: String) -> String {
let mut offset = 0;
while offset < text.len() {
if let Some(text1) = to_subscript1(header, &text, &mut offset) {
text = text1;
}
}
return text;
/// Looks for `Foo(\d*)` where `header=="Foo"` and replaces the `\d` with subscripts.
/// Updates `offset` to point to the next location where we might want to search.
/// Returns an updated string if changes were made, else None.
fn to_subscript1(header: &str, text: &str, offset: &mut usize) -> Option<String> {
let a = match text[*offset..].find(header) {
None => {
*offset = text.len();
return None;
}
Some(a) => a + *offset,
};
// Example:
//
// header: "Foo"
// text: ....Foo(123)...
// ^ ^
// a b
let b = a + header.len();
*offset = b;
let mut chars = text[b..].chars();
if Some('(') != chars.next() {
return None;
}
let mut result = String::new();
result.push_str(&text[..b]);
while let Some(c) = chars.next() {
if c == ')' {
break;
}
if !c.is_digit(10) {
return None;
}
// 0x208 is _0 in unicode, 0x209 is _1, etc
const SUBSCRIPTS: &'static str = "₀₁₂₃₄₅₆₇₈₉";
let n = (c as usize) - ('0' as usize);
result.extend(SUBSCRIPTS.chars().skip(n).take(1));
}
result.extend(chars);
return Some(result);
}
}
......@@ -19,7 +19,6 @@
#![crate_type = "dylib"]
#![feature(rustc_private)]
#![feature(into_cow)]
#[macro_use] extern crate log;
extern crate graphviz as dot;
......
......@@ -21,7 +21,7 @@
extern crate rustc_front;
use build;
use dot;
use graphviz;
use transform::*;
use rustc::mir::repr::Mir;
use hair::cx::Cx;
......@@ -157,7 +157,9 @@ fn visit_fn(&mut self,
Some(s) => {
match
File::create(format!("{}{}", prefix, s))
.and_then(|ref mut output| dot::render(&mir, output))
.and_then(|ref mut output| {
graphviz::write_mir_graphviz(&mir, output)
})
{
Ok(()) => { }
Err(e) => {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册