From e64f594f10600e39928c175f49682aacb45c5bae Mon Sep 17 00:00:00 2001
From: "Felix S. Klock II" <pnkfelix@pnkfx.org>
Date: Wed, 2 Jul 2014 17:50:18 +0200
Subject: [PATCH] Extend --pretty flowgraph=ID to include dataflow results in
 output.

Use one or more of the following `-Z` flag options to tell the
graphviz renderer to include the corresponding dataflow sets (after
the iterative constraint propagation reaches a fixed-point solution):

  * `-Z flowgraph-print-loans` : loans computed via middle::borrowck
  * `-Z flowgraph-print-moves` : moves computed via middle::borrowck::move_data
  * `-Z flowgraph-print-assigns` : assignments, via middle::borrowck::move_data
  * `-Z flowgraph-print-all` : all of the available sets are included.

Fix #15016.

----

This also adds a module, `syntax::ast_map::blocks`, that captures a
common abstraction shared amongst code blocks and procedure-like
things.  As part of this, moved `ast_map.rs` to subdir
`ast_map/mod.rs`, to follow our directory layout conventions.

(incorporated review feedback from huon, acrichto.)
---
 src/libgraphviz/lib.rs                       |  36 ++-
 src/librustc/driver/config.rs                |  16 +-
 src/librustc/driver/driver.rs                |  94 ++++++--
 src/librustc/middle/borrowck/graphviz.rs     | 148 +++++++++++++
 src/librustc/middle/borrowck/mod.rs          |  86 +++++++-
 src/librustc/middle/borrowck/move_data.rs    |   4 +-
 src/librustc/middle/cfg/graphviz.rs          |   1 +
 src/librustc/middle/dataflow.rs              |  69 ++++--
 src/libsyntax/ast_map/blocks.rs              | 218 +++++++++++++++++++
 src/libsyntax/{ast_map.rs => ast_map/mod.rs} |   2 +
 10 files changed, 627 insertions(+), 47 deletions(-)
 create mode 100644 src/librustc/middle/borrowck/graphviz.rs
 create mode 100644 src/libsyntax/ast_map/blocks.rs
 rename src/libsyntax/{ast_map.rs => ast_map/mod.rs} (99%)

diff --git a/src/libgraphviz/lib.rs b/src/libgraphviz/lib.rs
index 9d2f43b9513..a3653fa9735 100644
--- a/src/libgraphviz/lib.rs
+++ b/src/libgraphviz/lib.rs
@@ -434,10 +434,37 @@ impl<'a> LabelText<'a> {
     /// Renders text as string suitable for a label in a .dot file.
     pub fn escape(&self) -> String {
         match self {
-            &LabelStr(ref s) => s.as_slice().escape_default().to_string(),
-            &EscStr(ref s) => LabelText::escape_str(s.as_slice()).to_string(),
+            &LabelStr(ref s) => s.as_slice().escape_default(),
+            &EscStr(ref s) => LabelText::escape_str(s.as_slice()),
         }
     }
+
+    /// Decomposes content into string suitable for making EscStr that
+    /// yields same content as self.  The result obeys the law
+    /// render(`lt`) == render(`EscStr(lt.pre_escaped_content())`) for
+    /// all `lt: LabelText`.
+    fn pre_escaped_content(self) -> str::MaybeOwned<'a> {
+        match self {
+            EscStr(s) => s,
+            LabelStr(s) => if s.as_slice().contains_char('\\') {
+                str::Owned(s.as_slice().escape_default())
+            } else {
+                s
+            },
+        }
+    }
+
+    /// Puts `prefix` on a line above this label, with a blank line separator.
+    pub fn prefix_line(self, prefix: LabelText) -> LabelText {
+        prefix.suffix_line(self)
+    }
+
+    /// Puts `suffix` on a line below this label, with a blank line separator.
+    pub fn suffix_line(self, suffix: LabelText) -> LabelText {
+        let prefix = self.pre_escaped_content().into_string();
+        let suffix = suffix.pre_escaped_content();
+        EscStr(str::Owned(prefix.append(r"\n\n").append(suffix.as_slice())))
+    }
 }
 
 pub type Nodes<'a,N> = MaybeOwnedVector<'a,N>;
@@ -664,10 +691,7 @@ mod tests {
         let mut writer = MemWriter::new();
         render(&g, &mut writer).unwrap();
         let mut r = BufReader::new(writer.get_ref());
-        match r.read_to_string() {
-            Ok(string) => Ok(string.to_string()),
-            Err(err) => Err(err),
-        }
+        r.read_to_string()
     }
 
     // All of the tests use raw-strings as the format for the expected outputs,
diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs
index b726c50afe9..9fe32c6fbcb 100644
--- a/src/librustc/driver/config.rs
+++ b/src/librustc/driver/config.rs
@@ -179,7 +179,11 @@ debugging_opts!(
         AST_JSON,
         AST_JSON_NOEXPAND,
         LS,
-        SAVE_ANALYSIS
+        SAVE_ANALYSIS,
+        FLOWGRAPH_PRINT_LOANS,
+        FLOWGRAPH_PRINT_MOVES,
+        FLOWGRAPH_PRINT_ASSIGNS,
+        FLOWGRAPH_PRINT_ALL
     ]
     0
 )
@@ -215,7 +219,15 @@ pub fn debugging_opts_map() -> Vec<(&'static str, &'static str, u64)> {
      ("ast-json-noexpand", "Print the pre-expansion AST as JSON and halt", AST_JSON_NOEXPAND),
      ("ls", "List the symbols defined by a library crate", LS),
      ("save-analysis", "Write syntax and type analysis information \
-                        in addition to normal output", SAVE_ANALYSIS))
+                        in addition to normal output", SAVE_ANALYSIS),
+     ("flowgraph-print-loans", "Include loan analysis data in \
+                       --pretty flowgraph output", FLOWGRAPH_PRINT_LOANS),
+     ("flowgraph-print-moves", "Include move analysis data in \
+                       --pretty flowgraph output", FLOWGRAPH_PRINT_MOVES),
+     ("flowgraph-print-assigns", "Include assignment analysis data in \
+                       --pretty flowgraph output", FLOWGRAPH_PRINT_ASSIGNS),
+     ("flowgraph-print-all", "Include all dataflow analysis data in \
+                       --pretty flowgraph output", FLOWGRAPH_PRINT_ALL))
 }
 
 /// Declare a macro that will define all CodegenOptions fields and parsers all
diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs
index d03bf3593fa..ca9cbe1306e 100644
--- a/src/librustc/driver/driver.rs
+++ b/src/librustc/driver/driver.rs
@@ -19,6 +19,9 @@ use lib::llvm::{ContextRef, ModuleRef};
 use lint;
 use metadata::common::LinkMeta;
 use metadata::creader;
+use middle::borrowck::{FnPartsWithCFG};
+use middle::borrowck;
+use borrowck_dot = middle::borrowck::graphviz;
 use middle::cfg;
 use middle::cfg::graphviz::LabelledCFG;
 use middle::{trans, freevars, stability, kind, ty, typeck, reachable};
@@ -40,6 +43,7 @@ use std::io;
 use std::io::fs;
 use std::io::MemReader;
 use syntax::ast;
+use syntax::ast_map::blocks;
 use syntax::attr;
 use syntax::attr::{AttrMetaMethods};
 use syntax::diagnostics;
@@ -662,6 +666,25 @@ impl pprust::PpAnn for TypedAnnotation {
     }
 }
 
+fn gather_flowgraph_variants(sess: &Session) -> Vec<borrowck_dot::Variant> {
+    let print_loans   = config::FLOWGRAPH_PRINT_LOANS;
+    let print_moves   = config::FLOWGRAPH_PRINT_MOVES;
+    let print_assigns = config::FLOWGRAPH_PRINT_ASSIGNS;
+    let print_all     = config::FLOWGRAPH_PRINT_ALL;
+    let opt = |print_which| sess.debugging_opt(print_which);
+    let mut variants = Vec::new();
+    if opt(print_all) || opt(print_loans) {
+        variants.push(borrowck_dot::Loans);
+    }
+    if opt(print_all) || opt(print_moves) {
+        variants.push(borrowck_dot::Moves);
+    }
+    if opt(print_all) || opt(print_assigns) {
+        variants.push(borrowck_dot::Assigns);
+    }
+    variants
+}
+
 pub fn pretty_print_input(sess: Session,
                           cfg: ast::CrateConfig,
                           input: &Input,
@@ -733,10 +756,17 @@ pub fn pretty_print_input(sess: Session,
                 sess.fatal(format!("--pretty flowgraph couldn't find id: {}",
                                    nodeid).as_slice())
             });
-            let block = match node {
-                syntax::ast_map::NodeBlock(block) => block,
-                _ => {
-                    let message = format!("--pretty=flowgraph needs block, got {:?}",
+            let code = blocks::Code::from_node(node);
+            match code {
+                Some(code) => {
+                    let variants = gather_flowgraph_variants(&sess);
+                    let analysis = phase_3_run_analysis_passes(sess, &krate,
+                                                               ast_map, id);
+                    print_flowgraph(variants, analysis, code, out)
+                }
+                None => {
+                    let message = format!("--pretty=flowgraph needs \
+                                           block, fn, or method; got {:?}",
                                           node);
 
                     // point to what was found, if there's an
@@ -746,10 +776,7 @@ pub fn pretty_print_input(sess: Session,
                         None => sess.fatal(message.as_slice())
                     }
                 }
-            };
-            let analysis = phase_3_run_analysis_passes(sess, &krate,
-                                                       ast_map, id);
-            print_flowgraph(analysis, block, out)
+            }
         }
         _ => {
             pprust::print_crate(sess.codemap(),
@@ -765,17 +792,52 @@ pub fn pretty_print_input(sess: Session,
 
 }
 
-fn print_flowgraph<W:io::Writer>(analysis: CrateAnalysis,
-                                 block: ast::P<ast::Block>,
+fn print_flowgraph<W:io::Writer>(variants: Vec<borrowck_dot::Variant>,
+                                 analysis: CrateAnalysis,
+                                 code: blocks::Code,
                                  mut out: W) -> io::IoResult<()> {
     let ty_cx = &analysis.ty_cx;
-    let cfg = cfg::CFG::new(ty_cx, &*block);
-    let lcfg = LabelledCFG { ast_map: &ty_cx.map,
-                             cfg: &cfg,
-                             name: format!("block{}", block.id), };
+    let cfg = match code {
+        blocks::BlockCode(block) => cfg::CFG::new(ty_cx, &*block),
+        blocks::FnLikeCode(fn_like) => cfg::CFG::new(ty_cx, fn_like.body()),
+    };
     debug!("cfg: {:?}", cfg);
-    let r = dot::render(&lcfg, &mut out);
-    return expand_err_details(r);
+
+    match code {
+        _ if variants.len() == 0 => {
+            let lcfg = LabelledCFG {
+                ast_map: &ty_cx.map,
+                cfg: &cfg,
+                name: format!("node_{}", code.id()),
+            };
+            let r = dot::render(&lcfg, &mut out);
+            return expand_err_details(r);
+        }
+        blocks::BlockCode(_) => {
+            ty_cx.sess.err("--pretty flowgraph with -Z flowgraph-print \
+                            annotations requires fn-like node id.");
+            return Ok(())
+        }
+        blocks::FnLikeCode(fn_like) => {
+            let fn_parts = FnPartsWithCFG::from_fn_like(&fn_like, &cfg);
+            let (bccx, analysis_data) =
+                borrowck::build_borrowck_dataflow_data_for_fn(ty_cx, fn_parts);
+
+            let lcfg = LabelledCFG {
+                ast_map: &ty_cx.map,
+                cfg: &cfg,
+                name: format!("node_{}", code.id()),
+            };
+            let lcfg = borrowck_dot::DataflowLabeller {
+                inner: lcfg,
+                variants: variants,
+                borrowck_ctxt: &bccx,
+                analysis_data: &analysis_data,
+            };
+            let r = dot::render(&lcfg, &mut out);
+            return expand_err_details(r);
+        }
+    }
 
     fn expand_err_details(r: io::IoResult<()>) -> io::IoResult<()> {
         r.map_err(|ioerr| {
diff --git a/src/librustc/middle/borrowck/graphviz.rs b/src/librustc/middle/borrowck/graphviz.rs
new file mode 100644
index 00000000000..e2ddb4a703a
--- /dev/null
+++ b/src/librustc/middle/borrowck/graphviz.rs
@@ -0,0 +1,148 @@
+// 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.
+
+//! This module provides linkage between rustc::middle::graph and
+//! libgraphviz traits, specialized to attaching borrowck analysis
+//! data to rendered labels.
+
+/// For clarity, rename the graphviz crate locally to dot.
+use dot = graphviz;
+pub use middle::cfg::graphviz::{Node, Edge};
+use cfg_dot = middle::cfg::graphviz;
+
+use middle::borrowck;
+use middle::borrowck::{BorrowckCtxt, LoanPath};
+use middle::cfg::{CFGIndex};
+use middle::dataflow::{DataFlowOperator, DataFlowContext, EntryOrExit};
+use middle::dataflow;
+
+use std::rc::Rc;
+use std::str;
+
+#[deriving(Show)]
+pub enum Variant {
+    Loans,
+    Moves,
+    Assigns,
+}
+
+impl Variant {
+    pub fn short_name(&self) -> &'static str {
+        match *self {
+            Loans   => "loans",
+            Moves   => "moves",
+            Assigns => "assigns",
+        }
+    }
+}
+
+pub struct DataflowLabeller<'a> {
+    pub inner: cfg_dot::LabelledCFG<'a>,
+    pub variants: Vec<Variant>,
+    pub borrowck_ctxt: &'a BorrowckCtxt<'a>,
+    pub analysis_data: &'a borrowck::AnalysisData<'a>,
+}
+
+impl<'a> DataflowLabeller<'a> {
+    fn dataflow_for(&self, e: EntryOrExit, n: &Node<'a>) -> String {
+        let id = n.val1().data.id;
+        debug!("dataflow_for({}, id={}) {}", e, id, self.variants);
+        let mut sets = "".to_string();
+        let mut seen_one = false;
+        for &variant in self.variants.iter() {
+            if seen_one { sets.push_str(" "); } else { seen_one = true; }
+            sets.push_str(variant.short_name());
+            sets.push_str(": ");
+            sets.push_str(self.dataflow_for_variant(e, n, variant).as_slice());
+        }
+        sets
+    }
+
+    fn dataflow_for_variant(&self, e: EntryOrExit, n: &Node, v: Variant) -> String {
+        let cfgidx = n.val0();
+        match v {
+            Loans   => self.dataflow_loans_for(e, cfgidx),
+            Moves   => self.dataflow_moves_for(e, cfgidx),
+            Assigns => self.dataflow_assigns_for(e, cfgidx),
+        }
+    }
+
+    fn build_set<O:DataFlowOperator>(&self,
+                                     e: EntryOrExit,
+                                     cfgidx: CFGIndex,
+                                     dfcx: &DataFlowContext<'a, O>,
+                                     to_lp: |uint| -> Rc<LoanPath>) -> String {
+        let mut saw_some = false;
+        let mut set = "{".to_string();
+        dfcx.each_bit_for_node(e, cfgidx, |index| {
+            let lp = to_lp(index);
+            if saw_some {
+                set.push_str(", ");
+            }
+            let loan_str = self.borrowck_ctxt.loan_path_to_string(&*lp);
+            set.push_str(loan_str.as_slice());
+            saw_some = true;
+            true
+        });
+        set.append("}")
+    }
+
+    fn dataflow_loans_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
+        let dfcx = &self.analysis_data.loans;
+        let loan_index_to_path = |loan_index| {
+            let all_loans = &self.analysis_data.all_loans;
+            all_loans.get(loan_index).loan_path()
+        };
+        self.build_set(e, cfgidx, dfcx, loan_index_to_path)
+    }
+
+    fn dataflow_moves_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
+        let dfcx = &self.analysis_data.move_data.dfcx_moves;
+        let move_index_to_path = |move_index| {
+            let move_data = &self.analysis_data.move_data.move_data;
+            let moves = move_data.moves.borrow();
+            let move = moves.get(move_index);
+            move_data.path_loan_path(move.path)
+        };
+        self.build_set(e, cfgidx, dfcx, move_index_to_path)
+    }
+
+    fn dataflow_assigns_for(&self, e: EntryOrExit, cfgidx: CFGIndex) -> String {
+        let dfcx = &self.analysis_data.move_data.dfcx_assign;
+        let assign_index_to_path = |assign_index| {
+            let move_data = &self.analysis_data.move_data.move_data;
+            let assignments = move_data.var_assignments.borrow();
+            let assignment = assignments.get(assign_index);
+            move_data.path_loan_path(assignment.path)
+        };
+        self.build_set(e, cfgidx, dfcx, assign_index_to_path)
+    }
+}
+
+impl<'a> dot::Labeller<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a> {
+    fn graph_id(&'a self) -> dot::Id<'a> { self.inner.graph_id() }
+    fn node_id(&'a self, n: &Node<'a>) -> dot::Id<'a> { self.inner.node_id(n) }
+    fn node_label(&'a self, n: &Node<'a>) -> dot::LabelText<'a> {
+        let prefix = self.dataflow_for(dataflow::Entry, n);
+        let suffix = self.dataflow_for(dataflow::Exit, n);
+        let inner_label = self.inner.node_label(n);
+        inner_label
+            .prefix_line(dot::LabelStr(str::Owned(prefix)))
+            .suffix_line(dot::LabelStr(str::Owned(suffix)))
+    }
+    fn edge_label(&'a self, e: &Edge<'a>) -> dot::LabelText<'a> { self.inner.edge_label(e) }
+}
+
+impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for DataflowLabeller<'a> {
+    fn nodes(&self) -> dot::Nodes<'a, Node<'a>> { self.inner.nodes() }
+    fn edges(&self) -> dot::Edges<'a, Edge<'a>> { self.inner.edges() }
+    fn source(&self, edge: &Edge<'a>) -> Node<'a> { self.inner.source(edge) }
+    fn target(&self, edge: &Edge<'a>) -> Node<'a> { self.inner.target(edge) }
+}
diff --git a/src/librustc/middle/borrowck/mod.rs b/src/librustc/middle/borrowck/mod.rs
index 426a1fbede5..77b3cfafa63 100644
--- a/src/librustc/middle/borrowck/mod.rs
+++ b/src/librustc/middle/borrowck/mod.rs
@@ -28,6 +28,7 @@ use std::gc::{Gc, GC};
 use std::string::String;
 use syntax::ast;
 use syntax::ast_map;
+use syntax::ast_map::blocks::{FnLikeNode, FnParts};
 use syntax::ast_util;
 use syntax::codemap::Span;
 use syntax::parse::token;
@@ -50,6 +51,8 @@ pub mod check_loans;
 
 pub mod gather_loans;
 
+pub mod graphviz;
+
 pub mod move_data;
 
 #[deriving(Clone)]
@@ -116,6 +119,13 @@ fn borrowck_item(this: &mut BorrowckCtxt, item: &ast::Item) {
     }
 }
 
+/// Collection of conclusions determined via borrow checker analyses.
+pub struct AnalysisData<'a> {
+    pub all_loans: Vec<Loan>,
+    pub loans: DataFlowContext<'a, LoanDataFlowOperator>,
+    pub move_data: move_data::FlowedMoveData<'a>,
+}
+
 fn borrowck_fn(this: &mut BorrowckCtxt,
                fk: &FnKind,
                decl: &ast::FnDecl,
@@ -123,18 +133,35 @@ fn borrowck_fn(this: &mut BorrowckCtxt,
                sp: Span,
                id: ast::NodeId) {
     debug!("borrowck_fn(id={})", id);
+    let cfg = cfg::CFG::new(this.tcx, body);
+    let AnalysisData { all_loans,
+                       loans: loan_dfcx,
+                       move_data:flowed_moves } =
+        build_borrowck_dataflow_data(this, fk, decl, &cfg, body, sp, id);
 
+    check_loans::check_loans(this, &loan_dfcx, flowed_moves,
+                             all_loans.as_slice(), decl, body);
+
+    visit::walk_fn(this, fk, decl, body, sp, ());
+}
+
+fn build_borrowck_dataflow_data<'a>(this: &mut BorrowckCtxt<'a>,
+                                    fk: &FnKind,
+                                    decl: &ast::FnDecl,
+                                    cfg: &cfg::CFG,
+                                    body: &ast::Block,
+                                    sp: Span,
+                                    id: ast::NodeId) -> AnalysisData<'a> {
     // Check the body of fn items.
     let id_range = ast_util::compute_id_range_for_fn_body(fk, decl, body, sp, id);
     let (all_loans, move_data) =
         gather_loans::gather_loans_in_fn(this, decl, body);
-    let cfg = cfg::CFG::new(this.tcx, body);
 
     let mut loan_dfcx =
         DataFlowContext::new(this.tcx,
                              "borrowck",
                              Some(decl),
-                             &cfg,
+                             cfg,
                              LoanDataFlowOperator,
                              id_range,
                              all_loans.len());
@@ -142,20 +169,57 @@ fn borrowck_fn(this: &mut BorrowckCtxt,
         loan_dfcx.add_gen(loan.gen_scope, loan_idx);
         loan_dfcx.add_kill(loan.kill_scope, loan_idx);
     }
-    loan_dfcx.add_kills_from_flow_exits(&cfg);
-    loan_dfcx.propagate(&cfg, body);
+    loan_dfcx.add_kills_from_flow_exits(cfg);
+    loan_dfcx.propagate(cfg, body);
 
     let flowed_moves = move_data::FlowedMoveData::new(move_data,
                                                       this.tcx,
-                                                      &cfg,
+                                                      cfg,
                                                       id_range,
                                                       decl,
                                                       body);
 
-    check_loans::check_loans(this, &loan_dfcx, flowed_moves,
-                             all_loans.as_slice(), decl, body);
+    AnalysisData { all_loans: all_loans,
+                   loans: loan_dfcx,
+                   move_data:flowed_moves }
+}
 
-    visit::walk_fn(this, fk, decl, body, sp, ());
+/// This and a `ty::ctxt` is all you need to run the dataflow analyses
+/// used in the borrow checker.
+pub struct FnPartsWithCFG<'a> {
+    pub fn_parts: FnParts<'a>,
+    pub cfg:  &'a cfg::CFG,
+}
+
+impl<'a> FnPartsWithCFG<'a> {
+    pub fn from_fn_like(f: &'a FnLikeNode,
+                        g: &'a cfg::CFG) -> FnPartsWithCFG<'a> {
+        FnPartsWithCFG { fn_parts: f.to_fn_parts(), cfg: g }
+    }
+}
+
+/// Accessor for introspective clients inspecting `AnalysisData` and
+/// the `BorrowckCtxt` itself , e.g. the flowgraph visualizer.
+pub fn build_borrowck_dataflow_data_for_fn<'a>(
+    tcx: &'a ty::ctxt,
+    input: FnPartsWithCFG<'a>) -> (BorrowckCtxt<'a>, AnalysisData<'a>) {
+
+    let mut bccx = BorrowckCtxt {
+        tcx: tcx,
+        stats: box(GC) BorrowStats {
+            loaned_paths_same: Cell::new(0),
+            loaned_paths_imm: Cell::new(0),
+            stable_paths: Cell::new(0),
+            guaranteed_paths: Cell::new(0),
+        }
+    };
+
+    let p = input.fn_parts;
+
+    let dataflow_data = build_borrowck_dataflow_data(
+        &mut bccx, &p.kind, p.decl, input.cfg, p.body, p.span, p.id);
+
+    (bccx, dataflow_data)
 }
 
 // ----------------------------------------------------------------------
@@ -198,6 +262,12 @@ pub struct Loan {
     cause: euv::LoanCause,
 }
 
+impl Loan {
+    pub fn loan_path(&self) -> Rc<LoanPath> {
+        self.loan_path.clone()
+    }
+}
+
 #[deriving(PartialEq, Eq, Hash)]
 pub enum LoanPath {
     LpVar(ast::NodeId),               // `x` in doc.rs
diff --git a/src/librustc/middle/borrowck/move_data.rs b/src/librustc/middle/borrowck/move_data.rs
index b61596908e6..a9c312fc0a4 100644
--- a/src/librustc/middle/borrowck/move_data.rs
+++ b/src/librustc/middle/borrowck/move_data.rs
@@ -189,7 +189,7 @@ impl MoveData {
         }
     }
 
-    fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath> {
+    pub fn path_loan_path(&self, index: MovePathIndex) -> Rc<LoanPath> {
         self.paths.borrow().get(index.get()).loan_path.clone()
     }
 
@@ -534,7 +534,7 @@ impl MoveData {
 impl<'a> FlowedMoveData<'a> {
     pub fn new(move_data: MoveData,
                tcx: &'a ty::ctxt,
-               cfg: &'a cfg::CFG,
+               cfg: &cfg::CFG,
                id_range: ast_util::IdRange,
                decl: &ast::FnDecl,
                body: &ast::Block)
diff --git a/src/librustc/middle/cfg/graphviz.rs b/src/librustc/middle/cfg/graphviz.rs
index 9f44f0babc7..e9bcdff070d 100644
--- a/src/librustc/middle/cfg/graphviz.rs
+++ b/src/librustc/middle/cfg/graphviz.rs
@@ -117,3 +117,4 @@ impl<'a> dot::GraphWalk<'a, Node<'a>, Edge<'a>> for LabelledCFG<'a>
     fn source(&self, edge: &Edge<'a>) -> Node<'a> { self.cfg.source(edge) }
     fn target(&self, edge: &Edge<'a>) -> Node<'a> { self.cfg.target(edge) }
 }
+
diff --git a/src/librustc/middle/dataflow.rs b/src/librustc/middle/dataflow.rs
index 7d9178162a6..b28c0158584 100644
--- a/src/librustc/middle/dataflow.rs
+++ b/src/librustc/middle/dataflow.rs
@@ -28,6 +28,9 @@ use syntax::visit;
 use syntax::print::{pp, pprust};
 use util::nodemap::NodeMap;
 
+#[deriving(Show)]
+pub enum EntryOrExit { Entry, Exit }
+
 #[deriving(Clone)]
 pub struct DataFlowContext<'a, O> {
     tcx: &'a ty::ctxt,
@@ -93,17 +96,18 @@ fn to_cfgidx_or_die(id: ast::NodeId, index: &NodeMap<CFGIndex>) -> CFGIndex {
 }
 
 impl<'a, O:DataFlowOperator> DataFlowContext<'a, O> {
-    fn has_bitset(&self, n: ast::NodeId) -> bool {
+    fn has_bitset_for_nodeid(&self, n: ast::NodeId) -> bool {
         assert!(n != ast::DUMMY_NODE_ID);
         match self.nodeid_to_index.find(&n) {
             None => false,
-            Some(&cfgidx) => {
-                let node_id = cfgidx.node_id();
-                node_id < self.index_to_bitset.len() &&
-                    self.index_to_bitset.get(node_id).is_some()
-            }
+            Some(&cfgidx) => self.has_bitset_for_cfgidx(cfgidx),
         }
     }
+    fn has_bitset_for_cfgidx(&self, cfgidx: CFGIndex) -> bool {
+        let node_id = cfgidx.node_id();
+        node_id < self.index_to_bitset.len() &&
+            self.index_to_bitset.get(node_id).is_some()
+    }
     fn get_bitset_index(&self, cfgidx: CFGIndex) -> uint {
         let node_id = cfgidx.node_id();
         self.index_to_bitset.get(node_id).unwrap()
@@ -160,7 +164,7 @@ impl<'a, O:DataFlowOperator> pprust::PpAnn for DataFlowContext<'a, O> {
             pprust::NodePat(pat) => pat.id
         };
 
-        if self.has_bitset(id) {
+        if self.has_bitset_for_nodeid(id) {
             let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index);
             let (start, end) = self.compute_id_range_frozen(cfgidx);
             let on_entry = self.on_entry.slice(start, end);
@@ -287,7 +291,7 @@ impl<'a, O:DataFlowOperator> DataFlowContext<'a, O> {
     }
 
     fn apply_gen_kill(&mut self, cfgidx: CFGIndex, bits: &mut [uint]) {
-        //! Applies the gen and kill sets for `id` to `bits`
+        //! Applies the gen and kill sets for `cfgidx` to `bits`
         debug!("{:s} apply_gen_kill(cfgidx={}, bits={}) [before]",
                self.analysis_name, cfgidx, mut_bits_to_string(bits));
         let (start, end) = self.compute_id_range(cfgidx);
@@ -300,6 +304,21 @@ impl<'a, O:DataFlowOperator> DataFlowContext<'a, O> {
                self.analysis_name, cfgidx, mut_bits_to_string(bits));
     }
 
+    fn apply_gen_kill_frozen(&self, cfgidx: CFGIndex, bits: &mut [uint]) {
+        //! Applies the gen and kill sets for `cfgidx` to `bits`
+        //! Only useful after `propagate()` has been called.
+        debug!("{:s} apply_gen_kill(cfgidx={}, bits={}) [before]",
+               self.analysis_name, cfgidx, mut_bits_to_string(bits));
+        let (start, end) = self.compute_id_range_frozen(cfgidx);
+        let gens = self.gens.slice(start, end);
+        bitwise(bits, gens, &Union);
+        let kills = self.kills.slice(start, end);
+        bitwise(bits, kills, &Subtract);
+
+        debug!("{:s} apply_gen_kill(cfgidx={}, bits={}) [after]",
+               self.analysis_name, cfgidx, mut_bits_to_string(bits));
+    }
+
     fn compute_id_range_frozen(&self, cfgidx: CFGIndex) -> (uint, uint) {
         let n = self.get_bitset_index(cfgidx);
         let start = n * self.words_per_id;
@@ -327,21 +346,45 @@ impl<'a, O:DataFlowOperator> DataFlowContext<'a, O> {
                                     -> bool {
         //! Iterates through each bit that is set on entry to `id`.
         //! Only useful after `propagate()` has been called.
-        if !self.has_bitset(id) {
+        if !self.has_bitset_for_nodeid(id) {
             return true;
         }
         let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index);
+        self.each_bit_for_node(Entry, cfgidx, f)
+    }
+
+    pub fn each_bit_for_node(&self,
+                             e: EntryOrExit,
+                             cfgidx: CFGIndex,
+                             f: |uint| -> bool)
+                             -> bool {
+        //! Iterates through each bit that is set on entry/exit to `cfgidx`.
+        //! Only useful after `propagate()` has been called.
+        if !self.has_bitset_for_cfgidx(cfgidx) {
+            return true;
+        }
         let (start, end) = self.compute_id_range_frozen(cfgidx);
         let on_entry = self.on_entry.slice(start, end);
-        debug!("{:s} each_bit_on_entry_frozen(id={:?}, on_entry={})",
-               self.analysis_name, id, bits_to_string(on_entry));
-        self.each_bit(on_entry, f)
+        let temp_bits;
+        let slice = match e {
+            Entry => on_entry,
+            Exit => {
+                let mut t = on_entry.to_owned();
+                self.apply_gen_kill_frozen(cfgidx, t.as_mut_slice());
+                temp_bits = t;
+                temp_bits.as_slice()
+            }
+        };
+        debug!("{:s} each_bit_for_node({}, cfgidx={}) bits={}",
+               self.analysis_name, e, cfgidx, bits_to_string(slice));
+        self.each_bit(slice, f)
     }
 
     pub fn each_gen_bit_frozen(&self, id: ast::NodeId, f: |uint| -> bool)
                                -> bool {
         //! Iterates through each bit in the gen set for `id`.
-        if !self.has_bitset(id) {
+        //! Only useful after `propagate()` has been called.
+        if !self.has_bitset_for_nodeid(id) {
             return true;
         }
         let cfgidx = to_cfgidx_or_die(id, &self.nodeid_to_index);
diff --git a/src/libsyntax/ast_map/blocks.rs b/src/libsyntax/ast_map/blocks.rs
new file mode 100644
index 00000000000..1280b884f11
--- /dev/null
+++ b/src/libsyntax/ast_map/blocks.rs
@@ -0,0 +1,218 @@
+// 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.
+
+//! This module provides a simplified abstraction for working with
+//! code blocks identified by their integer node-id.  In particular,
+//! it captures a common set of attributes that all "function-like
+//! things" (represented by `FnLike` instances) share.  For example,
+//! all `FnLike` instances have a type signature (be it explicit or
+//! inferred).  And all `FnLike` instances have a body, i.e. the code
+//! that is run when the function-like thing it represents is invoked.
+//!
+//! With the above abstraction in place, one can treat the program
+//! text as a collection of blocks of code (and most such blocks are
+//! nested within a uniquely determined `FnLike`), and users can ask
+//! for the `Code` associated with a particular NodeId.
+
+use abi;
+use ast::{P, Block, FnDecl, NodeId};
+use ast;
+use ast_map::{Node};
+use ast_map;
+use ast_util;
+use codemap::Span;
+use visit;
+
+/// An FnLikeNode is a Node that is like a fn, in that it has a decl
+/// and a body (as well as a NodeId, a span, etc).
+///
+/// More specifically, it is one of either:
+///   - A function item,
+///   - A closure expr (i.e. an ExprFnBlock or ExprProc), or
+///   - The default implementation for a trait method.
+///
+/// To construct one, use the `Code::from_node` function.
+pub struct FnLikeNode { node: ast_map::Node }
+
+/// MaybeFnLike wraps a method that indicates if an object
+/// corresponds to some FnLikeNode.
+pub trait MaybeFnLike { fn is_fn_like(&self) -> bool; }
+
+/// Components shared by fn-like things (fn items, methods, closures).
+pub struct FnParts<'a> {
+    pub decl: P<FnDecl>,
+    pub body: P<Block>,
+    pub kind: visit::FnKind<'a>,
+    pub span: Span,
+    pub id:   NodeId,
+}
+
+impl MaybeFnLike for ast::Item {
+    fn is_fn_like(&self) -> bool {
+        match self.node { ast::ItemFn(..) => true, _ => false, }
+    }
+}
+
+impl MaybeFnLike for ast::TraitMethod {
+    fn is_fn_like(&self) -> bool {
+        match *self { ast::Provided(_) => true, _ => false, }
+    }
+}
+
+impl MaybeFnLike for ast::Expr {
+    fn is_fn_like(&self) -> bool {
+        match self.node {
+            ast::ExprFnBlock(..) | ast::ExprProc(..) => true,
+            _ => false,
+        }
+    }
+}
+
+/// Carries either an FnLikeNode or a Block, as these are the two
+/// constructs that correspond to "code" (as in, something from which
+/// we can construct a control-flow graph).
+pub enum Code {
+    FnLikeCode(FnLikeNode),
+    BlockCode(P<Block>),
+}
+
+impl Code {
+    pub fn id(&self) -> ast::NodeId {
+        match *self {
+            FnLikeCode(node) => node.id(),
+            BlockCode(block) => block.id,
+        }
+    }
+
+    /// Attempts to construct a Code from presumed FnLike or Block node input.
+    pub fn from_node(node: Node) -> Option<Code> {
+        fn new(node: Node) -> FnLikeNode { FnLikeNode { node: node } }
+        match node {
+            ast_map::NodeItem(item) if item.is_fn_like() =>
+                Some(FnLikeCode(new(node))),
+            ast_map::NodeTraitMethod(tm) if tm.is_fn_like() =>
+                Some(FnLikeCode(new(node))),
+            ast_map::NodeMethod(_) =>
+                Some(FnLikeCode(new(node))),
+            ast_map::NodeExpr(e) if e.is_fn_like() =>
+                Some(FnLikeCode(new(node))),
+            ast_map::NodeBlock(block) =>
+                Some(BlockCode(block)),
+            _ =>
+                None,
+        }
+    }
+}
+
+/// These are all the components one can extract from a fn item for
+/// use when implementing FnLikeNode operations.
+struct ItemFnParts<'a> {
+    ident:    ast::Ident,
+    decl:     P<ast::FnDecl>,
+    style:    ast::FnStyle,
+    abi:      abi::Abi,
+    generics: &'a ast::Generics,
+    body:     P<Block>,
+    id:       ast::NodeId,
+    span:     Span
+}
+
+/// These are all the components one can extract from a closure expr
+/// for use when implementing FnLikeNode operations.
+struct ClosureParts {
+    decl: P<FnDecl>,
+    body: P<Block>,
+    id: NodeId,
+    span: Span
+}
+
+impl ClosureParts {
+    fn new(d: P<FnDecl>, b: P<Block>, id: NodeId, s: Span) -> ClosureParts {
+        ClosureParts { decl: d, body: b, id: id, span: s }
+    }
+}
+
+impl FnLikeNode {
+    pub fn to_fn_parts<'a>(&'a self) -> FnParts<'a> {
+        FnParts {
+            decl: self.decl(),
+            body: self.body(),
+            kind: self.kind(),
+            span: self.span(),
+            id:   self.id(),
+        }
+    }
+
+    pub fn body<'a>(&'a self) -> P<Block> {
+        self.handle(|i: ItemFnParts|     i.body,
+                    |m: &'a ast::Method| ast_util::method_body(m),
+                    |c: ClosureParts|    c.body)
+    }
+
+    pub fn decl<'a>(&'a self) -> P<FnDecl> {
+        self.handle(|i: ItemFnParts|     i.decl,
+                    |m: &'a ast::Method| ast_util::method_fn_decl(m),
+                    |c: ClosureParts|    c.decl)
+    }
+
+    pub fn span<'a>(&'a self) -> Span {
+        self.handle(|i: ItemFnParts|     i.span,
+                    |m: &'a ast::Method| m.span,
+                    |c: ClosureParts|    c.span)
+    }
+
+    pub fn id<'a>(&'a self) -> NodeId {
+        self.handle(|i: ItemFnParts|     i.id,
+                    |m: &'a ast::Method| m.id,
+                    |c: ClosureParts|    c.id)
+    }
+
+    pub fn kind<'a>(&'a self) -> visit::FnKind<'a> {
+        let item = |p: ItemFnParts<'a>| -> visit::FnKind<'a> {
+            visit::FkItemFn(p.ident, p.generics, p.style, p.abi)
+        };
+        let closure = |_: ClosureParts| {
+            visit::FkFnBlock
+        };
+        let method = |m: &'a ast::Method| {
+            visit::FkMethod(ast_util::method_ident(m), ast_util::method_generics(m), m)
+        };
+        self.handle(item, method, closure)
+    }
+
+    fn handle<'a, A>(&'a self,
+                     item_fn: |ItemFnParts<'a>| -> A,
+                     method: |&'a ast::Method| -> A,
+                     closure: |ClosureParts| -> A) -> A {
+        match self.node {
+            ast_map::NodeItem(ref i) => match i.node {
+                ast::ItemFn(decl, style, abi, ref generics, block) =>
+                    item_fn(ItemFnParts{
+                        ident: i.ident, decl: decl, style: style, body: block,
+                        generics: generics, abi: abi, id: i.id, span: i.span
+                    }),
+                _ => fail!("item FnLikeNode that is not fn-like"),
+            },
+            ast_map::NodeTraitMethod(ref t) => match **t {
+                ast::Provided(ref m) => method(&**m),
+                _ => fail!("trait method FnLikeNode that is not fn-like"),
+            },
+            ast_map::NodeMethod(ref m) => method(&**m),
+            ast_map::NodeExpr(ref e) => match e.node {
+                ast::ExprFnBlock(ref decl, ref block) =>
+                    closure(ClosureParts::new(*decl, *block, e.id, e.span)),
+                ast::ExprProc(ref decl, ref block) =>
+                    closure(ClosureParts::new(*decl, *block, e.id, e.span)),
+                _ => fail!("expr FnLikeNode that is not fn-like"),
+            },
+            _ => fail!("other FnLikeNode that is not fn-like"),
+        }
+    }
+}
diff --git a/src/libsyntax/ast_map.rs b/src/libsyntax/ast_map/mod.rs
similarity index 99%
rename from src/libsyntax/ast_map.rs
rename to src/libsyntax/ast_map/mod.rs
index b8a0a31f9c3..50e487b63db 100644
--- a/src/libsyntax/ast_map.rs
+++ b/src/libsyntax/ast_map/mod.rs
@@ -24,6 +24,8 @@ use std::gc::{Gc, GC};
 use std::iter;
 use std::slice;
 
+pub mod blocks;
+
 #[deriving(Clone, PartialEq)]
 pub enum PathElem {
     PathMod(Name),