diff --git a/src/librustc/mir/repr.rs b/src/librustc/mir/repr.rs index be6bb8689eefa..b6e86077ccff9 100644 --- a/src/librustc/mir/repr.rs +++ b/src/librustc/mir/repr.rs @@ -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 @@ 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(&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 @@ impl<'tcx> Debug for Terminator<'tcx> { } 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> { + 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 @@ 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) => @@ -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), @@ -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>, @@ -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(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), + } +} diff --git a/src/librustc_mir/graphviz.rs b/src/librustc_mir/graphviz.rs new file mode 100644 index 0000000000000..d1d3e80e3402f --- /dev/null +++ b/src/librustc_mir/graphviz.rs @@ -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 or the MIT license +// , 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(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(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#""#)); + + // Basic block number at the top. + try!(write!(w, r#""#, block.index())); + + // List of statements in the middle. + if !data.statements.is_empty() { + try!(write!(w, r#"")); + } + + // 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#""#, dot::escape_html(&terminator_head))); + + // Close the table, node label, and the node itself. + writeln!(w, "
{}
"#)); + for statement in &data.statements { + try!(write!(w, "{}
", escape(statement))); + } + try!(write!(w, "
{}
>];") +} + +/// Write graphviz DOT edges with labels between the given basic block and all of its successors. +fn write_edges(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(mir: &Mir, w: &mut W) -> io::Result<()> { + try!(write!(w, " label= 0 { + try!(write!(w, ", ")); + } + try!(write!(w, "{:?}: {}", Lvalue::Arg(i as u32), escape(&arg.ty))); + } + + try!(write!(w, ") -> ")); + + // 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#"
"#)); + + // 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#"{:?}: {}; // {}
"#, + 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 {:?}: {};
"#, + Lvalue::Temp(i as u32), escape(&temp.ty))); + } + + writeln!(w, ">;") +} + +fn node(block: BasicBlock) -> String { + format!("bb{}", block.index()) +} + +fn escape(t: &T) -> String { + dot::escape_html(&format!("{:?}", t)) +} diff --git a/src/librustc_mir/graphviz/mod.rs b/src/librustc_mir/graphviz/mod.rs deleted file mode 100644 index d5da606d76e80..0000000000000 --- a/src/librustc_mir/graphviz/mod.rs +++ /dev/null @@ -1,166 +0,0 @@ -// 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 or the MIT license -// , 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> { - Some(dot::LabelText::label("none")) - } - - fn node_label(&'a self, &n: &BasicBlock) -> dot::LabelText<'a> { - let mut buffer = String::new(); - buffer.push_str(""); - - buffer.push_str(""); - - let data = self.basic_block_data(n); - for statement in &data.statements { - buffer.push_str(""); - } - - buffer.push_str(""); - - buffer.push_str("
"); - buffer.push_str(&escape(format!("{:?}", n))); - buffer.push_str("
"); - buffer.push_str(&escape(format!("{:?}", statement))); - buffer.push_str("
"); - buffer.push_str(&escape(format!("{:?}", &data.terminator))); - buffer.push_str("
"); - - 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::>() - .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 { - 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); - } -} diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs index 710d5ba4b4368..6a1134385d967 100644 --- a/src/librustc_mir/lib.rs +++ b/src/librustc_mir/lib.rs @@ -19,7 +19,6 @@ Rust MIR: a lowered representation of Rust. Also: an experiment! #![crate_type = "dylib"] #![feature(rustc_private)] -#![feature(into_cow)] #[macro_use] extern crate log; extern crate graphviz as dot; diff --git a/src/librustc_mir/mir_map.rs b/src/librustc_mir/mir_map.rs index 39a315f3c41c7..5c9399ebdadf7 100644 --- a/src/librustc_mir/mir_map.rs +++ b/src/librustc_mir/mir_map.rs @@ -21,7 +21,7 @@ extern crate rustc; 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 @@ impl<'a, 'm, 'tcx> Visitor<'tcx> for InnerDump<'a,'m,'tcx> { 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) => {