Skip to content

MIR graphviz prettification #30602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 4, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 117 additions & 31 deletions src/librustc/mir/repr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -317,31 +318,81 @@ impl<'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 {
try!(write!(fmt, ", "));
}
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()
}
}
}
Expand Down Expand Up @@ -495,19 +546,19 @@ impl<'tcx> Debug for Lvalue<'tcx> {

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) =>
Expand Down Expand Up @@ -671,12 +722,12 @@ impl<'tcx> Debug for Rvalue<'tcx> {
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),
Expand All @@ -691,7 +742,7 @@ impl<'tcx> Debug for Rvalue<'tcx> {
// 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>,
Expand All @@ -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,
Expand All @@ -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),
}
}
133 changes: 133 additions & 0 deletions src/librustc_mir/graphviz.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// 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))
}
Loading