about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJannis Christopher Köhl <mail@koehl.dev>2022-08-25 16:43:46 +0000
committerJannis Christopher Köhl <mail@koehl.dev>2022-11-07 10:35:08 +0100
commit4f9c30fb676482a5148f28c0728b5280246a9886 (patch)
tree55564521204033dbfceb6cffcdc04dfeac266546
parent9b735a7132acd58b3bd34c084e9ca5b4ca7450a2 (diff)
downloadrust-4f9c30fb676482a5148f28c0728b5280246a9886.tar.gz
rust-4f9c30fb676482a5148f28c0728b5280246a9886.zip
Add initial version of value analysis and dataflow constant propagation
-rw-r--r--compiler/rustc_graphviz/src/lib.rs6
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/graphviz.rs5
-rw-r--r--compiler/rustc_mir_dataflow/src/lib.rs1
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs677
-rw-r--r--compiler/rustc_mir_transform/src/dataflow_const_prop.rs490
-rw-r--r--compiler/rustc_mir_transform/src/lib.rs2
-rw-r--r--src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff41
-rw-r--r--src/test/mir-opt/dataflow-const-prop/cast.rs7
-rw-r--r--src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff12
-rw-r--r--src/test/mir-opt/dataflow-const-prop/cell.rs6
-rw-r--r--src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff81
-rw-r--r--src/test/mir-opt/dataflow-const-prop/checked.rs13
-rw-r--r--src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff118
-rw-r--r--src/test/mir-opt/dataflow-const-prop/if.rs12
-rw-r--r--src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff29
-rw-r--r--src/test/mir-opt/dataflow-const-prop/promoted.rs7
-rw-r--r--src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff133
-rw-r--r--src/test/mir-opt/dataflow-const-prop/recursive.rs23
-rw-r--r--src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff89
-rw-r--r--src/test/mir-opt/dataflow-const-prop/ref.rs9
-rw-r--r--src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff77
-rw-r--r--src/test/mir-opt/dataflow-const-prop/ref_mut.rs15
-rw-r--r--src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff40
-rw-r--r--src/test/mir-opt/dataflow-const-prop/terminator.rs9
-rw-r--r--src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff38
-rw-r--r--src/test/mir-opt/dataflow-const-prop/unnamed.rs10
26 files changed, 1948 insertions, 2 deletions
diff --git a/compiler/rustc_graphviz/src/lib.rs b/compiler/rustc_graphviz/src/lib.rs
index 3c1bb553266..401d3f6689c 100644
--- a/compiler/rustc_graphviz/src/lib.rs
+++ b/compiler/rustc_graphviz/src/lib.rs
@@ -471,7 +471,11 @@ pub trait Labeller<'a> {
 /// Escape tags in such a way that it is suitable for inclusion in a
 /// Graphviz HTML label.
 pub fn escape_html(s: &str) -> String {
-    s.replace('&', "&amp;").replace('\"', "&quot;").replace('<', "&lt;").replace('>', "&gt;")
+    s.replace('&', "&amp;")
+        .replace('\"', "&quot;")
+        .replace('<', "&lt;")
+        .replace('>', "&gt;")
+        .replace('\n', "<br align=\"left\"/>")
 }
 
 impl<'a> LabelText<'a> {
diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
index 579fe68a149..c9d5601f207 100644
--- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
@@ -475,7 +475,10 @@ where
                 r#"<td colspan="{colspan}" {fmt} align="left">{state}</td>"#,
                 colspan = this.style.num_state_columns(),
                 fmt = fmt,
-                state = format!("{:?}", DebugWithAdapter { this: state, ctxt: analysis }),
+                state = dot::escape_html(&format!(
+                    "{:?}",
+                    DebugWithAdapter { this: state, ctxt: analysis }
+                )),
             )
         })
     }
diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs
index b471d04fd60..7f40cfca32f 100644
--- a/compiler/rustc_mir_dataflow/src/lib.rs
+++ b/compiler/rustc_mir_dataflow/src/lib.rs
@@ -41,6 +41,7 @@ pub mod move_paths;
 pub mod rustc_peek;
 pub mod storage;
 pub mod un_derefer;
+pub mod value_analysis;
 
 pub(crate) mod indexes {
     pub(crate) use super::move_paths::MovePathIndex;
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
new file mode 100644
index 00000000000..1dcea430a0f
--- /dev/null
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -0,0 +1,677 @@
+//! This module provides a framework on top of the normal MIR dataflow framework to simplify the
+//! implementation of analyses that track the values stored in places of interest.
+//!
+//! The default methods of [`ValueAnalysis`] (prefixed with `super_` instead of `handle_`)
+//! provide some behavior that should be valid for all abstract domains that are based only on the
+//! value stored in a certain place. On top of these default rules, an implementation should
+//! override some of the `handle_` methods. For an example, see `ConstAnalysis`.
+//!
+//! An implementation must also provide a [`Map`]. Before the anaylsis begins, all places that
+//! should be tracked during the analysis must be registered. The set of tracked places cannot be
+//! changed during the analysis.
+
+use std::fmt::{Debug, Formatter};
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_index::vec::IndexVec;
+use rustc_middle::mir::*;
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_target::abi::VariantIdx;
+
+use crate::{
+    fmt::DebugWithContext, lattice::FlatSet, Analysis, AnalysisDomain, CallReturnPlaces,
+    JoinSemiLattice, SwitchIntEdgeEffects,
+};
+
+pub trait ValueAnalysis<'tcx> {
+    /// For each place of interest, the analysis tracks a value of the given type.
+    type Value: Clone + JoinSemiLattice + HasBottom + HasTop;
+
+    const NAME: &'static str;
+
+    fn map(&self) -> &Map;
+
+    fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
+        self.super_statement(statement, state)
+    }
+
+    fn super_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
+        match &statement.kind {
+            StatementKind::Assign(box (place, rvalue)) => {
+                self.handle_assign(*place, rvalue, state);
+            }
+            StatementKind::SetDiscriminant { .. } => {
+                // Could tread this as writing a constant to a pseudo-place.
+            }
+            StatementKind::CopyNonOverlapping(..) => {
+                // FIXME: What to do here?
+            }
+            StatementKind::StorageLive(..)
+            | StatementKind::StorageDead(..)
+            | StatementKind::Deinit(_) => {
+                // Could perhaps use these.
+            }
+            StatementKind::Nop
+            | StatementKind::Retag(..)
+            | StatementKind::FakeRead(..)
+            | StatementKind::Coverage(..)
+            | StatementKind::AscribeUserType(..) => (),
+        }
+    }
+
+    fn handle_assign(
+        &self,
+        target: Place<'tcx>,
+        rvalue: &Rvalue<'tcx>,
+        state: &mut State<Self::Value>,
+    ) {
+        self.super_assign(target, rvalue, state)
+    }
+
+    fn super_assign(
+        &self,
+        target: Place<'tcx>,
+        rvalue: &Rvalue<'tcx>,
+        state: &mut State<Self::Value>,
+    ) {
+        match rvalue {
+            Rvalue::Ref(_, BorrowKind::Shared, place) => {
+                let target_deref = self
+                    .map()
+                    .find(target.as_ref())
+                    .and_then(|target| self.map().apply_elem(target, ProjElem::Deref));
+                let place = self.map().find(place.as_ref());
+                match (target_deref, place) {
+                    (Some(target_deref), Some(place)) => {
+                        state.assign_idx(target_deref, ValueOrPlace::Place(place), self.map())
+                    }
+                    _ => (),
+                }
+            }
+            Rvalue::Ref(_, _, place) | Rvalue::AddressOf(_, place) => {
+                state.flood(place.as_ref(), self.map(), Self::Value::top());
+            }
+            _ => {
+                let result = self.handle_rvalue(rvalue, state);
+                state.assign(target.as_ref(), result, self.map());
+            }
+        }
+    }
+
+    fn handle_rvalue(
+        &self,
+        rvalue: &Rvalue<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> ValueOrPlace<Self::Value> {
+        self.super_rvalue(rvalue, state)
+    }
+
+    fn super_rvalue(
+        &self,
+        rvalue: &Rvalue<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> ValueOrPlace<Self::Value> {
+        match rvalue {
+            Rvalue::Use(operand) => self.handle_operand(operand, state),
+            Rvalue::CopyForDeref(place) => self.handle_operand(&Operand::Copy(*place), state),
+            Rvalue::Ref(..) | Rvalue::AddressOf(..) => {
+                bug!("this rvalue must be handled by handle_assign() or super_assign()")
+            }
+            _ => {
+                // FIXME: Check that other Rvalues really have no side-effect.
+                ValueOrPlace::Unknown
+            }
+        }
+    }
+
+    fn handle_operand(
+        &self,
+        operand: &Operand<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> ValueOrPlace<Self::Value> {
+        self.super_operand(operand, state)
+    }
+
+    fn super_operand(
+        &self,
+        operand: &Operand<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> ValueOrPlace<Self::Value> {
+        match operand {
+            Operand::Constant(box constant) => {
+                ValueOrPlace::Value(self.handle_constant(constant, state))
+            }
+            Operand::Copy(place) | Operand::Move(place) => {
+                // Do want want to handle moves different? Could flood place with bottom.
+                self.map()
+                    .find(place.as_ref())
+                    .map(ValueOrPlace::Place)
+                    .unwrap_or(ValueOrPlace::Unknown)
+            }
+        }
+    }
+
+    fn handle_constant(
+        &self,
+        constant: &Constant<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> Self::Value {
+        self.super_constant(constant, state)
+    }
+
+    fn super_constant(
+        &self,
+        _constant: &Constant<'tcx>,
+        _state: &mut State<Self::Value>,
+    ) -> Self::Value {
+        Self::Value::top()
+    }
+
+    fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
+        self.super_terminator(terminator, state)
+    }
+
+    fn super_terminator(&self, _terminator: &Terminator<'tcx>, _state: &mut State<Self::Value>) {}
+
+    fn handle_call_return(
+        &self,
+        return_places: CallReturnPlaces<'_, 'tcx>,
+        state: &mut State<Self::Value>,
+    ) {
+        self.super_call_return(return_places, state)
+    }
+
+    fn super_call_return(
+        &self,
+        return_places: CallReturnPlaces<'_, 'tcx>,
+        state: &mut State<Self::Value>,
+    ) {
+        return_places.for_each(|place| {
+            state.flood(place.as_ref(), self.map(), Self::Value::top());
+        })
+    }
+
+    fn handle_switch_int(
+        &self,
+        discr: &Operand<'tcx>,
+        apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
+    ) {
+        self.super_switch_int(discr, apply_edge_effects)
+    }
+
+    fn super_switch_int(
+        &self,
+        _discr: &Operand<'tcx>,
+        _apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
+    ) {
+    }
+
+    fn wrap(self) -> ValueAnalysisWrapper<Self>
+    where
+        Self: Sized,
+    {
+        ValueAnalysisWrapper(self)
+    }
+}
+
+pub struct ValueAnalysisWrapper<T>(pub T);
+
+impl<'tcx, T: ValueAnalysis<'tcx>> AnalysisDomain<'tcx> for ValueAnalysisWrapper<T> {
+    type Domain = State<T::Value>;
+
+    type Direction = crate::Forward;
+
+    const NAME: &'static str = T::NAME;
+
+    fn bottom_value(&self, _body: &Body<'tcx>) -> Self::Domain {
+        State(IndexVec::from_elem_n(T::Value::bottom(), self.0.map().value_count))
+    }
+
+    fn initialize_start_block(&self, body: &Body<'tcx>, state: &mut Self::Domain) {
+        for arg in body.args_iter() {
+            state.flood(PlaceRef { local: arg, projection: &[] }, self.0.map(), T::Value::top());
+        }
+    }
+}
+
+impl<'tcx, T> Analysis<'tcx> for ValueAnalysisWrapper<T>
+where
+    T: ValueAnalysis<'tcx>,
+{
+    fn apply_statement_effect(
+        &self,
+        state: &mut Self::Domain,
+        statement: &Statement<'tcx>,
+        _location: Location,
+    ) {
+        self.0.handle_statement(statement, state);
+    }
+
+    fn apply_terminator_effect(
+        &self,
+        state: &mut Self::Domain,
+        terminator: &Terminator<'tcx>,
+        _location: Location,
+    ) {
+        self.0.handle_terminator(terminator, state);
+    }
+
+    fn apply_call_return_effect(
+        &self,
+        state: &mut Self::Domain,
+        _block: BasicBlock,
+        return_places: crate::CallReturnPlaces<'_, 'tcx>,
+    ) {
+        self.0.handle_call_return(return_places, state)
+    }
+
+    fn apply_switch_int_edge_effects(
+        &self,
+        _block: BasicBlock,
+        discr: &Operand<'tcx>,
+        apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
+    ) {
+        self.0.handle_switch_int(discr, apply_edge_effects)
+    }
+}
+
+rustc_index::newtype_index!(
+    pub struct PlaceIndex {}
+);
+
+rustc_index::newtype_index!(
+    struct ValueIndex {}
+);
+
+#[derive(PartialEq, Eq, Clone, Debug)]
+pub struct State<V>(IndexVec<ValueIndex, V>);
+
+impl<V: Clone + HasTop> State<V> {
+    pub fn flood_all(&mut self, value: V) {
+        self.0.raw.fill(value);
+    }
+
+    pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
+        if let Some(root) = map.find(place) {
+            self.flood_idx(root, map, value);
+        }
+    }
+
+    pub fn flood_idx(&mut self, place: PlaceIndex, map: &Map, value: V) {
+        map.preorder_invoke(place, &mut |place| {
+            if let Some(vi) = map.places[place].value_index {
+                self.0[vi] = value.clone();
+            }
+        });
+    }
+
+    pub fn assign_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
+        if let Some(target_value) = map.places[target].value_index {
+            if let Some(source_value) = map.places[source].value_index {
+                self.0[target_value] = self.0[source_value].clone();
+            } else {
+                self.0[target_value] = V::top();
+            }
+        }
+        for target_child in map.children(target) {
+            // Try to find corresponding child in source.
+            let projection = map.places[target_child].proj_elem.unwrap();
+            if let Some(source_child) = map.projections.get(&(source, projection)) {
+                self.assign_place_idx(target_child, *source_child, map);
+            } else {
+                self.flood_idx(target_child, map, V::top());
+            }
+        }
+    }
+
+    pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map) {
+        if let Some(target) = map.find(target) {
+            self.assign_idx(target, result, map);
+        }
+    }
+
+    pub fn assign_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map) {
+        match result {
+            ValueOrPlace::Value(value) => {
+                // FIXME: What if not all tracked projections are overwritten? Can this happen?
+                if let Some(value_index) = map.places[target].value_index {
+                    self.0[value_index] = value;
+                }
+            }
+            ValueOrPlace::Place(source) => self.assign_place_idx(target, source, map),
+            ValueOrPlace::Unknown => {
+                self.flood_idx(target, map, V::top());
+            }
+        }
+    }
+
+    pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V {
+        map.find(place).map(|place| self.get_idx(place, map)).unwrap_or(V::top())
+    }
+
+    pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V {
+        map.places[place].value_index.map(|v| self.0[v].clone()).unwrap_or(V::top())
+    }
+}
+
+impl<V: JoinSemiLattice> JoinSemiLattice for State<V> {
+    fn join(&mut self, other: &Self) -> bool {
+        self.0.join(&other.0)
+    }
+}
+
+#[derive(Debug)]
+pub struct Map {
+    locals: IndexVec<Local, Option<PlaceIndex>>,
+    projections: FxHashMap<(PlaceIndex, ProjElem), PlaceIndex>,
+    places: IndexVec<PlaceIndex, PlaceInfo>,
+    value_count: usize,
+}
+
+impl Map {
+    pub fn new() -> Self {
+        Self {
+            locals: IndexVec::new(),
+            projections: FxHashMap::default(),
+            places: IndexVec::new(),
+            value_count: 0,
+        }
+    }
+
+    /// Register all places with suitable types up to a certain derefence depth (to prevent cycles).
+    pub fn register_with_filter<'tcx>(
+        &mut self,
+        tcx: TyCtxt<'tcx>,
+        source: &impl HasLocalDecls<'tcx>,
+        max_derefs: u32,
+        mut filter: impl FnMut(Ty<'tcx>) -> bool,
+    ) {
+        let mut projection = Vec::new();
+        for (local, decl) in source.local_decls().iter_enumerated() {
+            self.register_with_filter_rec(
+                tcx,
+                max_derefs,
+                local,
+                &mut projection,
+                decl.ty,
+                &mut filter,
+            );
+        }
+    }
+
+    fn register_with_filter_rec<'tcx>(
+        &mut self,
+        tcx: TyCtxt<'tcx>,
+        max_derefs: u32,
+        local: Local,
+        projection: &mut Vec<PlaceElem<'tcx>>,
+        ty: Ty<'tcx>,
+        filter: &mut impl FnMut(Ty<'tcx>) -> bool,
+    ) {
+        if filter(ty) {
+            self.register(local, projection)
+                .expect("projection should only contain convertible elements");
+        }
+        if max_derefs > 0 {
+            if let Some(ty::TypeAndMut { ty, .. }) = ty.builtin_deref(false) {
+                projection.push(PlaceElem::Deref);
+                self.register_with_filter_rec(tcx, max_derefs - 1, local, projection, ty, filter);
+                projection.pop();
+            }
+        }
+        iter_fields(ty, tcx, |variant, field, ty| {
+            if let Some(variant) = variant {
+                projection.push(PlaceElem::Downcast(None, variant));
+            }
+            projection.push(PlaceElem::Field(field, ty));
+            self.register_with_filter_rec(tcx, max_derefs, local, projection, ty, filter);
+            projection.pop();
+            if variant.is_some() {
+                projection.pop();
+            }
+        });
+    }
+
+    pub fn register<'tcx>(
+        &mut self,
+        local: Local,
+        projection: &[PlaceElem<'tcx>],
+    ) -> Result<(), ()> {
+        // Get the base index of the local.
+        let mut index =
+            *self.locals.get_or_insert_with(local, || self.places.push(PlaceInfo::new(None)));
+
+        // Apply the projection.
+        for &elem in projection {
+            let elem = elem.try_into()?;
+            index = *self.projections.entry((index, elem)).or_insert_with(|| {
+                // Prepend new child to the linked list.
+                let next = self.places.push(PlaceInfo::new(Some(elem)));
+                self.places[next].next_sibling = self.places[index].first_child;
+                self.places[index].first_child = Some(next);
+                next
+            });
+        }
+
+        // Allocate a value slot if it doesn't have one.
+        if self.places[index].value_index.is_none() {
+            self.places[index].value_index = Some(self.value_count.into());
+            self.value_count += 1;
+        }
+
+        Ok(())
+    }
+
+    pub fn apply_elem(&self, place: PlaceIndex, elem: ProjElem) -> Option<PlaceIndex> {
+        self.projections.get(&(place, elem)).copied()
+    }
+
+    pub fn find(&self, place: PlaceRef<'_>) -> Option<PlaceIndex> {
+        let mut index = *self.locals.get(place.local)?.as_ref()?;
+
+        for &elem in place.projection {
+            index = self.apply_elem(index, elem.try_into().ok()?)?;
+        }
+
+        Some(index)
+    }
+
+    pub fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
+        Children::new(self, parent)
+    }
+
+    pub fn preorder_invoke(&self, root: PlaceIndex, f: &mut impl FnMut(PlaceIndex)) {
+        f(root);
+        for child in self.children(root) {
+            self.preorder_invoke(child, f);
+        }
+    }
+}
+
+#[derive(Debug)]
+struct PlaceInfo {
+    next_sibling: Option<PlaceIndex>,
+    first_child: Option<PlaceIndex>,
+    /// The projection used to go from parent to this node (only None for root).
+    proj_elem: Option<ProjElem>,
+    value_index: Option<ValueIndex>,
+}
+
+impl PlaceInfo {
+    fn new(proj_elem: Option<ProjElem>) -> Self {
+        Self { next_sibling: None, first_child: None, proj_elem, value_index: None }
+    }
+}
+
+struct Children<'a> {
+    map: &'a Map,
+    next: Option<PlaceIndex>,
+}
+
+impl<'a> Children<'a> {
+    fn new(map: &'a Map, parent: PlaceIndex) -> Self {
+        Self { map, next: map.places[parent].first_child }
+    }
+}
+
+impl<'a> Iterator for Children<'a> {
+    type Item = PlaceIndex;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        match self.next {
+            Some(child) => {
+                self.next = self.map.places[child].next_sibling;
+                Some(child)
+            }
+            None => None,
+        }
+    }
+}
+
+// FIXME: See if we can get rid of `Unknown`.
+pub enum ValueOrPlace<V> {
+    Value(V),
+    Place(PlaceIndex),
+    Unknown,
+}
+
+pub trait HasBottom {
+    fn bottom() -> Self;
+}
+
+pub trait HasTop {
+    fn top() -> Self;
+}
+
+impl<V> HasBottom for FlatSet<V> {
+    fn bottom() -> Self {
+        Self::Bottom
+    }
+}
+
+impl<V> HasTop for FlatSet<V> {
+    fn top() -> Self {
+        Self::Top
+    }
+}
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum ProjElem {
+    Deref,
+    Field(Field),
+    Downcast(VariantIdx),
+}
+
+impl<V, T> TryFrom<ProjectionElem<V, T>> for ProjElem {
+    type Error = ();
+
+    fn try_from(value: ProjectionElem<V, T>) -> Result<Self, Self::Error> {
+        match value {
+            ProjectionElem::Deref => Ok(ProjElem::Deref),
+            ProjectionElem::Field(field, _) => Ok(ProjElem::Field(field)),
+            ProjectionElem::Downcast(_, variant) => Ok(ProjElem::Downcast(variant)),
+            _ => Err(()),
+        }
+    }
+}
+
+fn iter_fields<'tcx>(
+    ty: Ty<'tcx>,
+    tcx: TyCtxt<'tcx>,
+    mut f: impl FnMut(Option<VariantIdx>, Field, Ty<'tcx>),
+) {
+    match ty.kind() {
+        ty::Tuple(list) => {
+            for (field, ty) in list.iter().enumerate() {
+                f(None, field.into(), ty);
+            }
+        }
+        ty::Adt(def, substs) => {
+            for (v_index, v_def) in def.variants().iter_enumerated() {
+                for (f_index, f_def) in v_def.fields.iter().enumerate() {
+                    let field_ty = tcx.normalize_erasing_regions(
+                        ty::ParamEnv::reveal_all(),
+                        f_def.ty(tcx, substs),
+                    );
+                    f(Some(v_index), f_index.into(), field_ty);
+                }
+            }
+        }
+        ty::Closure(_, substs) => {
+            iter_fields(substs.as_closure().tupled_upvars_ty(), tcx, f);
+        }
+        _ => (),
+    }
+}
+
+fn debug_with_context_rec<V: Debug + Eq>(
+    place: PlaceIndex,
+    place_str: &str,
+    new: &State<V>,
+    old: Option<&State<V>>,
+    map: &Map,
+    f: &mut Formatter<'_>,
+) -> std::fmt::Result {
+    if let Some(value) = map.places[place].value_index {
+        match old {
+            None => writeln!(f, "{}: {:?}", place_str, new.0[value])?,
+            Some(old) => {
+                if new.0[value] != old.0[value] {
+                    writeln!(f, "\u{001f}-{}: {:?}", place_str, old.0[value])?;
+                    writeln!(f, "\u{001f}+{}: {:?}", place_str, new.0[value])?;
+                }
+            }
+        }
+    }
+
+    for child in map.children(place) {
+        let info_elem = map.places[child].proj_elem.unwrap();
+        let child_place_str = match info_elem {
+            ProjElem::Deref => format!("*{}", place_str),
+            ProjElem::Field(field) => {
+                if place_str.starts_with("*") {
+                    format!("({}).{}", place_str, field.index())
+                } else {
+                    format!("{}.{}", place_str, field.index())
+                }
+            }
+            ProjElem::Downcast(variant) => format!("({} as #{})", place_str, variant.index()),
+        };
+        debug_with_context_rec(child, &child_place_str, new, old, map, f)?;
+    }
+
+    Ok(())
+}
+
+fn debug_with_context<V: Debug + Eq>(
+    new: &State<V>,
+    old: Option<&State<V>>,
+    map: &Map,
+    f: &mut Formatter<'_>,
+) -> std::fmt::Result {
+    for (local, place) in map.locals.iter_enumerated() {
+        if let Some(place) = place {
+            debug_with_context_rec(*place, &format!("{:?}", local), new, old, map, f)?;
+        }
+    }
+    Ok(())
+}
+
+impl<'tcx, T> DebugWithContext<ValueAnalysisWrapper<T>> for State<T::Value>
+where
+    T: ValueAnalysis<'tcx>,
+    T::Value: Debug,
+{
+    fn fmt_with(&self, ctxt: &ValueAnalysisWrapper<T>, f: &mut Formatter<'_>) -> std::fmt::Result {
+        debug_with_context(self, None, ctxt.0.map(), f)
+    }
+
+    fn fmt_diff_with(
+        &self,
+        old: &Self,
+        ctxt: &ValueAnalysisWrapper<T>,
+        f: &mut Formatter<'_>,
+    ) -> std::fmt::Result {
+        debug_with_context(self, Some(old), ctxt.0.map(), f)
+    }
+}
diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
new file mode 100644
index 00000000000..f1220f634ac
--- /dev/null
+++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
@@ -0,0 +1,490 @@
+use rustc_const_eval::interpret::{ConstValue, ImmTy, Immediate, InterpCx, Scalar};
+use rustc_data_structures::fx::FxHashMap;
+use rustc_middle::mir::visit::{MutVisitor, Visitor};
+use rustc_middle::mir::*;
+use rustc_middle::ty::{self, ScalarInt, Ty, TyCtxt};
+use rustc_mir_dataflow::value_analysis::{Map, ProjElem, State, ValueAnalysis, ValueOrPlace};
+use rustc_mir_dataflow::{lattice::FlatSet, Analysis, ResultsVisitor, SwitchIntEdgeEffects};
+use rustc_span::DUMMY_SP;
+
+use crate::MirPass;
+
+pub struct DataflowConstProp;
+
+impl<'tcx> MirPass<'tcx> for DataflowConstProp {
+    fn is_enabled(&self, sess: &rustc_session::Session) -> bool {
+        // Choose different minimum level?
+        sess.mir_opt_level() >= 4
+    }
+
+    fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
+        // Decide which places to track during the analysis.
+        let mut map = Map::new();
+        map.register_with_filter(tcx, body, 3, |ty| ty.is_scalar() && !ty.is_unsafe_ptr());
+
+        // Perform the actual dataflow analysis.
+        let analysis = ConstAnalysis::new(tcx, body, map);
+        let results = analysis.wrap().into_engine(tcx, body).iterate_to_fixpoint();
+
+        // Collect results and patch the body afterwards.
+        let mut visitor = CollectAndPatch::new(tcx, &results.analysis.0.map);
+        results.visit_reachable_with(body, &mut visitor);
+        visitor.visit_body(body);
+    }
+}
+
+// FIXME: Consider support for discriminants, mutable references, arrays and slices.
+struct ConstAnalysis<'tcx> {
+    map: Map,
+    tcx: TyCtxt<'tcx>,
+    ecx: InterpCx<'tcx, 'tcx, DummyMachine>,
+    param_env: ty::ParamEnv<'tcx>,
+}
+
+impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'tcx> {
+    type Value = FlatSet<Const<'tcx>>;
+
+    const NAME: &'static str = "ConstAnalysis";
+
+    fn map(&self) -> &Map {
+        &self.map
+    }
+
+    fn handle_assign(
+        &self,
+        target: Place<'tcx>,
+        rvalue: &Rvalue<'tcx>,
+        state: &mut State<Self::Value>,
+    ) {
+        match rvalue {
+            Rvalue::CheckedBinaryOp(op, box (left, right)) => {
+                let target = self.map().find(target.as_ref());
+                let value_target = target.and_then(|target| {
+                    self.map().apply_elem(target, ProjElem::Field(0_u32.into()))
+                });
+                let overflow_target = target.and_then(|target| {
+                    self.map().apply_elem(target, ProjElem::Field(1_u32.into()))
+                });
+
+                if value_target.is_some() || overflow_target.is_some() {
+                    let (val, overflow) = self.binary_op(state, *op, left, right);
+
+                    if let Some(value_target) = value_target {
+                        state.assign_idx(value_target, ValueOrPlace::Value(val), self.map());
+                    }
+                    if let Some(overflow_target) = overflow_target {
+                        state.assign_idx(
+                            overflow_target,
+                            ValueOrPlace::Value(overflow),
+                            self.map(),
+                        );
+                    }
+                }
+            }
+            _ => self.super_assign(target, rvalue, state),
+        }
+    }
+
+    fn handle_rvalue(
+        &self,
+        rvalue: &Rvalue<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> ValueOrPlace<Self::Value> {
+        match rvalue {
+            Rvalue::Cast(CastKind::Misc, operand, ty) => {
+                let operand = self.eval_operand(operand, state);
+                match operand {
+                    FlatSet::Elem(operand) => self
+                        .ecx
+                        .misc_cast(&operand, *ty)
+                        .map(|result| ValueOrPlace::Value(self.wrap_immediate(result, *ty)))
+                        .unwrap_or(ValueOrPlace::Unknown),
+                    _ => ValueOrPlace::Unknown,
+                }
+            }
+            Rvalue::BinaryOp(op, box (left, right)) => {
+                let (val, _overflow) = self.binary_op(state, *op, left, right);
+                // FIXME: Just ignore overflow here?
+                ValueOrPlace::Value(val)
+            }
+            Rvalue::UnaryOp(op, operand) => match self.eval_operand(operand, state) {
+                FlatSet::Elem(value) => self
+                    .ecx
+                    .unary_op(*op, &value)
+                    .map(|val| ValueOrPlace::Value(self.wrap_immty(val)))
+                    .unwrap_or(ValueOrPlace::Value(FlatSet::Top)),
+                FlatSet::Bottom => ValueOrPlace::Value(FlatSet::Bottom),
+                FlatSet::Top => ValueOrPlace::Value(FlatSet::Top),
+            },
+            _ => self.super_rvalue(rvalue, state),
+        }
+    }
+
+    fn handle_constant(
+        &self,
+        constant: &Constant<'tcx>,
+        _state: &mut State<Self::Value>,
+    ) -> Self::Value {
+        constant
+            .literal
+            .eval(self.tcx, self.param_env)
+            .try_to_scalar()
+            .and_then(|scalar| scalar.try_to_int().ok())
+            .map(|value| FlatSet::Elem(Const::Scalar(value, constant.ty())))
+            .unwrap_or(FlatSet::Top)
+    }
+
+    fn handle_switch_int(
+        &self,
+        discr: &Operand<'tcx>,
+        apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
+    ) {
+        // FIXME: The dataflow framework only provides the state if we call `apply()`, which makes
+        // this more inefficient than it has to be.
+        // FIXME: Perhaps we rather need a proper unreachability flag for every block.
+        let mut discr_value = None;
+        let mut handled = false;
+        apply_edge_effects.apply(|state, target| {
+            let discr_value = match discr_value {
+                Some(value) => value,
+                None => {
+                    let value = match self.handle_operand(discr, state) {
+                        ValueOrPlace::Value(value) => value,
+                        ValueOrPlace::Place(place) => state.get_idx(place, self.map()),
+                        ValueOrPlace::Unknown => FlatSet::Top,
+                    };
+                    let result = match value {
+                        FlatSet::Top => FlatSet::Top,
+                        FlatSet::Elem(Const::Scalar(scalar, _)) => {
+                            FlatSet::Elem(scalar.assert_bits(scalar.size()))
+                        }
+                        FlatSet::Bottom => FlatSet::Bottom,
+                    };
+                    discr_value = Some(result);
+                    result
+                }
+            };
+
+            let FlatSet::Elem(choice) = discr_value else {
+                // Do nothing if we don't know which branch will be taken.
+                return
+            };
+
+            if target.value.map(|n| n == choice).unwrap_or(!handled) {
+                // Branch is taken. Has no effect on state.
+                handled = true;
+            } else {
+                // Branch is not taken, we can flood everything.
+                state.flood_all(FlatSet::Bottom);
+            }
+        })
+    }
+}
+
+#[derive(Clone, PartialEq, Eq)]
+enum Const<'tcx> {
+    // FIXME: If there won't be any other cases, make it a struct.
+    Scalar(ScalarInt, Ty<'tcx>),
+}
+
+impl<'tcx> std::fmt::Debug for Const<'tcx> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match *self {
+            Self::Scalar(scalar, ty) => {
+                std::fmt::Display::fmt(&ConstantKind::Val(ConstValue::Scalar(scalar.into()), ty), f)
+            }
+        }
+    }
+}
+
+impl<'tcx> ConstAnalysis<'tcx> {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, map: Map) -> Self {
+        Self {
+            map,
+            tcx,
+            ecx: InterpCx::new(tcx, DUMMY_SP, ty::ParamEnv::empty(), DummyMachine),
+            param_env: tcx.param_env(body.source.def_id()),
+        }
+    }
+
+    fn binary_op(
+        &self,
+        state: &mut State<FlatSet<Const<'tcx>>>,
+        op: BinOp,
+        left: &Operand<'tcx>,
+        right: &Operand<'tcx>,
+    ) -> (FlatSet<Const<'tcx>>, FlatSet<Const<'tcx>>) {
+        let left = self.eval_operand(left, state);
+        let right = self.eval_operand(right, state);
+        match (left, right) {
+            (FlatSet::Elem(left), FlatSet::Elem(right)) => {
+                match self.ecx.overflowing_binary_op(op, &left, &right) {
+                    Ok((val, overflow, ty)) => {
+                        let val = val
+                            .try_to_int()
+                            .ok()
+                            .map(|val| self.wrap_scalar(val, ty))
+                            .unwrap_or(FlatSet::Top);
+                        let overflow = self.wrap_scalar(overflow.into(), self.tcx.types.bool);
+                        (val, overflow)
+                    }
+                    _ => (FlatSet::Top, FlatSet::Top),
+                }
+            }
+            (FlatSet::Bottom, _) | (_, FlatSet::Bottom) => (FlatSet::Bottom, FlatSet::Bottom),
+            (_, _) => {
+                // Could attempt some algebraic simplifcations here.
+                (FlatSet::Top, FlatSet::Top)
+            }
+        }
+    }
+
+    fn eval_operand(
+        &self,
+        op: &Operand<'tcx>,
+        state: &mut State<FlatSet<Const<'tcx>>>,
+    ) -> FlatSet<ImmTy<'tcx>> {
+        let value = match self.handle_operand(op, state) {
+            ValueOrPlace::Value(value) => value,
+            ValueOrPlace::Place(place) => state.get_idx(place, &self.map),
+            ValueOrPlace::Unknown => FlatSet::Top,
+        };
+        match value {
+            FlatSet::Top => FlatSet::Top,
+            FlatSet::Elem(Const::Scalar(value, ty)) => {
+                let layout = self
+                    .tcx
+                    .layout_of(ty::ParamEnv::empty().and(ty))
+                    .expect("this should not happen"); // FIXME
+                FlatSet::Elem(ImmTy::from_scalar(value.into(), layout))
+            }
+            FlatSet::Bottom => FlatSet::Bottom,
+        }
+    }
+
+    fn wrap_scalar(&self, scalar: ScalarInt, ty: Ty<'tcx>) -> FlatSet<Const<'tcx>> {
+        FlatSet::Elem(Const::Scalar(scalar, ty))
+    }
+
+    fn wrap_immediate(&self, imm: Immediate, ty: Ty<'tcx>) -> FlatSet<Const<'tcx>> {
+        match imm {
+            Immediate::Scalar(Scalar::Int(scalar)) => self.wrap_scalar(scalar, ty),
+            _ => FlatSet::Top,
+        }
+    }
+
+    fn wrap_immty(&self, val: ImmTy<'tcx>) -> FlatSet<Const<'tcx>> {
+        self.wrap_immediate(*val, val.layout.ty)
+    }
+}
+
+struct CollectAndPatch<'tcx, 'map> {
+    tcx: TyCtxt<'tcx>,
+    map: &'map Map,
+    before_effect: FxHashMap<(Location, Place<'tcx>), Const<'tcx>>,
+    assignments: FxHashMap<Location, Const<'tcx>>,
+}
+
+impl<'tcx, 'map> CollectAndPatch<'tcx, 'map> {
+    fn new(tcx: TyCtxt<'tcx>, map: &'map Map) -> Self {
+        Self { tcx, map, before_effect: FxHashMap::default(), assignments: FxHashMap::default() }
+    }
+
+    fn make_operand(&self, constant: Const<'tcx>) -> Operand<'tcx> {
+        let Const::Scalar(scalar, ty) = constant;
+        Operand::Constant(Box::new(Constant {
+            span: DUMMY_SP,
+            user_ty: None,
+            literal: ConstantKind::Val(ConstValue::Scalar(scalar.into()), ty),
+        }))
+    }
+}
+
+impl<'mir, 'tcx, 'map> ResultsVisitor<'mir, 'tcx> for CollectAndPatch<'tcx, 'map> {
+    type FlowState = State<FlatSet<Const<'tcx>>>;
+
+    fn visit_statement_before_primary_effect(
+        &mut self,
+        state: &Self::FlowState,
+        statement: &'mir Statement<'tcx>,
+        location: Location,
+    ) {
+        match &statement.kind {
+            StatementKind::Assign(box (_, rvalue)) => {
+                OperandCollector { state, visitor: self }.visit_rvalue(rvalue, location);
+            }
+            _ => (),
+        }
+    }
+
+    fn visit_statement_after_primary_effect(
+        &mut self,
+        state: &Self::FlowState,
+        statement: &'mir Statement<'tcx>,
+        location: Location,
+    ) {
+        match statement.kind {
+            StatementKind::Assign(box (place, _)) => match state.get(place.as_ref(), self.map) {
+                FlatSet::Top => (),
+                FlatSet::Elem(value) => {
+                    self.assignments.insert(location, value);
+                }
+                FlatSet::Bottom => {
+                    // This statement is not reachable. Do nothing, it will (hopefully) be removed.
+                }
+            },
+            _ => (),
+        }
+    }
+
+    fn visit_terminator_before_primary_effect(
+        &mut self,
+        state: &Self::FlowState,
+        terminator: &'mir Terminator<'tcx>,
+        location: Location,
+    ) {
+        OperandCollector { state, visitor: self }.visit_terminator(terminator, location);
+    }
+}
+
+impl<'tcx, 'map> MutVisitor<'tcx> for CollectAndPatch<'tcx, 'map> {
+    fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
+        self.tcx
+    }
+
+    fn visit_statement(&mut self, statement: &mut Statement<'tcx>, location: Location) {
+        if let Some(value) = self.assignments.get(&location) {
+            match &mut statement.kind {
+                StatementKind::Assign(box (_, rvalue)) => {
+                    *rvalue = Rvalue::Use(self.make_operand(value.clone()));
+                }
+                _ => bug!("found assignment info for non-assign statement"),
+            }
+        } else {
+            self.super_statement(statement, location);
+        }
+    }
+
+    fn visit_operand(&mut self, operand: &mut Operand<'tcx>, location: Location) {
+        match operand {
+            Operand::Copy(place) | Operand::Move(place) => {
+                if let Some(value) = self.before_effect.get(&(location, *place)) {
+                    *operand = self.make_operand(value.clone());
+                }
+            }
+            _ => (),
+        }
+    }
+}
+
+struct OperandCollector<'tcx, 'map, 'a> {
+    state: &'a State<FlatSet<Const<'tcx>>>,
+    visitor: &'a mut CollectAndPatch<'tcx, 'map>,
+}
+
+impl<'tcx, 'map, 'a> Visitor<'tcx> for OperandCollector<'tcx, 'map, 'a> {
+    fn visit_operand(&mut self, operand: &Operand<'tcx>, location: Location) {
+        match operand {
+            Operand::Copy(place) | Operand::Move(place) => {
+                match self.state.get(place.as_ref(), self.visitor.map) {
+                    FlatSet::Top => (),
+                    FlatSet::Elem(value) => {
+                        self.visitor.before_effect.insert((location, *place), value);
+                    }
+                    FlatSet::Bottom => {
+                        // This only happens if this location is unreachable.
+                    }
+                }
+            }
+            _ => (),
+        }
+    }
+}
+
+struct DummyMachine;
+
+impl<'mir, 'tcx> rustc_const_eval::interpret::Machine<'mir, 'tcx> for DummyMachine {
+    rustc_const_eval::interpret::compile_time_machine!(<'mir, 'tcx>);
+    type MemoryKind = !;
+    const PANIC_ON_ALLOC_FAIL: bool = true;
+
+    fn enforce_alignment(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
+        unimplemented!()
+    }
+
+    fn enforce_validity(_ecx: &InterpCx<'mir, 'tcx, Self>) -> bool {
+        unimplemented!()
+    }
+
+    fn find_mir_or_eval_fn(
+        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        _instance: ty::Instance<'tcx>,
+        _abi: rustc_target::spec::abi::Abi,
+        _args: &[rustc_const_eval::interpret::OpTy<'tcx, Self::Provenance>],
+        _destination: &rustc_const_eval::interpret::PlaceTy<'tcx, Self::Provenance>,
+        _target: Option<BasicBlock>,
+        _unwind: rustc_const_eval::interpret::StackPopUnwind,
+    ) -> interpret::InterpResult<'tcx, Option<(&'mir Body<'tcx>, ty::Instance<'tcx>)>> {
+        unimplemented!()
+    }
+
+    fn call_intrinsic(
+        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        _instance: ty::Instance<'tcx>,
+        _args: &[rustc_const_eval::interpret::OpTy<'tcx, Self::Provenance>],
+        _destination: &rustc_const_eval::interpret::PlaceTy<'tcx, Self::Provenance>,
+        _target: Option<BasicBlock>,
+        _unwind: rustc_const_eval::interpret::StackPopUnwind,
+    ) -> interpret::InterpResult<'tcx> {
+        unimplemented!()
+    }
+
+    fn assert_panic(
+        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        _msg: &rustc_middle::mir::AssertMessage<'tcx>,
+        _unwind: Option<BasicBlock>,
+    ) -> interpret::InterpResult<'tcx> {
+        unimplemented!()
+    }
+
+    fn binary_ptr_op(
+        _ecx: &InterpCx<'mir, 'tcx, Self>,
+        _bin_op: BinOp,
+        _left: &rustc_const_eval::interpret::ImmTy<'tcx, Self::Provenance>,
+        _right: &rustc_const_eval::interpret::ImmTy<'tcx, Self::Provenance>,
+    ) -> interpret::InterpResult<'tcx, (interpret::Scalar<Self::Provenance>, bool, Ty<'tcx>)> {
+        unimplemented!()
+    }
+
+    fn expose_ptr(
+        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        _ptr: interpret::Pointer<Self::Provenance>,
+    ) -> interpret::InterpResult<'tcx> {
+        unimplemented!()
+    }
+
+    fn init_frame_extra(
+        _ecx: &mut InterpCx<'mir, 'tcx, Self>,
+        _frame: rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance>,
+    ) -> interpret::InterpResult<
+        'tcx,
+        rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
+    > {
+        unimplemented!()
+    }
+
+    fn stack<'a>(
+        _ecx: &'a InterpCx<'mir, 'tcx, Self>,
+    ) -> &'a [rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>]
+    {
+        unimplemented!()
+    }
+
+    fn stack_mut<'a>(
+        _ecx: &'a mut InterpCx<'mir, 'tcx, Self>,
+    ) -> &'a mut Vec<
+        rustc_const_eval::interpret::Frame<'mir, 'tcx, Self::Provenance, Self::FrameExtra>,
+    > {
+        unimplemented!()
+    }
+}
diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs
index 4791be1306c..692eeddfb98 100644
--- a/compiler/rustc_mir_transform/src/lib.rs
+++ b/compiler/rustc_mir_transform/src/lib.rs
@@ -54,6 +54,7 @@ mod const_goto;
 mod const_prop;
 mod const_prop_lint;
 mod coverage;
+mod dataflow_const_prop;
 mod dead_store_elimination;
 mod deaggregator;
 mod deduce_param_attrs;
@@ -569,6 +570,7 @@ fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
             //
             // FIXME(#70073): This pass is responsible for both optimization as well as some lints.
             &const_prop::ConstProp,
+            &dataflow_const_prop::DataflowConstProp,
             //
             // Const-prop runs unconditionally, but doesn't mutate the MIR at mir-opt-level=0.
             &const_debuginfo::ConstDebugInfo,
diff --git a/src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..950b0518e0b
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/cast.main.DataflowConstProp.diff
@@ -0,0 +1,41 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/cast.rs:+0:11: +0:11
+      let _1: i32;                         // in scope 0 at $DIR/cast.rs:+1:9: +1:10
+      let mut _3: u8;                      // in scope 0 at $DIR/cast.rs:+2:13: +2:22
+      let mut _4: i32;                     // in scope 0 at $DIR/cast.rs:+2:13: +2:16
+      let mut _5: &i32;                    // in scope 0 at $DIR/cast.rs:+2:14: +2:16
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/cast.rs:+1:9: +1:10
+          let _2: u8;                      // in scope 1 at $DIR/cast.rs:+2:9: +2:10
+          scope 2 {
+              debug b => _2;               // in scope 2 at $DIR/cast.rs:+2:9: +2:10
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/cast.rs:+1:9: +1:10
+          _1 = const 257_i32;              // scope 0 at $DIR/cast.rs:+1:13: +1:16
+          StorageLive(_2);                 // scope 1 at $DIR/cast.rs:+2:9: +2:10
+          StorageLive(_3);                 // scope 1 at $DIR/cast.rs:+2:13: +2:22
+          StorageLive(_4);                 // scope 1 at $DIR/cast.rs:+2:13: +2:16
+          StorageLive(_5);                 // scope 1 at $DIR/cast.rs:+2:14: +2:16
+          _5 = &_1;                        // scope 1 at $DIR/cast.rs:+2:14: +2:16
+-         _4 = (*_5);                      // scope 1 at $DIR/cast.rs:+2:13: +2:16
+-         _3 = move _4 as u8 (Misc);       // scope 1 at $DIR/cast.rs:+2:13: +2:22
++         _4 = const 257_i32;              // scope 1 at $DIR/cast.rs:+2:13: +2:16
++         _3 = const 1_u8;                 // scope 1 at $DIR/cast.rs:+2:13: +2:22
+          StorageDead(_4);                 // scope 1 at $DIR/cast.rs:+2:21: +2:22
+-         _2 = Add(move _3, const 1_u8);   // scope 1 at $DIR/cast.rs:+2:13: +2:26
++         _2 = const 2_u8;                 // scope 1 at $DIR/cast.rs:+2:13: +2:26
+          StorageDead(_3);                 // scope 1 at $DIR/cast.rs:+2:25: +2:26
+          StorageDead(_5);                 // scope 1 at $DIR/cast.rs:+2:26: +2:27
+          _0 = const ();                   // scope 0 at $DIR/cast.rs:+0:11: +3:2
+          StorageDead(_2);                 // scope 1 at $DIR/cast.rs:+3:1: +3:2
+          StorageDead(_1);                 // scope 0 at $DIR/cast.rs:+3:1: +3:2
+          return;                          // scope 0 at $DIR/cast.rs:+3:2: +3:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/cast.rs b/src/test/mir-opt/dataflow-const-prop/cast.rs
new file mode 100644
index 00000000000..bf5838cb89d
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/cast.rs
@@ -0,0 +1,7 @@
+// unit-test: DataflowConstProp
+
+// EMIT_MIR cast.main.DataflowConstProp.diff
+fn main() {
+    let a = 257;
+    let b = *&a as u8 + 1;
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..e947f084970
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/cell.main.DataflowConstProp.diff
@@ -0,0 +1,12 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/cell.rs:+0:11: +0:11
+  
+      bb0: {
+          _0 = const ();                   // scope 0 at $DIR/cell.rs:+0:11: +2:2
+          return;                          // scope 0 at $DIR/cell.rs:+2:2: +2:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/cell.rs b/src/test/mir-opt/dataflow-const-prop/cell.rs
new file mode 100644
index 00000000000..82a4c346595
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/cell.rs
@@ -0,0 +1,6 @@
+// unit-test: DataflowConstProp
+
+// EMIT_MIR cell.main.DataflowConstProp.diff
+fn main() {
+    // FIXME: Is it possible to build something with UnsafeCell?
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..39bc506adac
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/checked.main.DataflowConstProp.diff
@@ -0,0 +1,81 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/checked.rs:+0:11: +0:11
+      let _1: i32;                         // in scope 0 at $DIR/checked.rs:+1:9: +1:10
+      let mut _4: i32;                     // in scope 0 at $DIR/checked.rs:+3:13: +3:14
+      let mut _5: i32;                     // in scope 0 at $DIR/checked.rs:+3:17: +3:18
+      let mut _6: (i32, bool);             // in scope 0 at $DIR/checked.rs:+3:13: +3:18
+      let mut _9: i32;                     // in scope 0 at $DIR/checked.rs:+6:13: +6:14
+      let mut _10: (i32, bool);            // in scope 0 at $DIR/checked.rs:+6:13: +6:18
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/checked.rs:+1:9: +1:10
+          let _2: i32;                     // in scope 1 at $DIR/checked.rs:+2:9: +2:10
+          scope 2 {
+              debug b => _2;               // in scope 2 at $DIR/checked.rs:+2:9: +2:10
+              let _3: i32;                 // in scope 2 at $DIR/checked.rs:+3:9: +3:10
+              scope 3 {
+                  debug c => _3;           // in scope 3 at $DIR/checked.rs:+3:9: +3:10
+                  let _7: i32;             // in scope 3 at $DIR/checked.rs:+5:9: +5:10
+                  scope 4 {
+                      debug d => _7;       // in scope 4 at $DIR/checked.rs:+5:9: +5:10
+                      let _8: i32;         // in scope 4 at $DIR/checked.rs:+6:9: +6:10
+                      scope 5 {
+                          debug e => _8;   // in scope 5 at $DIR/checked.rs:+6:9: +6:10
+                      }
+                  }
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/checked.rs:+1:9: +1:10
+          _1 = const 1_i32;                // scope 0 at $DIR/checked.rs:+1:13: +1:14
+          StorageLive(_2);                 // scope 1 at $DIR/checked.rs:+2:9: +2:10
+          _2 = const 2_i32;                // scope 1 at $DIR/checked.rs:+2:13: +2:14
+          StorageLive(_3);                 // scope 2 at $DIR/checked.rs:+3:9: +3:10
+          StorageLive(_4);                 // scope 2 at $DIR/checked.rs:+3:13: +3:14
+-         _4 = _1;                         // scope 2 at $DIR/checked.rs:+3:13: +3:14
++         _4 = const 1_i32;                // scope 2 at $DIR/checked.rs:+3:13: +3:14
+          StorageLive(_5);                 // scope 2 at $DIR/checked.rs:+3:17: +3:18
+-         _5 = _2;                         // scope 2 at $DIR/checked.rs:+3:17: +3:18
+-         _6 = CheckedAdd(_4, _5);         // scope 2 at $DIR/checked.rs:+3:13: +3:18
+-         assert(!move (_6.1: bool), "attempt to compute `{} + {}`, which would overflow", move _4, move _5) -> bb1; // scope 2 at $DIR/checked.rs:+3:13: +3:18
++         _5 = const 2_i32;                // scope 2 at $DIR/checked.rs:+3:17: +3:18
++         _6 = CheckedAdd(const 1_i32, const 2_i32); // scope 2 at $DIR/checked.rs:+3:13: +3:18
++         assert(!const false, "attempt to compute `{} + {}`, which would overflow", const 1_i32, const 2_i32) -> bb1; // scope 2 at $DIR/checked.rs:+3:13: +3:18
+      }
+  
+      bb1: {
+-         _3 = move (_6.0: i32);           // scope 2 at $DIR/checked.rs:+3:13: +3:18
++         _3 = const 3_i32;                // scope 2 at $DIR/checked.rs:+3:13: +3:18
+          StorageDead(_5);                 // scope 2 at $DIR/checked.rs:+3:17: +3:18
+          StorageDead(_4);                 // scope 2 at $DIR/checked.rs:+3:17: +3:18
+          StorageLive(_7);                 // scope 3 at $DIR/checked.rs:+5:9: +5:10
+-         _7 = const core::num::<impl i32>::MAX; // scope 3 at $DIR/checked.rs:+5:13: +5:21
++         _7 = const i32::MAX;             // scope 3 at $DIR/checked.rs:+5:13: +5:21
+          StorageLive(_8);                 // scope 4 at $DIR/checked.rs:+6:9: +6:10
+          StorageLive(_9);                 // scope 4 at $DIR/checked.rs:+6:13: +6:14
+-         _9 = _7;                         // scope 4 at $DIR/checked.rs:+6:13: +6:14
+-         _10 = CheckedAdd(_9, const 1_i32); // scope 4 at $DIR/checked.rs:+6:13: +6:18
+-         assert(!move (_10.1: bool), "attempt to compute `{} + {}`, which would overflow", move _9, const 1_i32) -> bb2; // scope 4 at $DIR/checked.rs:+6:13: +6:18
++         _9 = const i32::MAX;             // scope 4 at $DIR/checked.rs:+6:13: +6:14
++         _10 = CheckedAdd(const i32::MAX, const 1_i32); // scope 4 at $DIR/checked.rs:+6:13: +6:18
++         assert(!const true, "attempt to compute `{} + {}`, which would overflow", const i32::MAX, const 1_i32) -> bb2; // scope 4 at $DIR/checked.rs:+6:13: +6:18
+      }
+  
+      bb2: {
+-         _8 = move (_10.0: i32);          // scope 4 at $DIR/checked.rs:+6:13: +6:18
++         _8 = const i32::MIN;             // scope 4 at $DIR/checked.rs:+6:13: +6:18
+          StorageDead(_9);                 // scope 4 at $DIR/checked.rs:+6:17: +6:18
+          _0 = const ();                   // scope 0 at $DIR/checked.rs:+0:11: +7:2
+          StorageDead(_8);                 // scope 4 at $DIR/checked.rs:+7:1: +7:2
+          StorageDead(_7);                 // scope 3 at $DIR/checked.rs:+7:1: +7:2
+          StorageDead(_3);                 // scope 2 at $DIR/checked.rs:+7:1: +7:2
+          StorageDead(_2);                 // scope 1 at $DIR/checked.rs:+7:1: +7:2
+          StorageDead(_1);                 // scope 0 at $DIR/checked.rs:+7:1: +7:2
+          return;                          // scope 0 at $DIR/checked.rs:+7:2: +7:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/checked.rs b/src/test/mir-opt/dataflow-const-prop/checked.rs
new file mode 100644
index 00000000000..ae457af72c0
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/checked.rs
@@ -0,0 +1,13 @@
+// unit-test: DataflowConstProp
+// compile-flags: -C overflow-checks=on
+
+// EMIT_MIR checked.main.DataflowConstProp.diff
+#[allow(arithmetic_overflow)]
+fn main() {
+    let a = 1;
+    let b = 2;
+    let c = a + b;
+
+    let d = i32::MAX;
+    let e = d + 1;
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..951e5c5b9ad
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/if.main.DataflowConstProp.diff
@@ -0,0 +1,118 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/if.rs:+0:11: +0:11
+      let _1: i32;                         // in scope 0 at $DIR/if.rs:+2:9: +2:10
+      let mut _3: bool;                    // in scope 0 at $DIR/if.rs:+3:16: +3:24
+      let mut _4: i32;                     // in scope 0 at $DIR/if.rs:+3:16: +3:19
+      let mut _5: &i32;                    // in scope 0 at $DIR/if.rs:+3:17: +3:19
+      let mut _7: i32;                     // in scope 0 at $DIR/if.rs:+4:13: +4:14
+      let mut _9: bool;                    // in scope 0 at $DIR/if.rs:+6:16: +6:24
+      let mut _10: i32;                    // in scope 0 at $DIR/if.rs:+6:16: +6:19
+      let mut _11: &i32;                   // in scope 0 at $DIR/if.rs:+6:17: +6:19
+      let mut _12: i32;                    // in scope 0 at $DIR/if.rs:+6:38: +6:39
+      let mut _14: i32;                    // in scope 0 at $DIR/if.rs:+7:13: +7:14
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/if.rs:+2:9: +2:10
+          let _2: i32;                     // in scope 1 at $DIR/if.rs:+3:9: +3:10
+          scope 2 {
+              debug b => _2;               // in scope 2 at $DIR/if.rs:+3:9: +3:10
+              let _6: i32;                 // in scope 2 at $DIR/if.rs:+4:9: +4:10
+              scope 3 {
+                  debug c => _6;           // in scope 3 at $DIR/if.rs:+4:9: +4:10
+                  let _8: i32;             // in scope 3 at $DIR/if.rs:+6:9: +6:10
+                  scope 4 {
+                      debug d => _8;       // in scope 4 at $DIR/if.rs:+6:9: +6:10
+                      let _13: i32;        // in scope 4 at $DIR/if.rs:+7:9: +7:10
+                      scope 5 {
+                          debug e => _13;  // in scope 5 at $DIR/if.rs:+7:9: +7:10
+                      }
+                  }
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/if.rs:+2:9: +2:10
+          _1 = const 1_i32;                // scope 0 at $DIR/if.rs:+2:13: +2:14
+          StorageLive(_2);                 // scope 1 at $DIR/if.rs:+3:9: +3:10
+          StorageLive(_3);                 // scope 1 at $DIR/if.rs:+3:16: +3:24
+          StorageLive(_4);                 // scope 1 at $DIR/if.rs:+3:16: +3:19
+          StorageLive(_5);                 // scope 1 at $DIR/if.rs:+3:17: +3:19
+          _5 = &_1;                        // scope 1 at $DIR/if.rs:+3:17: +3:19
+-         _4 = (*_5);                      // scope 1 at $DIR/if.rs:+3:16: +3:19
+-         _3 = Eq(move _4, const 1_i32);   // scope 1 at $DIR/if.rs:+3:16: +3:24
++         _4 = const 1_i32;                // scope 1 at $DIR/if.rs:+3:16: +3:19
++         _3 = const true;                 // scope 1 at $DIR/if.rs:+3:16: +3:24
+          StorageDead(_5);                 // scope 1 at $DIR/if.rs:+3:23: +3:24
+          StorageDead(_4);                 // scope 1 at $DIR/if.rs:+3:23: +3:24
+-         switchInt(move _3) -> [false: bb2, otherwise: bb1]; // scope 1 at $DIR/if.rs:+3:16: +3:24
++         switchInt(const true) -> [false: bb2, otherwise: bb1]; // scope 1 at $DIR/if.rs:+3:16: +3:24
+      }
+  
+      bb1: {
+          _2 = const 2_i32;                // scope 1 at $DIR/if.rs:+3:27: +3:28
+          goto -> bb3;                     // scope 1 at $DIR/if.rs:+3:13: +3:41
+      }
+  
+      bb2: {
+          _2 = const 3_i32;                // scope 1 at $DIR/if.rs:+3:38: +3:39
+          goto -> bb3;                     // scope 1 at $DIR/if.rs:+3:13: +3:41
+      }
+  
+      bb3: {
+          StorageDead(_3);                 // scope 1 at $DIR/if.rs:+3:40: +3:41
+          StorageLive(_6);                 // scope 2 at $DIR/if.rs:+4:9: +4:10
+          StorageLive(_7);                 // scope 2 at $DIR/if.rs:+4:13: +4:14
+          _7 = _2;                         // scope 2 at $DIR/if.rs:+4:13: +4:14
+          _6 = Add(move _7, const 1_i32);  // scope 2 at $DIR/if.rs:+4:13: +4:18
+          StorageDead(_7);                 // scope 2 at $DIR/if.rs:+4:17: +4:18
+          StorageLive(_8);                 // scope 3 at $DIR/if.rs:+6:9: +6:10
+          StorageLive(_9);                 // scope 3 at $DIR/if.rs:+6:16: +6:24
+          StorageLive(_10);                // scope 3 at $DIR/if.rs:+6:16: +6:19
+          StorageLive(_11);                // scope 3 at $DIR/if.rs:+6:17: +6:19
+          _11 = &_1;                       // scope 3 at $DIR/if.rs:+6:17: +6:19
+-         _10 = (*_11);                    // scope 3 at $DIR/if.rs:+6:16: +6:19
+-         _9 = Eq(move _10, const 1_i32);  // scope 3 at $DIR/if.rs:+6:16: +6:24
++         _10 = const 1_i32;               // scope 3 at $DIR/if.rs:+6:16: +6:19
++         _9 = const true;                 // scope 3 at $DIR/if.rs:+6:16: +6:24
+          StorageDead(_11);                // scope 3 at $DIR/if.rs:+6:23: +6:24
+          StorageDead(_10);                // scope 3 at $DIR/if.rs:+6:23: +6:24
+-         switchInt(move _9) -> [false: bb5, otherwise: bb4]; // scope 3 at $DIR/if.rs:+6:16: +6:24
++         switchInt(const true) -> [false: bb5, otherwise: bb4]; // scope 3 at $DIR/if.rs:+6:16: +6:24
+      }
+  
+      bb4: {
+-         _8 = _1;                         // scope 3 at $DIR/if.rs:+6:27: +6:28
++         _8 = const 1_i32;                // scope 3 at $DIR/if.rs:+6:27: +6:28
+          goto -> bb6;                     // scope 3 at $DIR/if.rs:+6:13: +6:45
+      }
+  
+      bb5: {
+          StorageLive(_12);                // scope 3 at $DIR/if.rs:+6:38: +6:39
+          _12 = _1;                        // scope 3 at $DIR/if.rs:+6:38: +6:39
+          _8 = Add(move _12, const 1_i32); // scope 3 at $DIR/if.rs:+6:38: +6:43
+          StorageDead(_12);                // scope 3 at $DIR/if.rs:+6:42: +6:43
+          goto -> bb6;                     // scope 3 at $DIR/if.rs:+6:13: +6:45
+      }
+  
+      bb6: {
+          StorageDead(_9);                 // scope 3 at $DIR/if.rs:+6:44: +6:45
+          StorageLive(_13);                // scope 4 at $DIR/if.rs:+7:9: +7:10
+          StorageLive(_14);                // scope 4 at $DIR/if.rs:+7:13: +7:14
+-         _14 = _8;                        // scope 4 at $DIR/if.rs:+7:13: +7:14
+-         _13 = Add(move _14, const 1_i32); // scope 4 at $DIR/if.rs:+7:13: +7:18
++         _14 = const 1_i32;               // scope 4 at $DIR/if.rs:+7:13: +7:14
++         _13 = const 2_i32;               // scope 4 at $DIR/if.rs:+7:13: +7:18
+          StorageDead(_14);                // scope 4 at $DIR/if.rs:+7:17: +7:18
+          _0 = const ();                   // scope 0 at $DIR/if.rs:+0:11: +8:2
+          StorageDead(_13);                // scope 4 at $DIR/if.rs:+8:1: +8:2
+          StorageDead(_8);                 // scope 3 at $DIR/if.rs:+8:1: +8:2
+          StorageDead(_6);                 // scope 2 at $DIR/if.rs:+8:1: +8:2
+          StorageDead(_2);                 // scope 1 at $DIR/if.rs:+8:1: +8:2
+          StorageDead(_1);                 // scope 0 at $DIR/if.rs:+8:1: +8:2
+          return;                          // scope 0 at $DIR/if.rs:+8:2: +8:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/if.rs b/src/test/mir-opt/dataflow-const-prop/if.rs
new file mode 100644
index 00000000000..8df89080477
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/if.rs
@@ -0,0 +1,12 @@
+// unit-test: DataflowConstProp
+
+// EMIT_MIR if.main.DataflowConstProp.diff
+fn main() {
+    // This does not work (yet). Needs perhaps additional state to track unreachability.
+    let a = 1;
+    let b = if *&a == 1 { 2 } else { 3 };
+    let c = b + 1;
+
+    let d = if *&a == 1 { a } else { a + 1 };
+    let e = d + 1;
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..d5a17d08356
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/promoted.main.DataflowConstProp.diff
@@ -0,0 +1,29 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/promoted.rs:+0:11: +0:11
+      let _1: i32;                         // in scope 0 at $DIR/promoted.rs:+2:9: +2:10
+      let mut _2: &i32;                    // in scope 0 at $DIR/promoted.rs:+2:14: +2:17
+      let _3: i32;                         // in scope 0 at $DIR/promoted.rs:+2:15: +2:17
+      let mut _4: &i32;                    // in scope 0 at $DIR/promoted.rs:+2:14: +2:17
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/promoted.rs:+2:9: +2:10
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/promoted.rs:+2:9: +2:10
+          StorageLive(_2);                 // scope 0 at $DIR/promoted.rs:+2:14: +2:17
+          _4 = const main::promoted[0];    // scope 0 at $DIR/promoted.rs:+2:14: +2:17
+                                           // mir::Constant
+                                           // + span: $DIR/promoted.rs:6:14: 6:17
+                                           // + literal: Const { ty: &i32, val: Unevaluated(main, [], Some(promoted[0])) }
+          _2 = &(*_4);                     // scope 0 at $DIR/promoted.rs:+2:14: +2:17
+          _1 = (*_2);                      // scope 0 at $DIR/promoted.rs:+2:13: +2:17
+          StorageDead(_2);                 // scope 0 at $DIR/promoted.rs:+2:17: +2:18
+          _0 = const ();                   // scope 0 at $DIR/promoted.rs:+0:11: +3:2
+          StorageDead(_1);                 // scope 0 at $DIR/promoted.rs:+3:1: +3:2
+          return;                          // scope 0 at $DIR/promoted.rs:+3:2: +3:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/promoted.rs b/src/test/mir-opt/dataflow-const-prop/promoted.rs
new file mode 100644
index 00000000000..1653cf33344
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/promoted.rs
@@ -0,0 +1,7 @@
+// unit-test: DataflowConstProp
+
+// EMIT_MIR promoted.main.DataflowConstProp.diff
+fn main() {
+    // This does not work because `&42` gets promoted.
+    let a = *&42;
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..953628cff06
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/recursive.main.DataflowConstProp.diff
@@ -0,0 +1,133 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/recursive.rs:+0:11: +0:11
+      let _1: S;                           // in scope 0 at $DIR/recursive.rs:+1:9: +1:10
+      let mut _3: &S;                      // in scope 0 at $DIR/recursive.rs:+2:20: +2:22
+      let _4: &S;                          // in scope 0 at $DIR/recursive.rs:+2:20: +2:22
+      let mut _6: &S;                      // in scope 0 at $DIR/recursive.rs:+3:20: +3:22
+      let _7: &S;                          // in scope 0 at $DIR/recursive.rs:+3:20: +3:22
+      let mut _9: isize;                   // in scope 0 at $DIR/recursive.rs:+5:9: +5:18
+      let mut _11: isize;                  // in scope 0 at $DIR/recursive.rs:+6:13: +6:22
+      let mut _13: isize;                  // in scope 0 at $DIR/recursive.rs:+7:17: +7:28
+      let mut _15: !;                      // in scope 0 at $DIR/recursive.rs:+8:22: +8:43
+      let mut _16: !;                      // in scope 0 at $DIR/recursive.rs:+10:18: +10:39
+      let mut _17: !;                      // in scope 0 at $DIR/recursive.rs:+12:14: +12:35
+      let mut _18: &S;                     // in scope 0 at $DIR/recursive.rs:+6:20: +6:21
+      let mut _19: &S;                     // in scope 0 at $DIR/recursive.rs:+6:20: +6:21
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/recursive.rs:+1:9: +1:10
+          let _2: S;                       // in scope 1 at $DIR/recursive.rs:+2:9: +2:10
+          scope 2 {
+              debug b => _2;               // in scope 2 at $DIR/recursive.rs:+2:9: +2:10
+              let _5: S;                   // in scope 2 at $DIR/recursive.rs:+3:9: +3:10
+              scope 3 {
+                  debug c => _5;           // in scope 3 at $DIR/recursive.rs:+3:9: +3:10
+                  let _8: u32;             // in scope 3 at $DIR/recursive.rs:+4:9: +4:10
+                  let _10: &S;             // in scope 3 at $DIR/recursive.rs:+5:16: +5:17
+                  scope 4 {
+                      debug d => _8;       // in scope 4 at $DIR/recursive.rs:+4:9: +4:10
+                  }
+                  scope 5 {
+                      debug b => _10;      // in scope 5 at $DIR/recursive.rs:+5:16: +5:17
+                      let _12: &&S;        // in scope 5 at $DIR/recursive.rs:+6:20: +6:21
+                      scope 6 {
+                          debug a => _12;  // in scope 6 at $DIR/recursive.rs:+6:20: +6:21
+                          let _14: &u32;   // in scope 6 at $DIR/recursive.rs:+7:24: +7:27
+                          scope 7 {
+                              debug num => _14; // in scope 7 at $DIR/recursive.rs:+7:24: +7:27
+                          }
+                      }
+                  }
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/recursive.rs:+1:9: +1:10
+          Deinit(_1);                      // scope 0 at $DIR/recursive.rs:+1:13: +1:22
+          ((_1 as Num).0: u32) = const 0_u32; // scope 0 at $DIR/recursive.rs:+1:13: +1:22
+          discriminant(_1) = 1;            // scope 0 at $DIR/recursive.rs:+1:13: +1:22
+          StorageLive(_2);                 // scope 1 at $DIR/recursive.rs:+2:9: +2:10
+          StorageLive(_3);                 // scope 1 at $DIR/recursive.rs:+2:20: +2:22
+          StorageLive(_4);                 // scope 1 at $DIR/recursive.rs:+2:20: +2:22
+          _4 = &_1;                        // scope 1 at $DIR/recursive.rs:+2:20: +2:22
+          _3 = &(*_4);                     // scope 1 at $DIR/recursive.rs:+2:20: +2:22
+          Deinit(_2);                      // scope 1 at $DIR/recursive.rs:+2:13: +2:23
+          ((_2 as Rec).0: &S) = move _3;   // scope 1 at $DIR/recursive.rs:+2:13: +2:23
+          discriminant(_2) = 0;            // scope 1 at $DIR/recursive.rs:+2:13: +2:23
+          StorageDead(_3);                 // scope 1 at $DIR/recursive.rs:+2:22: +2:23
+          StorageDead(_4);                 // scope 1 at $DIR/recursive.rs:+2:23: +2:24
+          StorageLive(_5);                 // scope 2 at $DIR/recursive.rs:+3:9: +3:10
+          StorageLive(_6);                 // scope 2 at $DIR/recursive.rs:+3:20: +3:22
+          StorageLive(_7);                 // scope 2 at $DIR/recursive.rs:+3:20: +3:22
+          _7 = &_2;                        // scope 2 at $DIR/recursive.rs:+3:20: +3:22
+          _6 = &(*_7);                     // scope 2 at $DIR/recursive.rs:+3:20: +3:22
+          Deinit(_5);                      // scope 2 at $DIR/recursive.rs:+3:13: +3:23
+          ((_5 as Rec).0: &S) = move _6;   // scope 2 at $DIR/recursive.rs:+3:13: +3:23
+          discriminant(_5) = 0;            // scope 2 at $DIR/recursive.rs:+3:13: +3:23
+          StorageDead(_6);                 // scope 2 at $DIR/recursive.rs:+3:22: +3:23
+          StorageDead(_7);                 // scope 2 at $DIR/recursive.rs:+3:23: +3:24
+          StorageLive(_8);                 // scope 3 at $DIR/recursive.rs:+4:9: +4:10
+          _9 = discriminant(_5);           // scope 3 at $DIR/recursive.rs:+4:19: +4:20
+          switchInt(move _9) -> [0_isize: bb2, otherwise: bb1]; // scope 3 at $DIR/recursive.rs:+4:13: +4:20
+      }
+  
+      bb1: {
+          StorageLive(_17);                // scope 3 at $DIR/recursive.rs:+12:14: +12:35
+          _17 = exit(const 0_i32);         // scope 3 at $DIR/recursive.rs:+12:14: +12:35
+                                           // mir::Constant
+                                           // + span: $DIR/recursive.rs:21:14: 21:32
+                                           // + literal: Const { ty: fn(i32) -> ! {exit}, val: Value(<ZST>) }
+      }
+  
+      bb2: {
+          StorageLive(_10);                // scope 3 at $DIR/recursive.rs:+5:16: +5:17
+          _10 = ((_5 as Rec).0: &S);       // scope 3 at $DIR/recursive.rs:+5:16: +5:17
+          _11 = discriminant((*_10));      // scope 5 at $DIR/recursive.rs:+5:28: +5:29
+          switchInt(move _11) -> [0_isize: bb4, otherwise: bb3]; // scope 5 at $DIR/recursive.rs:+5:22: +5:29
+      }
+  
+      bb3: {
+          StorageLive(_16);                // scope 5 at $DIR/recursive.rs:+10:18: +10:39
+          _16 = exit(const 0_i32);         // scope 5 at $DIR/recursive.rs:+10:18: +10:39
+                                           // mir::Constant
+                                           // + span: $DIR/recursive.rs:19:18: 19:36
+                                           // + literal: Const { ty: fn(i32) -> ! {exit}, val: Value(<ZST>) }
+      }
+  
+      bb4: {
+          StorageLive(_12);                // scope 5 at $DIR/recursive.rs:+6:20: +6:21
+          _12 = &(((*_10) as Rec).0: &S);  // scope 5 at $DIR/recursive.rs:+6:20: +6:21
+          _18 = deref_copy (*_12);         // scope 6 at $DIR/recursive.rs:+6:32: +6:33
+          _13 = discriminant((*_18));      // scope 6 at $DIR/recursive.rs:+6:32: +6:33
+          switchInt(move _13) -> [1_isize: bb6, otherwise: bb5]; // scope 6 at $DIR/recursive.rs:+6:26: +6:33
+      }
+  
+      bb5: {
+          StorageLive(_15);                // scope 6 at $DIR/recursive.rs:+8:22: +8:43
+          _15 = exit(const 0_i32);         // scope 6 at $DIR/recursive.rs:+8:22: +8:43
+                                           // mir::Constant
+                                           // + span: $DIR/recursive.rs:17:22: 17:40
+                                           // + literal: Const { ty: fn(i32) -> ! {exit}, val: Value(<ZST>) }
+      }
+  
+      bb6: {
+          StorageLive(_14);                // scope 6 at $DIR/recursive.rs:+7:24: +7:27
+          _19 = deref_copy (*_12);         // scope 6 at $DIR/recursive.rs:+7:24: +7:27
+          _14 = &(((*_19) as Num).0: u32); // scope 6 at $DIR/recursive.rs:+7:24: +7:27
+-         _8 = (*_14);                     // scope 7 at $DIR/recursive.rs:+7:32: +7:36
++         _8 = const 0_u32;                // scope 7 at $DIR/recursive.rs:+7:32: +7:36
+          StorageDead(_14);                // scope 6 at $DIR/recursive.rs:+7:35: +7:36
+          StorageDead(_12);                // scope 5 at $DIR/recursive.rs:+9:13: +9:14
+          StorageDead(_10);                // scope 3 at $DIR/recursive.rs:+11:9: +11:10
+          _0 = const ();                   // scope 0 at $DIR/recursive.rs:+0:11: +14:2
+          StorageDead(_8);                 // scope 3 at $DIR/recursive.rs:+14:1: +14:2
+          StorageDead(_5);                 // scope 2 at $DIR/recursive.rs:+14:1: +14:2
+          StorageDead(_2);                 // scope 1 at $DIR/recursive.rs:+14:1: +14:2
+          StorageDead(_1);                 // scope 0 at $DIR/recursive.rs:+14:1: +14:2
+          return;                          // scope 0 at $DIR/recursive.rs:+14:2: +14:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/recursive.rs b/src/test/mir-opt/dataflow-const-prop/recursive.rs
new file mode 100644
index 00000000000..0eda1239ecd
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/recursive.rs
@@ -0,0 +1,23 @@
+// unit-test: DataflowConstProp
+
+enum S<'a> {
+    Rec(&'a S<'a>),
+    Num(u32),
+}
+
+// EMIT_MIR recursive.main.DataflowConstProp.diff
+fn main() {
+    let a = S::Num(0);
+    let b = S::Rec(&a);
+    let c = S::Rec(&b);
+    let d = match c {
+        S::Rec(b) => match b {
+            S::Rec(a) => match a {
+                S::Num(num) => *num,
+                _ => std::process::exit(0),
+            },
+            _ => std::process::exit(0),
+        },
+        _ => std::process::exit(0),
+    };
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..6af381617f9
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/ref.main.DataflowConstProp.diff
@@ -0,0 +1,89 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/ref.rs:+0:11: +0:11
+      let _1: i32;                         // in scope 0 at $DIR/ref.rs:+1:9: +1:10
+      let mut _4: bool;                    // in scope 0 at $DIR/ref.rs:+3:16: +3:43
+      let mut _5: u32;                     // in scope 0 at $DIR/ref.rs:+3:16: +3:38
+      let mut _6: u32;                     // in scope 0 at $DIR/ref.rs:+3:16: +3:34
+      let mut _7: bool;                    // in scope 0 at $DIR/ref.rs:+3:16: +3:38
+      let _8: &i32;                        // in scope 0 at $DIR/ref.rs:+3:58: +3:60
+      let mut _10: i32;                    // in scope 0 at $DIR/ref.rs:+4:13: +4:15
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/ref.rs:+1:9: +1:10
+          let _2: i32;                     // in scope 1 at $DIR/ref.rs:+2:9: +2:10
+          scope 2 {
+              debug b => _2;               // in scope 2 at $DIR/ref.rs:+2:9: +2:10
+              let _3: &i32;                // in scope 2 at $DIR/ref.rs:+3:9: +3:10
+              scope 3 {
+                  debug c => _3;           // in scope 3 at $DIR/ref.rs:+3:9: +3:10
+                  let _9: i32;             // in scope 3 at $DIR/ref.rs:+4:9: +4:10
+                  scope 4 {
+                      debug d => _9;       // in scope 4 at $DIR/ref.rs:+4:9: +4:10
+                  }
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/ref.rs:+1:9: +1:10
+          _1 = const 0_i32;                // scope 0 at $DIR/ref.rs:+1:13: +1:14
+          StorageLive(_2);                 // scope 1 at $DIR/ref.rs:+2:9: +2:10
+          _2 = const 0_i32;                // scope 1 at $DIR/ref.rs:+2:13: +2:14
+          StorageLive(_3);                 // scope 2 at $DIR/ref.rs:+3:9: +3:10
+          StorageLive(_4);                 // scope 2 at $DIR/ref.rs:+3:16: +3:43
+          StorageLive(_5);                 // scope 2 at $DIR/ref.rs:+3:16: +3:38
+          StorageLive(_6);                 // scope 2 at $DIR/ref.rs:+3:16: +3:34
+          _6 = id() -> bb1;                // scope 2 at $DIR/ref.rs:+3:16: +3:34
+                                           // mir::Constant
+                                           // + span: $DIR/ref.rs:7:16: 7:32
+                                           // + literal: Const { ty: fn() -> u32 {id}, val: Value(<ZST>) }
+      }
+  
+      bb1: {
+-         _7 = Eq(const 2_u32, const 0_u32); // scope 2 at $DIR/ref.rs:+3:16: +3:38
+-         assert(!move _7, "attempt to calculate the remainder of `{}` with a divisor of zero", _6) -> bb2; // scope 2 at $DIR/ref.rs:+3:16: +3:38
++         _7 = const false;                // scope 2 at $DIR/ref.rs:+3:16: +3:38
++         assert(!const false, "attempt to calculate the remainder of `{}` with a divisor of zero", _6) -> bb2; // scope 2 at $DIR/ref.rs:+3:16: +3:38
+      }
+  
+      bb2: {
+          _5 = Rem(move _6, const 2_u32);  // scope 2 at $DIR/ref.rs:+3:16: +3:38
+          StorageDead(_6);                 // scope 2 at $DIR/ref.rs:+3:37: +3:38
+          _4 = Eq(move _5, const 0_u32);   // scope 2 at $DIR/ref.rs:+3:16: +3:43
+          StorageDead(_5);                 // scope 2 at $DIR/ref.rs:+3:42: +3:43
+          switchInt(move _4) -> [false: bb4, otherwise: bb3]; // scope 2 at $DIR/ref.rs:+3:16: +3:43
+      }
+  
+      bb3: {
+          _3 = &_1;                        // scope 2 at $DIR/ref.rs:+3:46: +3:48
+          goto -> bb5;                     // scope 2 at $DIR/ref.rs:+3:13: +3:62
+      }
+  
+      bb4: {
+          StorageLive(_8);                 // scope 2 at $DIR/ref.rs:+3:58: +3:60
+          _8 = &_2;                        // scope 2 at $DIR/ref.rs:+3:58: +3:60
+          _3 = &(*_8);                     // scope 2 at $DIR/ref.rs:+3:58: +3:60
+          StorageDead(_8);                 // scope 2 at $DIR/ref.rs:+3:61: +3:62
+          goto -> bb5;                     // scope 2 at $DIR/ref.rs:+3:13: +3:62
+      }
+  
+      bb5: {
+          StorageDead(_4);                 // scope 2 at $DIR/ref.rs:+3:61: +3:62
+          StorageLive(_9);                 // scope 3 at $DIR/ref.rs:+4:9: +4:10
+          StorageLive(_10);                // scope 3 at $DIR/ref.rs:+4:13: +4:15
+-         _10 = (*_3);                     // scope 3 at $DIR/ref.rs:+4:13: +4:15
+-         _9 = Add(move _10, const 1_i32); // scope 3 at $DIR/ref.rs:+4:13: +4:19
++         _10 = const 0_i32;               // scope 3 at $DIR/ref.rs:+4:13: +4:15
++         _9 = const 1_i32;                // scope 3 at $DIR/ref.rs:+4:13: +4:19
+          StorageDead(_10);                // scope 3 at $DIR/ref.rs:+4:18: +4:19
+          _0 = const ();                   // scope 0 at $DIR/ref.rs:+0:11: +5:2
+          StorageDead(_9);                 // scope 3 at $DIR/ref.rs:+5:1: +5:2
+          StorageDead(_3);                 // scope 2 at $DIR/ref.rs:+5:1: +5:2
+          StorageDead(_2);                 // scope 1 at $DIR/ref.rs:+5:1: +5:2
+          StorageDead(_1);                 // scope 0 at $DIR/ref.rs:+5:1: +5:2
+          return;                          // scope 0 at $DIR/ref.rs:+5:2: +5:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/ref.rs b/src/test/mir-opt/dataflow-const-prop/ref.rs
new file mode 100644
index 00000000000..6da613fc516
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/ref.rs
@@ -0,0 +1,9 @@
+// unit-test: DataflowConstProp
+
+// EMIT_MIR ref.main.DataflowConstProp.diff
+fn main() {
+    let a = 0;
+    let b = 0;
+    let c = if std::process::id() % 2 == 0 { &a } else { &b };
+    let d = *c + 1;
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..fe5bdd07d4a
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/ref_mut.main.DataflowConstProp.diff
@@ -0,0 +1,77 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/ref_mut.rs:+0:11: +0:11
+      let mut _1: i32;                     // in scope 0 at $DIR/ref_mut.rs:+1:9: +1:14
+      let mut _7: &i32;                    // in scope 0 at $DIR/ref_mut.rs:+9:10: +9:12
+      let _8: &i32;                        // in scope 0 at $DIR/ref_mut.rs:+9:10: +9:12
+      let _9: i32;                         // in scope 0 at $DIR/ref_mut.rs:+9:11: +9:12
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/ref_mut.rs:+1:9: +1:14
+          let _2: &mut i32;                // in scope 1 at $DIR/ref_mut.rs:+2:9: +2:10
+          scope 2 {
+              debug b => _2;               // in scope 2 at $DIR/ref_mut.rs:+2:9: +2:10
+              let _3: i32;                 // in scope 2 at $DIR/ref_mut.rs:+4:9: +4:10
+              scope 3 {
+                  debug c => _3;           // in scope 3 at $DIR/ref_mut.rs:+4:9: +4:10
+                  let _4: i32;             // in scope 3 at $DIR/ref_mut.rs:+6:9: +6:10
+                  scope 4 {
+                      debug d => _4;       // in scope 4 at $DIR/ref_mut.rs:+6:9: +6:10
+                      let mut _5: &i32;    // in scope 4 at $DIR/ref_mut.rs:+7:9: +7:14
+                      scope 5 {
+                          debug e => _5;   // in scope 5 at $DIR/ref_mut.rs:+7:9: +7:14
+                          let _6: &mut &i32; // in scope 5 at $DIR/ref_mut.rs:+8:9: +8:10
+                          scope 6 {
+                              debug f => _6; // in scope 6 at $DIR/ref_mut.rs:+8:9: +8:10
+                              let _10: i32; // in scope 6 at $DIR/ref_mut.rs:+10:9: +10:10
+                              let mut _11: &i32; // in scope 6 at $DIR/ref_mut.rs:+9:10: +9:12
+                              scope 7 {
+                                  debug g => _10; // in scope 7 at $DIR/ref_mut.rs:+10:9: +10:10
+                              }
+                          }
+                      }
+                  }
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/ref_mut.rs:+1:9: +1:14
+          _1 = const 0_i32;                // scope 0 at $DIR/ref_mut.rs:+1:17: +1:18
+          StorageLive(_2);                 // scope 1 at $DIR/ref_mut.rs:+2:9: +2:10
+          _2 = &mut _1;                    // scope 1 at $DIR/ref_mut.rs:+2:13: +2:19
+          (*_2) = const 1_i32;             // scope 2 at $DIR/ref_mut.rs:+3:5: +3:11
+          StorageLive(_3);                 // scope 2 at $DIR/ref_mut.rs:+4:9: +4:10
+          _3 = _1;                         // scope 2 at $DIR/ref_mut.rs:+4:13: +4:14
+          StorageLive(_4);                 // scope 3 at $DIR/ref_mut.rs:+6:9: +6:10
+          _4 = const 0_i32;                // scope 3 at $DIR/ref_mut.rs:+6:13: +6:14
+          StorageLive(_5);                 // scope 4 at $DIR/ref_mut.rs:+7:9: +7:14
+          _5 = &_4;                        // scope 4 at $DIR/ref_mut.rs:+7:17: +7:19
+          StorageLive(_6);                 // scope 5 at $DIR/ref_mut.rs:+8:9: +8:10
+          _6 = &mut _5;                    // scope 5 at $DIR/ref_mut.rs:+8:13: +8:19
+          StorageLive(_7);                 // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12
+          StorageLive(_8);                 // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12
+          _11 = const main::promoted[0];   // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12
+                                           // mir::Constant
+                                           // + span: $DIR/ref_mut.rs:13:10: 13:12
+                                           // + literal: Const { ty: &i32, val: Unevaluated(main, [], Some(promoted[0])) }
+          _8 = &(*_11);                    // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12
+          _7 = &(*_8);                     // scope 6 at $DIR/ref_mut.rs:+9:10: +9:12
+          (*_6) = move _7;                 // scope 6 at $DIR/ref_mut.rs:+9:5: +9:12
+          StorageDead(_7);                 // scope 6 at $DIR/ref_mut.rs:+9:11: +9:12
+          StorageDead(_8);                 // scope 6 at $DIR/ref_mut.rs:+9:12: +9:13
+          StorageLive(_10);                // scope 6 at $DIR/ref_mut.rs:+10:9: +10:10
+          _10 = (*_5);                     // scope 6 at $DIR/ref_mut.rs:+10:13: +10:15
+          _0 = const ();                   // scope 0 at $DIR/ref_mut.rs:+0:11: +11:2
+          StorageDead(_10);                // scope 6 at $DIR/ref_mut.rs:+11:1: +11:2
+          StorageDead(_6);                 // scope 5 at $DIR/ref_mut.rs:+11:1: +11:2
+          StorageDead(_5);                 // scope 4 at $DIR/ref_mut.rs:+11:1: +11:2
+          StorageDead(_4);                 // scope 3 at $DIR/ref_mut.rs:+11:1: +11:2
+          StorageDead(_3);                 // scope 2 at $DIR/ref_mut.rs:+11:1: +11:2
+          StorageDead(_2);                 // scope 1 at $DIR/ref_mut.rs:+11:1: +11:2
+          StorageDead(_1);                 // scope 0 at $DIR/ref_mut.rs:+11:1: +11:2
+          return;                          // scope 0 at $DIR/ref_mut.rs:+11:2: +11:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/ref_mut.rs b/src/test/mir-opt/dataflow-const-prop/ref_mut.rs
new file mode 100644
index 00000000000..8b2baf7676d
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/ref_mut.rs
@@ -0,0 +1,15 @@
+// unit-test: DataflowConstProp
+
+// EMIT_MIR ref_mut.main.DataflowConstProp.diff
+fn main() {
+    let mut a = 0;
+    let b = &mut a;
+    *b = 1;
+    let c = a;
+
+    let d = 0;
+    let mut e = &d;
+    let f = &mut e;
+    *f = &1;
+    let g = *e;
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..1d01d4fd2e7
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/terminator.main.DataflowConstProp.diff
@@ -0,0 +1,40 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/terminator.rs:+0:11: +0:11
+      let _1: i32;                         // in scope 0 at $DIR/terminator.rs:+1:9: +1:10
+      let _2: ();                          // in scope 0 at $DIR/terminator.rs:+2:5: +2:15
+      let mut _3: i32;                     // in scope 0 at $DIR/terminator.rs:+2:9: +2:14
+      let mut _4: i32;                     // in scope 0 at $DIR/terminator.rs:+2:9: +2:10
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/terminator.rs:+1:9: +1:10
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/terminator.rs:+1:9: +1:10
+          _1 = const 1_i32;                // scope 0 at $DIR/terminator.rs:+1:13: +1:14
+          StorageLive(_2);                 // scope 1 at $DIR/terminator.rs:+2:5: +2:15
+          StorageLive(_3);                 // scope 1 at $DIR/terminator.rs:+2:9: +2:14
+          StorageLive(_4);                 // scope 1 at $DIR/terminator.rs:+2:9: +2:10
+-         _4 = _1;                         // scope 1 at $DIR/terminator.rs:+2:9: +2:10
+-         _3 = Add(move _4, const 1_i32);  // scope 1 at $DIR/terminator.rs:+2:9: +2:14
++         _4 = const 1_i32;                // scope 1 at $DIR/terminator.rs:+2:9: +2:10
++         _3 = const 2_i32;                // scope 1 at $DIR/terminator.rs:+2:9: +2:14
+          StorageDead(_4);                 // scope 1 at $DIR/terminator.rs:+2:13: +2:14
+-         _2 = foo(move _3) -> bb1;        // scope 1 at $DIR/terminator.rs:+2:5: +2:15
++         _2 = foo(const 2_i32) -> bb1;    // scope 1 at $DIR/terminator.rs:+2:5: +2:15
+                                           // mir::Constant
+                                           // + span: $DIR/terminator.rs:8:5: 8:8
+                                           // + literal: Const { ty: fn(i32) {foo}, val: Value(<ZST>) }
+      }
+  
+      bb1: {
+          StorageDead(_3);                 // scope 1 at $DIR/terminator.rs:+2:14: +2:15
+          StorageDead(_2);                 // scope 1 at $DIR/terminator.rs:+2:15: +2:16
+          _0 = const ();                   // scope 0 at $DIR/terminator.rs:+0:11: +3:2
+          StorageDead(_1);                 // scope 0 at $DIR/terminator.rs:+3:1: +3:2
+          return;                          // scope 0 at $DIR/terminator.rs:+3:2: +3:2
+      }
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/terminator.rs b/src/test/mir-opt/dataflow-const-prop/terminator.rs
new file mode 100644
index 00000000000..e96b25de63c
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/terminator.rs
@@ -0,0 +1,9 @@
+// unit-test: DataflowConstProp
+
+fn foo(n: i32) {}
+
+// EMIT_MIR terminator.main.DataflowConstProp.diff
+fn main() {
+    let a = 1;
+    foo(a + 1);
+}
diff --git a/src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff b/src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff
new file mode 100644
index 00000000000..684c661fc11
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/unnamed.main.DataflowConstProp.diff
@@ -0,0 +1,38 @@
+- // MIR for `main` before DataflowConstProp
++ // MIR for `main` after DataflowConstProp
+  
+  fn main() -> () {
+      let mut _0: ();                      // return place in scope 0 at $DIR/unnamed.rs:+0:11: +0:11
+      let mut _1: i32;                     // in scope 0 at $DIR/unnamed.rs:+1:9: +1:14
+      let mut _2: i32;                     // in scope 0 at $DIR/unnamed.rs:+3:10: +3:11
+      let mut _3: &i32;                    // in scope 0 at $DIR/unnamed.rs:+3:10: +3:11
+      scope 1 {
+          debug a => _1;                   // in scope 1 at $DIR/unnamed.rs:+1:9: +1:14
+      }
+  
+      bb0: {
+          StorageLive(_1);                 // scope 0 at $DIR/unnamed.rs:+1:9: +1:14
+          _1 = const 0_i32;                // scope 0 at $DIR/unnamed.rs:+1:17: +1:18
+-         _1 = Add(_1, const 1_i32);       // scope 1 at $DIR/unnamed.rs:+2:5: +2:11
++         _1 = const 1_i32;                // scope 1 at $DIR/unnamed.rs:+2:5: +2:11
+          StorageLive(_2);                 // scope 1 at $DIR/unnamed.rs:+3:10: +3:11
+          StorageLive(_3);                 // scope 1 at $DIR/unnamed.rs:+3:10: +3:11
+          _3 = const {alloc1: &i32};       // scope 1 at $DIR/unnamed.rs:+3:10: +3:11
+                                           // mir::Constant
+                                           // + span: $DIR/unnamed.rs:9:10: 9:11
+                                           // + literal: Const { ty: &i32, val: Value(Scalar(alloc1)) }
+          _2 = (*_3);                      // scope 1 at $DIR/unnamed.rs:+3:10: +3:11
+-         _1 = Add(_1, move _2);           // scope 1 at $DIR/unnamed.rs:+3:5: +3:11
++         _1 = Add(const 1_i32, move _2);  // scope 1 at $DIR/unnamed.rs:+3:5: +3:11
+          StorageDead(_2);                 // scope 1 at $DIR/unnamed.rs:+3:10: +3:11
+          StorageDead(_3);                 // scope 1 at $DIR/unnamed.rs:+3:11: +3:12
+          _0 = const ();                   // scope 0 at $DIR/unnamed.rs:+0:11: +4:2
+          StorageDead(_1);                 // scope 0 at $DIR/unnamed.rs:+4:1: +4:2
+          return;                          // scope 0 at $DIR/unnamed.rs:+4:2: +4:2
+      }
+  }
+  
+  alloc1 (static: g, size: 4, align: 4) {
+      02 00 00 00                                     │ ....
+  }
+  
diff --git a/src/test/mir-opt/dataflow-const-prop/unnamed.rs b/src/test/mir-opt/dataflow-const-prop/unnamed.rs
new file mode 100644
index 00000000000..91f5a9e1c12
--- /dev/null
+++ b/src/test/mir-opt/dataflow-const-prop/unnamed.rs
@@ -0,0 +1,10 @@
+// unit-test: DataflowConstProp
+
+static g: i32 = 2;
+
+// EMIT_MIR unnamed.main.DataflowConstProp.diff
+fn main() {
+    let mut a = 0;
+    a += 1;
+    a += g;
+}