about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorPatrick Walton <pcwalton@mimiga.net>2016-09-15 18:18:40 -0700
committerPatrick Walton <pcwalton@mimiga.net>2016-09-19 19:18:38 -0700
commit480287ec3b0260e26c8796506039c379bd7e0ead (patch)
treea31d9ae4975552192e6e3845775bc037a5361db3 /src
parent2e6a91812c64a507e3d2ab727824ff9cda8449fc (diff)
librustc: Implement def-use chains and trivial copy propagation on MIR.
This only supports trivial cases in which there is exactly one def and
one use.
Diffstat (limited to 'src')
-rw-r--r--src/librustc/mir/repr.rs46
-rw-r--r--src/librustc/mir/visit.rs108
-rw-r--r--src/librustc_borrowck/borrowck/mir/gather_moves.rs1
-rw-r--r--src/librustc_driver/driver.rs1
-rw-r--r--src/librustc_mir/def_use.rs197
-rw-r--r--src/librustc_mir/lib.rs2
-rw-r--r--src/librustc_mir/transform/copy_prop.rs180
-rw-r--r--src/librustc_mir/transform/mod.rs2
-rw-r--r--src/librustc_mir/transform/promote_consts.rs2
-rw-r--r--src/librustc_mir/transform/qualify_consts.rs5
-rw-r--r--src/librustc_trans/collector.rs2
-rw-r--r--src/librustc_trans/mir/analyze.rs4
-rw-r--r--src/test/codegen/refs.rs4
-rw-r--r--src/test/mir-opt/storage_ranges.rs38
14 files changed, 560 insertions, 32 deletions
diff --git a/src/librustc/mir/repr.rs b/src/librustc/mir/repr.rs
index 3ab8ed5bce2..be761c95b61 100644
--- a/src/librustc/mir/repr.rs
+++ b/src/librustc/mir/repr.rs
@@ -188,6 +188,24 @@ impl<'tcx> Mir<'tcx> {
         self.temp_decls.len() + 1
     }
 
+    pub fn format_local(&self, local: Local) -> String {
+        let mut index = local.index();
+        index = match index.checked_sub(self.arg_decls.len()) {
+            None => return format!("{:?}", Arg::new(index)),
+            Some(index) => index,
+        };
+        index = match index.checked_sub(self.var_decls.len()) {
+            None => return format!("{:?}", Var::new(index)),
+            Some(index) => index,
+        };
+        index = match index.checked_sub(self.temp_decls.len()) {
+            None => return format!("{:?}", Temp::new(index)),
+            Some(index) => index,
+        };
+        debug_assert!(index == 0);
+        return "ReturnPointer".to_string()
+    }
+
     /// Changes a statement to a nop. This is both faster than deleting instructions and avoids
     /// invalidating statement indices in `Location`s.
     pub fn make_statement_nop(&mut self, location: Location) {
@@ -844,6 +862,24 @@ impl<'tcx> Lvalue<'tcx> {
             elem: elem,
         }))
     }
+
+    pub fn from_local(mir: &Mir<'tcx>, local: Local) -> Lvalue<'tcx> {
+        let mut index = local.index();
+        index = match index.checked_sub(mir.arg_decls.len()) {
+            None => return Lvalue::Arg(Arg(index as u32)),
+            Some(index) => index,
+        };
+        index = match index.checked_sub(mir.var_decls.len()) {
+            None => return Lvalue::Var(Var(index as u32)),
+            Some(index) => index,
+        };
+        index = match index.checked_sub(mir.temp_decls.len()) {
+            None => return Lvalue::Temp(Temp(index as u32)),
+            Some(index) => index,
+        };
+        debug_assert!(index == 0);
+        Lvalue::ReturnPointer
+    }
 }
 
 impl<'tcx> Debug for Lvalue<'tcx> {
@@ -1278,3 +1314,13 @@ impl fmt::Debug for Location {
         write!(fmt, "{:?}[{}]", self.block, self.statement_index)
     }
 }
+
+impl Location {
+    pub fn dominates(&self, other: &Location, dominators: &Dominators<BasicBlock>) -> bool {
+        if self.block == other.block {
+            self.statement_index <= other.statement_index
+        } else {
+            dominators.is_dominated_by(other.block, self.block)
+        }
+    }
+}
diff --git a/src/librustc/mir/visit.rs b/src/librustc/mir/visit.rs
index 072faf79522..2c58d35973e 100644
--- a/src/librustc/mir/visit.rs
+++ b/src/librustc/mir/visit.rs
@@ -150,7 +150,7 @@ macro_rules! make_mir_visitor {
 
             fn visit_lvalue(&mut self,
                             lvalue: & $($mutability)* Lvalue<'tcx>,
-                            context: LvalueContext,
+                            context: LvalueContext<'tcx>,
                             location: Location) {
                 self.super_lvalue(lvalue, context, location);
             }
@@ -581,7 +581,7 @@ macro_rules! make_mir_visitor {
 
             fn super_lvalue(&mut self,
                             lvalue: & $($mutability)* Lvalue<'tcx>,
-                            context: LvalueContext,
+                            context: LvalueContext<'tcx>,
                             location: Location) {
                 match *lvalue {
                     Lvalue::Var(_) |
@@ -606,7 +606,12 @@ macro_rules! make_mir_visitor {
                     ref $($mutability)* base,
                     ref $($mutability)* elem,
                 } = *proj;
-                self.visit_lvalue(base, LvalueContext::Projection, location);
+                let context = if context.is_mutating_use() {
+                    LvalueContext::Projection(Mutability::Mut)
+                } else {
+                    LvalueContext::Projection(Mutability::Not)
+                };
+                self.visit_lvalue(base, context, location);
                 self.visit_projection_elem(elem, context, location);
             }
 
@@ -751,6 +756,21 @@ macro_rules! make_mir_visitor {
 
             fn super_const_usize(&mut self, _substs: & $($mutability)* ConstUsize) {
             }
+
+            // Convenience methods
+
+            fn visit_location(&mut self, mir: & $($mutability)* Mir<'tcx>, location: Location) {
+                let basic_block = & $($mutability)* mir[location.block];
+                if basic_block.statements.len() == location.statement_index {
+                    if let Some(ref $($mutability)* terminator) = basic_block.terminator {
+                        self.visit_terminator(location.block, terminator, location)
+                    }
+                } else {
+                    let statement = & $($mutability)*
+                        basic_block.statements[location.statement_index];
+                    self.visit_statement(location.block, statement, location)
+                }
+            }
         }
     }
 }
@@ -775,8 +795,20 @@ pub enum LvalueContext<'tcx> {
     // Being borrowed
     Borrow { region: &'tcx Region, kind: BorrowKind },
 
-    // Used as base for another lvalue, e.g. `x` in `x.y`
-    Projection,
+    // Used as base for another lvalue, e.g. `x` in `x.y`.
+    //
+    // The `Mutability` argument specifies whether the projection is being performed in order to
+    // (potentially) mutate the lvalue. For example, the projection `x.y` is marked as a mutation
+    // in these cases:
+    //
+    //     x.y = ...;
+    //     f(&mut x.y);
+    //
+    // But not in these cases:
+    //
+    //     z = x.y;
+    //     f(&x.y);
+    Projection(Mutability),
 
     // Consumed as part of an operand
     Consume,
@@ -785,3 +817,69 @@ pub enum LvalueContext<'tcx> {
     StorageLive,
     StorageDead,
 }
+
+impl<'tcx> LvalueContext<'tcx> {
+    /// Returns true if this lvalue context represents a drop.
+    pub fn is_drop(&self) -> bool {
+        match *self {
+            LvalueContext::Drop => true,
+            _ => false,
+        }
+    }
+
+    /// Returns true if this lvalue context represents a storage live or storage dead marker.
+    pub fn is_storage_marker(&self) -> bool {
+        match *self {
+            LvalueContext::StorageLive | LvalueContext::StorageDead => true,
+            _ => false,
+        }
+    }
+
+    /// Returns true if this lvalue context represents a storage live marker.
+    pub fn is_storage_live_marker(&self) -> bool {
+        match *self {
+            LvalueContext::StorageLive => true,
+            _ => false,
+        }
+    }
+
+    /// Returns true if this lvalue context represents a storage dead marker.
+    pub fn is_storage_dead_marker(&self) -> bool {
+        match *self {
+            LvalueContext::StorageDead => true,
+            _ => false,
+        }
+    }
+
+    /// Returns true if this lvalue context represents a use that potentially changes the value.
+    pub fn is_mutating_use(&self) -> bool {
+        match *self {
+            LvalueContext::Store | LvalueContext::Call |
+            LvalueContext::Borrow { kind: BorrowKind::Mut, .. } |
+            LvalueContext::Projection(Mutability::Mut) |
+            LvalueContext::Drop => true,
+            LvalueContext::Inspect |
+            LvalueContext::Borrow { kind: BorrowKind::Shared, .. } |
+            LvalueContext::Borrow { kind: BorrowKind::Unique, .. } |
+            LvalueContext::Projection(Mutability::Not) | LvalueContext::Consume |
+            LvalueContext::StorageLive | LvalueContext::StorageDead => false,
+        }
+    }
+
+    /// Returns true if this lvalue context represents a use that does not change the value.
+    pub fn is_nonmutating_use(&self) -> bool {
+        match *self {
+            LvalueContext::Inspect | LvalueContext::Borrow { kind: BorrowKind::Shared, .. } |
+            LvalueContext::Borrow { kind: BorrowKind::Unique, .. } |
+            LvalueContext::Projection(Mutability::Not) | LvalueContext::Consume => true,
+            LvalueContext::Borrow { kind: BorrowKind::Mut, .. } | LvalueContext::Store |
+            LvalueContext::Call | LvalueContext::Projection(Mutability::Mut) |
+            LvalueContext::Drop | LvalueContext::StorageLive | LvalueContext::StorageDead => false,
+        }
+    }
+
+    pub fn is_use(&self) -> bool {
+        self.is_mutating_use() || self.is_nonmutating_use()
+    }
+}
+
diff --git a/src/librustc_borrowck/borrowck/mir/gather_moves.rs b/src/librustc_borrowck/borrowck/mir/gather_moves.rs
index bd38f554dc9..6346c1e5889 100644
--- a/src/librustc_borrowck/borrowck/mir/gather_moves.rs
+++ b/src/librustc_borrowck/borrowck/mir/gather_moves.rs
@@ -438,6 +438,7 @@ impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
                 span_bug!(stmt.source_info.span,
                           "SetDiscriminant should not exist during borrowck");
             }
+            StatementKind::Nop => {}
         }
     }
 
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index c02c5f18b4c..55892801247 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -1028,6 +1028,7 @@ pub fn phase_4_translate_to_llvm<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
         // No lifetime analysis based on borrowing can be done from here on out.
         passes.push_pass(box mir::transform::instcombine::InstCombine::new());
         passes.push_pass(box mir::transform::deaggregator::Deaggregator);
+        passes.push_pass(box mir::transform::copy_prop::CopyPropagation);
 
         passes.push_pass(box mir::transform::add_call_guards::AddCallGuards);
         passes.push_pass(box mir::transform::dump_mir::Marker("PreTrans"));
diff --git a/src/librustc_mir/def_use.rs b/src/librustc_mir/def_use.rs
new file mode 100644
index 00000000000..7329a20c497
--- /dev/null
+++ b/src/librustc_mir/def_use.rs
@@ -0,0 +1,197 @@
+// Copyright 2016 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.
+
+//! Def-use analysis.
+
+use rustc::mir::repr::{Local, Location, Lvalue, Mir};
+use rustc::mir::visit::{LvalueContext, MutVisitor, Visitor};
+use rustc_data_structures::indexed_vec::{Idx, IndexVec};
+use std::marker::PhantomData;
+use std::mem;
+
+pub struct DefUseAnalysis<'tcx> {
+    info: IndexVec<Local, Info<'tcx>>,
+    mir_summary: MirSummary,
+}
+
+#[derive(Clone)]
+pub struct Info<'tcx> {
+    pub defs_and_uses: Vec<Use<'tcx>>,
+}
+
+#[derive(Clone)]
+pub struct Use<'tcx> {
+    pub context: LvalueContext<'tcx>,
+    pub location: Location,
+}
+
+impl<'tcx> DefUseAnalysis<'tcx> {
+    pub fn new(mir: &Mir<'tcx>) -> DefUseAnalysis<'tcx> {
+        DefUseAnalysis {
+            info: IndexVec::from_elem_n(Info::new(), mir.count_locals()),
+            mir_summary: MirSummary::new(mir),
+        }
+    }
+
+    pub fn analyze(&mut self, mir: &Mir<'tcx>) {
+        let mut finder = DefUseFinder {
+            info: mem::replace(&mut self.info, IndexVec::new()),
+            mir_summary: self.mir_summary,
+        };
+        finder.visit_mir(mir);
+        self.info = finder.info
+    }
+
+    pub fn local_info(&self, local: Local) -> &Info<'tcx> {
+        &self.info[local]
+    }
+
+    pub fn local_info_mut(&mut self, local: Local) -> &mut Info<'tcx> {
+        &mut self.info[local]
+    }
+
+    fn mutate_defs_and_uses<F>(&self, local: Local, mir: &mut Mir<'tcx>, mut callback: F)
+                               where F: for<'a> FnMut(&'a mut Lvalue<'tcx>,
+                                                      LvalueContext<'tcx>,
+                                                      Location) {
+        for lvalue_use in &self.info[local].defs_and_uses {
+            MutateUseVisitor::new(local,
+                                  &mut callback,
+                                  self.mir_summary,
+                                  mir).visit_location(mir, lvalue_use.location)
+        }
+    }
+
+    /// FIXME(pcwalton): This should update the def-use chains.
+    pub fn replace_all_defs_and_uses_with(&self,
+                                          local: Local,
+                                          mir: &mut Mir<'tcx>,
+                                          new_lvalue: Lvalue<'tcx>) {
+        self.mutate_defs_and_uses(local, mir, |lvalue, _, _| *lvalue = new_lvalue.clone())
+    }
+}
+
+struct DefUseFinder<'tcx> {
+    info: IndexVec<Local, Info<'tcx>>,
+    mir_summary: MirSummary,
+}
+
+impl<'tcx> DefUseFinder<'tcx> {
+    fn lvalue_mut_info(&mut self, lvalue: &Lvalue<'tcx>) -> Option<&mut Info<'tcx>> {
+        let info = &mut self.info;
+        self.mir_summary.local_index(lvalue).map(move |local| &mut info[local])
+    }
+}
+
+impl<'tcx> Visitor<'tcx> for DefUseFinder<'tcx> {
+    fn visit_lvalue(&mut self,
+                    lvalue: &Lvalue<'tcx>,
+                    context: LvalueContext<'tcx>,
+                    location: Location) {
+        if let Some(ref mut info) = self.lvalue_mut_info(lvalue) {
+            info.defs_and_uses.push(Use {
+                context: context,
+                location: location,
+            })
+        }
+        self.super_lvalue(lvalue, context, location)
+    }
+}
+
+impl<'tcx> Info<'tcx> {
+    fn new() -> Info<'tcx> {
+        Info {
+            defs_and_uses: vec![],
+        }
+    }
+
+    pub fn def_count(&self) -> usize {
+        self.defs_and_uses.iter().filter(|lvalue_use| lvalue_use.context.is_mutating_use()).count()
+    }
+
+    pub fn def_count_not_including_drop(&self) -> usize {
+        self.defs_and_uses.iter().filter(|lvalue_use| {
+            lvalue_use.context.is_mutating_use() && !lvalue_use.context.is_drop()
+        }).count()
+    }
+
+    pub fn use_count(&self) -> usize {
+        self.defs_and_uses.iter().filter(|lvalue_use| {
+            lvalue_use.context.is_nonmutating_use()
+        }).count()
+    }
+}
+
+struct MutateUseVisitor<'tcx, F> {
+    query: Local,
+    callback: F,
+    mir_summary: MirSummary,
+    phantom: PhantomData<&'tcx ()>,
+}
+
+impl<'tcx, F> MutateUseVisitor<'tcx, F> {
+    fn new(query: Local, callback: F, mir_summary: MirSummary, _: &Mir<'tcx>)
+           -> MutateUseVisitor<'tcx, F>
+           where F: for<'a> FnMut(&'a mut Lvalue<'tcx>, LvalueContext<'tcx>, Location) {
+        MutateUseVisitor {
+            query: query,
+            callback: callback,
+            mir_summary: mir_summary,
+            phantom: PhantomData,
+        }
+    }
+}
+
+impl<'tcx, F> MutVisitor<'tcx> for MutateUseVisitor<'tcx, F>
+              where F: for<'a> FnMut(&'a mut Lvalue<'tcx>, LvalueContext<'tcx>, Location) {
+    fn visit_lvalue(&mut self,
+                    lvalue: &mut Lvalue<'tcx>,
+                    context: LvalueContext<'tcx>,
+                    location: Location) {
+        if self.mir_summary.local_index(lvalue) == Some(self.query) {
+            (self.callback)(lvalue, context, location)
+        }
+        self.super_lvalue(lvalue, context, location)
+    }
+}
+
+/// A small structure that enables various metadata of the MIR to be queried
+/// without a reference to the MIR itself.
+#[derive(Clone, Copy)]
+struct MirSummary {
+    arg_count: usize,
+    var_count: usize,
+    temp_count: usize,
+}
+
+impl MirSummary {
+    fn new(mir: &Mir) -> MirSummary {
+        MirSummary {
+            arg_count: mir.arg_decls.len(),
+            var_count: mir.var_decls.len(),
+            temp_count: mir.temp_decls.len(),
+        }
+    }
+
+    fn local_index<'tcx>(&self, lvalue: &Lvalue<'tcx>) -> Option<Local> {
+        match *lvalue {
+            Lvalue::Arg(arg) => Some(Local::new(arg.index())),
+            Lvalue::Var(var) => Some(Local::new(var.index() + self.arg_count)),
+            Lvalue::Temp(temp) => {
+                Some(Local::new(temp.index() + self.arg_count + self.var_count))
+            }
+            Lvalue::ReturnPointer => {
+                Some(Local::new(self.arg_count + self.var_count + self.temp_count))
+            }
+            _ => None,
+        }
+    }
+}
+
diff --git a/src/librustc_mir/lib.rs b/src/librustc_mir/lib.rs
index f580ceeee5d..12f1eb8535a 100644
--- a/src/librustc_mir/lib.rs
+++ b/src/librustc_mir/lib.rs
@@ -46,8 +46,10 @@ extern crate rustc_const_eval;
 pub mod diagnostics;
 
 pub mod build;
+pub mod def_use;
 pub mod graphviz;
 mod hair;
 pub mod mir_map;
 pub mod pretty;
 pub mod transform;
+
diff --git a/src/librustc_mir/transform/copy_prop.rs b/src/librustc_mir/transform/copy_prop.rs
new file mode 100644
index 00000000000..33f3d6d8842
--- /dev/null
+++ b/src/librustc_mir/transform/copy_prop.rs
@@ -0,0 +1,180 @@
+// Copyright 2016 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.
+
+//! Trivial copy propagation pass.
+//!
+//! This uses def-use analysis to remove values that have exactly one def and one use, which must
+//! be an assignment.
+//!
+//! To give an example, we look for patterns that look like:
+//!
+//!     DEST = SRC
+//!     ...
+//!     USE(DEST)
+//!
+//! where `DEST` and `SRC` are both locals of some form. We replace that with:
+//!
+//!     NOP
+//!     ...
+//!     USE(SRC)
+//!
+//! The assignment `DEST = SRC` must be (a) the only mutation of `DEST` and (b) the only
+//! (non-mutating) use of `SRC`. These restrictions are conservative and may be relaxed in the
+//! future.
+
+use def_use::DefUseAnalysis;
+use rustc::mir::repr::{Local, Lvalue, Mir, Operand, Rvalue, StatementKind};
+use rustc::mir::transform::{MirPass, MirSource, Pass};
+use rustc::ty::TyCtxt;
+use rustc_data_structures::indexed_vec::Idx;
+
+pub struct CopyPropagation;
+
+impl Pass for CopyPropagation {}
+
+impl<'tcx> MirPass<'tcx> for CopyPropagation {
+    fn run_pass<'a>(&mut self, _: TyCtxt<'a, 'tcx, 'tcx>, _: MirSource, mir: &mut Mir<'tcx>) {
+        loop {
+            let mut def_use_analysis = DefUseAnalysis::new(mir);
+            def_use_analysis.analyze(mir);
+
+            let mut changed = false;
+            for dest_local_index in 0..mir.count_locals() {
+                let dest_local = Local::new(dest_local_index);
+                debug!("Considering destination local: {}", mir.format_local(dest_local));
+
+                let src_local;
+                let location;
+                {
+                    // The destination must have exactly one def.
+                    let dest_use_info = def_use_analysis.local_info(dest_local);
+                    let dest_def_count = dest_use_info.def_count_not_including_drop();
+                    if dest_def_count == 0 {
+                        debug!("  Can't copy-propagate local: dest {} undefined",
+                               mir.format_local(dest_local));
+                        continue
+                    }
+                    if dest_def_count > 1 {
+                        debug!("  Can't copy-propagate local: dest {} defined {} times",
+                               mir.format_local(dest_local),
+                               dest_use_info.def_count());
+                        continue
+                    }
+                    if dest_use_info.use_count() == 0 {
+                        debug!("  Can't copy-propagate local: dest {} unused",
+                               mir.format_local(dest_local));
+                        continue
+                    }
+                    let dest_lvalue_def = dest_use_info.defs_and_uses.iter().filter(|lvalue_def| {
+                        lvalue_def.context.is_mutating_use() && !lvalue_def.context.is_drop()
+                    }).next().unwrap();
+                    location = dest_lvalue_def.location;
+
+                    let basic_block = &mir[location.block];
+                    let statement_index = location.statement_index;
+                    let statement = match basic_block.statements.get(statement_index) {
+                        Some(statement) => statement,
+                        None => {
+                            debug!("  Can't copy-propagate local: used in terminator");
+                            continue
+                        }
+                    };
+
+                    // That use of the source must be an assignment.
+                    let src_lvalue = match statement.kind {
+                        StatementKind::Assign(
+                                ref dest_lvalue,
+                                Rvalue::Use(Operand::Consume(ref src_lvalue)))
+                                if Some(dest_local) == mir.local_index(dest_lvalue) => {
+                            src_lvalue
+                        }
+                        _ => {
+                            debug!("  Can't copy-propagate local: source use is not an \
+                                    assignment");
+                            continue
+                        }
+                    };
+                    src_local = match mir.local_index(src_lvalue) {
+                        Some(src_local) => src_local,
+                        None => {
+                            debug!("  Can't copy-propagate local: source is not a local");
+                            continue
+                        }
+                    };
+
+                    // There must be exactly one use of the source used in a statement (not in a
+                    // terminator).
+                    let src_use_info = def_use_analysis.local_info(src_local);
+                    let src_use_count = src_use_info.use_count();
+                    if src_use_count == 0 {
+                        debug!("  Can't copy-propagate local: no uses");
+                        continue
+                    }
+                    if src_use_count != 1 {
+                        debug!("  Can't copy-propagate local: {} uses", src_use_info.use_count());
+                        continue
+                    }
+
+                    // Verify that the source doesn't change in between. This is done
+                    // conservatively for now, by ensuring that the source has exactly one
+                    // mutation. The goal is to prevent things like:
+                    //
+                    //     DEST = SRC;
+                    //     SRC = X;
+                    //     USE(DEST);
+                    //
+                    // From being misoptimized into:
+                    //
+                    //     SRC = X;
+                    //     USE(SRC);
+                    let src_def_count = src_use_info.def_count_not_including_drop();
+                    if src_def_count != 1 {
+                        debug!("  Can't copy-propagate local: {} defs of src",
+                               src_use_info.def_count_not_including_drop());
+                        continue
+                    }
+                }
+
+                // If all checks passed, then we can eliminate the destination and the assignment.
+                //
+                // First, remove all markers.
+                //
+                // FIXME(pcwalton): Don't do this. Merge live ranges instead.
+                debug!("  Replacing all uses of {}", mir.format_local(dest_local));
+                for lvalue_use in &def_use_analysis.local_info(dest_local).defs_and_uses {
+                    if lvalue_use.context.is_storage_marker() {
+                        mir.make_statement_nop(lvalue_use.location)
+                    }
+                }
+                for lvalue_use in &def_use_analysis.local_info(src_local).defs_and_uses {
+                    if lvalue_use.context.is_storage_marker() {
+                        mir.make_statement_nop(lvalue_use.location)
+                    }
+                }
+
+                // Now replace all uses of the destination local with the source local.
+                let src_lvalue = Lvalue::from_local(mir, src_local);
+                def_use_analysis.replace_all_defs_and_uses_with(dest_local, mir, src_lvalue);
+
+                // Finally, zap the now-useless assignment instruction.
+                mir.make_statement_nop(location);
+
+                changed = true;
+                // FIXME(pcwalton): Update the use-def chains to delete the instructions instead of
+                // regenerating the chains.
+                break
+            }
+            if !changed {
+                break
+            }
+        }
+    }
+}
+
diff --git a/src/librustc_mir/transform/mod.rs b/src/librustc_mir/transform/mod.rs
index e99b7a976e3..7bcb89b5895 100644
--- a/src/librustc_mir/transform/mod.rs
+++ b/src/librustc_mir/transform/mod.rs
@@ -19,4 +19,4 @@ pub mod qualify_consts;
 pub mod dump_mir;
 pub mod deaggregator;
 pub mod instcombine;
-
+pub mod copy_prop;
diff --git a/src/librustc_mir/transform/promote_consts.rs b/src/librustc_mir/transform/promote_consts.rs
index f864f1678f2..57de68fce1d 100644
--- a/src/librustc_mir/transform/promote_consts.rs
+++ b/src/librustc_mir/transform/promote_consts.rs
@@ -328,7 +328,7 @@ impl<'a, 'tcx> Promoter<'a, 'tcx> {
 impl<'a, 'tcx> MutVisitor<'tcx> for Promoter<'a, 'tcx> {
     fn visit_lvalue(&mut self,
                     lvalue: &mut Lvalue<'tcx>,
-                    context: LvalueContext,
+                    context: LvalueContext<'tcx>,
                     location: Location) {
         if let Lvalue::Temp(ref mut temp) = *lvalue {
             *temp = self.promote_temp(*temp);
diff --git a/src/librustc_mir/transform/qualify_consts.rs b/src/librustc_mir/transform/qualify_consts.rs
index 9f162aabe0c..c3a22853f84 100644
--- a/src/librustc_mir/transform/qualify_consts.rs
+++ b/src/librustc_mir/transform/qualify_consts.rs
@@ -475,7 +475,10 @@ impl<'a, 'tcx> Qualifier<'a, 'tcx, 'tcx> {
 /// For functions (constant or not), it also records
 /// candidates for promotion in promotion_candidates.
 impl<'a, 'tcx> Visitor<'tcx> for Qualifier<'a, 'tcx, 'tcx> {
-    fn visit_lvalue(&mut self, lvalue: &Lvalue<'tcx>, context: LvalueContext, location: Location) {
+    fn visit_lvalue(&mut self,
+                    lvalue: &Lvalue<'tcx>,
+                    context: LvalueContext<'tcx>,
+                    location: Location) {
         match *lvalue {
             Lvalue::Arg(_) => {
                 self.add(Qualif::FN_ARGUMENT);
diff --git a/src/librustc_trans/collector.rs b/src/librustc_trans/collector.rs
index a58de71ca41..6648944540e 100644
--- a/src/librustc_trans/collector.rs
+++ b/src/librustc_trans/collector.rs
@@ -523,7 +523,7 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> {
 
     fn visit_lvalue(&mut self,
                     lvalue: &mir::Lvalue<'tcx>,
-                    context: mir_visit::LvalueContext,
+                    context: mir_visit::LvalueContext<'tcx>,
                     location: Location) {
         debug!("visiting lvalue {:?}", *lvalue);
 
diff --git a/src/librustc_trans/mir/analyze.rs b/src/librustc_trans/mir/analyze.rs
index e13da253102..5de59b9f6bd 100644
--- a/src/librustc_trans/mir/analyze.rs
+++ b/src/librustc_trans/mir/analyze.rs
@@ -147,7 +147,7 @@ impl<'mir, 'bcx, 'tcx> Visitor<'tcx> for LocalAnalyzer<'mir, 'bcx, 'tcx> {
 
     fn visit_lvalue(&mut self,
                     lvalue: &mir::Lvalue<'tcx>,
-                    context: LvalueContext,
+                    context: LvalueContext<'tcx>,
                     location: Location) {
         debug!("visit_lvalue(lvalue={:?}, context={:?})", lvalue, context);
 
@@ -180,7 +180,7 @@ impl<'mir, 'bcx, 'tcx> Visitor<'tcx> for LocalAnalyzer<'mir, 'bcx, 'tcx> {
                 LvalueContext::Store |
                 LvalueContext::Inspect |
                 LvalueContext::Borrow { .. } |
-                LvalueContext::Projection => {
+                LvalueContext::Projection(..) => {
                     self.mark_as_lvalue(index);
                 }
 
diff --git a/src/test/codegen/refs.rs b/src/test/codegen/refs.rs
index 49ed2229fcd..891ca03cc4d 100644
--- a/src/test/codegen/refs.rs
+++ b/src/test/codegen/refs.rs
@@ -23,9 +23,9 @@ fn helper(_: usize) {
 pub fn ref_dst(s: &[u8]) {
     // We used to generate an extra alloca and memcpy to ref the dst, so check that we copy
     // directly to the alloca for "x"
-// CHECK: [[X0:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %x, i32 0, i32 0
+// CHECK: [[X0:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %s, i32 0, i32 0
 // CHECK: store i8* %0, i8** [[X0]]
-// CHECK: [[X1:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %x, i32 0, i32 1
+// CHECK: [[X1:%[0-9]+]] = getelementptr {{.*}} { i8*, [[USIZE]] }* %s, i32 0, i32 1
 // CHECK: store [[USIZE]] %1, [[USIZE]]* [[X1]]
 
     let x = &*s;
diff --git a/src/test/mir-opt/storage_ranges.rs b/src/test/mir-opt/storage_ranges.rs
index f93447b642a..8782dcf8898 100644
--- a/src/test/mir-opt/storage_ranges.rs
+++ b/src/test/mir-opt/storage_ranges.rs
@@ -21,27 +21,27 @@ fn main() {
 // END RUST SOURCE
 // START rustc.node4.PreTrans.after.mir
 //     bb0: {
-//         StorageLive(var0);               // scope 0 at storage_ranges.rs:12:9: 12:10
-//         var0 = const 0i32;               // scope 0 at storage_ranges.rs:12:13: 12:14
-//         StorageLive(var1);               // scope 1 at storage_ranges.rs:14:13: 14:14
-//         StorageLive(tmp1);               // scope 1 at storage_ranges.rs:14:18: 14:25
-//         StorageLive(tmp2);               // scope 1 at storage_ranges.rs:14:23: 14:24
-//         tmp2 = var0;                     // scope 1 at storage_ranges.rs:14:23: 14:24
-//         tmp1 = std::option::Option<i32>::Some(tmp2,); // scope 1 at storage_ranges.rs:14:18: 14:25
-//         var1 = &tmp1;                    // scope 1 at storage_ranges.rs:14:17: 14:25
-//         StorageDead(tmp2);               // scope 1 at storage_ranges.rs:14:23: 14:24
-//         tmp0 = ();                       // scope 2 at storage_ranges.rs:13:5: 15:6
-//         StorageDead(tmp1);               // scope 1 at storage_ranges.rs:14:18: 14:25
-//         StorageDead(var1);               // scope 1 at storage_ranges.rs:14:13: 14:14
-//         StorageLive(var2);               // scope 1 at storage_ranges.rs:16:9: 16:10
-//         var2 = const 1i32;               // scope 1 at storage_ranges.rs:16:13: 16:14
-//         return = ();                     // scope 3 at storage_ranges.rs:11:11: 17:2
-//         StorageDead(var2);               // scope 1 at storage_ranges.rs:16:9: 16:10
-//         StorageDead(var0);               // scope 0 at storage_ranges.rs:12:9: 12:10
-//         goto -> bb1;                     // scope 0 at storage_ranges.rs:11:1: 17:2
+//         nop;                             // scope 0 at storage_ranges.rs:14:9: 14:10
+//         var0 = const 0i32;               // scope 0 at storage_ranges.rs:14:13: 14:14
+//         StorageLive(var1);               // scope 1 at storage_ranges.rs:16:13: 16:14
+//         StorageLive(tmp1);               // scope 1 at storage_ranges.rs:16:18: 16:25
+//         nop;                             // scope 1 at storage_ranges.rs:16:23: 16:24
+//         nop;                             // scope 1 at storage_ranges.rs:16:23: 16:24
+//         tmp1 = std::option::Option<i32>::Some(var0,); // scope 1 at storage_ranges.rs:16:18: 16:25
+//         var1 = &tmp1;                    // scope 1 at storage_ranges.rs:16:17: 16:25
+//         nop;                             // scope 1 at storage_ranges.rs:16:23: 16:24
+//         tmp0 = ();                       // scope 2 at storage_ranges.rs:15:5: 17:6
+//         StorageDead(tmp1);               // scope 1 at storage_ranges.rs:16:18: 16:25
+//         StorageDead(var1);               // scope 1 at storage_ranges.rs:16:13: 16:14
+//         StorageLive(var2);               // scope 1 at storage_ranges.rs:18:9: 18:10
+//         var2 = const 1i32;               // scope 1 at storage_ranges.rs:18:13: 18:14
+//         return = ();                     // scope 3 at storage_ranges.rs:13:11: 19:2
+//         StorageDead(var2);               // scope 1 at storage_ranges.rs:18:9: 18:10
+//         nop;                             // scope 0 at storage_ranges.rs:14:9: 14:10
+//         goto -> bb1;                     // scope 0 at storage_ranges.rs:13:1: 19:2
 //     }
 //
 //     bb1: {
-//         return;                          // scope 0 at storage_ranges.rs:11:1: 17:2
+//         return;                          // scope 0 at storage_ranges.rs:13:1: 19:2
 //     }
 // END rustc.node4.PreTrans.after.mir