about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-07-14 08:01:21 +0000
committerbors <bors@rust-lang.org>2024-07-14 08:01:21 +0000
commit0ffbddd09e02a00b762d838ea1598bfa89dd1668 (patch)
tree177207783f425e6ce97cbc40333e06f93e3a412a
parent8a63c84af5e7a201239e87d2175128907495b028 (diff)
parent6e3cff70948b2292467f2e3e1b14d14372956034 (diff)
downloadrust-0ffbddd09e02a00b762d838ea1598bfa89dd1668.tar.gz
rust-0ffbddd09e02a00b762d838ea1598bfa89dd1668.zip
Auto merge of #127087 - cjgillot:small-map, r=oli-obk
Only track mentioned places for jump threading

This PR aims to reduce the state space size in jump threading and dataflow const-prop opts.

The current implementation walks the types of all locals, and creates a place for each possible projection. This can easily lead to a large number of places and tracked values, most being useless to the actual pass.

With this PR, we instead collect places that appear syntactically in the MIR (first commit). However, this is not sufficient (second commit), and we miss places that we could track in aggregate assignments. The third commit tracks such assignments to mirror place projections, see the inline comment.

This is complementary to https://github.com/rust-lang/rust/pull/127036

r? `@oli-obk`
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs338
-rw-r--r--compiler/rustc_mir_transform/src/dataflow_const_prop.rs20
-rw-r--r--compiler/rustc_mir_transform/src/jump_threading.rs2
-rw-r--r--tests/coverage/closure.cov-map16
-rw-r--r--tests/coverage/try_error_result.cov-map199
-rw-r--r--tests/mir-opt/dataflow-const-prop/aggregate_copy.foo.DataflowConstProp.diff65
-rw-r--r--tests/mir-opt/dataflow-const-prop/aggregate_copy.rs42
-rw-r--r--tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.32bit.diff29
-rw-r--r--tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.64bit.diff29
-rw-r--r--tests/mir-opt/dataflow-const-prop/struct.rs6
-rw-r--r--tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.32bit.diff14
-rw-r--r--tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.64bit.diff14
-rw-r--r--tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-abort.diff56
-rw-r--r--tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-unwind.diff56
-rw-r--r--tests/mir-opt/jump_threading.rs16
15 files changed, 632 insertions, 270 deletions
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 1582c2e8a90..c9f5d38fe2c 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -32,15 +32,16 @@
 //! Because of that, we can assume that the only way to change the value behind a tracked place is
 //! by direct assignment.
 
-use std::collections::VecDeque;
 use std::fmt::{Debug, Formatter};
 use std::ops::Range;
 
-use rustc_data_structures::fx::{FxHashMap, StdEntry};
+use rustc_data_structures::captures::Captures;
+use rustc_data_structures::fx::{FxHashMap, FxIndexSet, StdEntry};
 use rustc_data_structures::stack::ensure_sufficient_stack;
 use rustc_index::bit_set::BitSet;
 use rustc_index::IndexVec;
 use rustc_middle::bug;
+use rustc_middle::mir::tcx::PlaceTy;
 use rustc_middle::mir::visit::{MutatingUseContext, PlaceContext, Visitor};
 use rustc_middle::mir::*;
 use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -58,7 +59,7 @@ pub trait ValueAnalysis<'tcx> {
 
     const NAME: &'static str;
 
-    fn map(&self) -> &Map;
+    fn map(&self) -> &Map<'tcx>;
 
     fn handle_statement(&self, statement: &Statement<'tcx>, state: &mut State<Self::Value>) {
         self.super_statement(statement, state)
@@ -523,12 +524,12 @@ impl<V: Clone + HasBottom> State<V> {
     }
 
     /// Assign `value` to all places that are contained in `place` or may alias one.
-    pub fn flood_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
+    pub fn flood_with(&mut self, place: PlaceRef<'_>, map: &Map<'_>, value: V) {
         self.flood_with_tail_elem(place, None, map, value)
     }
 
     /// Assign `TOP` to all places that are contained in `place` or may alias one.
-    pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map)
+    pub fn flood(&mut self, place: PlaceRef<'_>, map: &Map<'_>)
     where
         V: HasTop,
     {
@@ -536,12 +537,12 @@ impl<V: Clone + HasBottom> State<V> {
     }
 
     /// Assign `value` to the discriminant of `place` and all places that may alias it.
-    fn flood_discr_with(&mut self, place: PlaceRef<'_>, map: &Map, value: V) {
+    fn flood_discr_with(&mut self, place: PlaceRef<'_>, map: &Map<'_>, value: V) {
         self.flood_with_tail_elem(place, Some(TrackElem::Discriminant), map, value)
     }
 
     /// Assign `TOP` to the discriminant of `place` and all places that may alias it.
-    pub fn flood_discr(&mut self, place: PlaceRef<'_>, map: &Map)
+    pub fn flood_discr(&mut self, place: PlaceRef<'_>, map: &Map<'_>)
     where
         V: HasTop,
     {
@@ -559,7 +560,7 @@ impl<V: Clone + HasBottom> State<V> {
         &mut self,
         place: PlaceRef<'_>,
         tail_elem: Option<TrackElem>,
-        map: &Map,
+        map: &Map<'_>,
         value: V,
     ) {
         let State::Reachable(values) = self else { return };
@@ -570,7 +571,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// This does nothing if the place is not tracked.
     ///
     /// The target place must have been flooded before calling this method.
-    fn insert_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map) {
+    fn insert_idx(&mut self, target: PlaceIndex, result: ValueOrPlace<V>, map: &Map<'_>) {
         match result {
             ValueOrPlace::Value(value) => self.insert_value_idx(target, value, map),
             ValueOrPlace::Place(source) => self.insert_place_idx(target, source, map),
@@ -581,7 +582,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// This does nothing if the place is not tracked.
     ///
     /// The target place must have been flooded before calling this method.
-    pub fn insert_value_idx(&mut self, target: PlaceIndex, value: V, map: &Map) {
+    pub fn insert_value_idx(&mut self, target: PlaceIndex, value: V, map: &Map<'_>) {
         let State::Reachable(values) = self else { return };
         if let Some(value_index) = map.places[target].value_index {
             values.insert(value_index, value)
@@ -595,7 +596,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// places that are non-overlapping or identical.
     ///
     /// The target place must have been flooded before calling this method.
-    pub fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map) {
+    pub fn insert_place_idx(&mut self, target: PlaceIndex, source: PlaceIndex, map: &Map<'_>) {
         let State::Reachable(values) = self else { return };
 
         // If both places are tracked, we copy the value to the target.
@@ -616,7 +617,7 @@ impl<V: Clone + HasBottom> State<V> {
     }
 
     /// Helper method to interpret `target = result`.
-    pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map)
+    pub fn assign(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map<'_>)
     where
         V: HasTop,
     {
@@ -627,7 +628,7 @@ impl<V: Clone + HasBottom> State<V> {
     }
 
     /// Helper method for assignments to a discriminant.
-    pub fn assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map)
+    pub fn assign_discr(&mut self, target: PlaceRef<'_>, result: ValueOrPlace<V>, map: &Map<'_>)
     where
         V: HasTop,
     {
@@ -638,25 +639,25 @@ impl<V: Clone + HasBottom> State<V> {
     }
 
     /// Retrieve the value stored for a place, or `None` if it is not tracked.
-    pub fn try_get(&self, place: PlaceRef<'_>, map: &Map) -> Option<V> {
+    pub fn try_get(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option<V> {
         let place = map.find(place)?;
         self.try_get_idx(place, map)
     }
 
     /// Retrieve the discriminant stored for a place, or `None` if it is not tracked.
-    pub fn try_get_discr(&self, place: PlaceRef<'_>, map: &Map) -> Option<V> {
+    pub fn try_get_discr(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option<V> {
         let place = map.find_discr(place)?;
         self.try_get_idx(place, map)
     }
 
     /// Retrieve the slice length stored for a place, or `None` if it is not tracked.
-    pub fn try_get_len(&self, place: PlaceRef<'_>, map: &Map) -> Option<V> {
+    pub fn try_get_len(&self, place: PlaceRef<'_>, map: &Map<'_>) -> Option<V> {
         let place = map.find_len(place)?;
         self.try_get_idx(place, map)
     }
 
     /// Retrieve the value stored for a place index, or `None` if it is not tracked.
-    pub fn try_get_idx(&self, place: PlaceIndex, map: &Map) -> Option<V> {
+    pub fn try_get_idx(&self, place: PlaceIndex, map: &Map<'_>) -> Option<V> {
         match self {
             State::Reachable(values) => {
                 map.places[place].value_index.map(|v| values.get(v).clone())
@@ -668,7 +669,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// Retrieve the value stored for a place, or ⊤ if it is not tracked.
     ///
     /// This method returns ⊥ if the place is tracked and the state is unreachable.
-    pub fn get(&self, place: PlaceRef<'_>, map: &Map) -> V
+    pub fn get(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V
     where
         V: HasBottom + HasTop,
     {
@@ -682,7 +683,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// Retrieve the value stored for a place, or ⊤ if it is not tracked.
     ///
     /// This method returns ⊥ the current state is unreachable.
-    pub fn get_discr(&self, place: PlaceRef<'_>, map: &Map) -> V
+    pub fn get_discr(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V
     where
         V: HasBottom + HasTop,
     {
@@ -696,7 +697,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// Retrieve the value stored for a place, or ⊤ if it is not tracked.
     ///
     /// This method returns ⊥ the current state is unreachable.
-    pub fn get_len(&self, place: PlaceRef<'_>, map: &Map) -> V
+    pub fn get_len(&self, place: PlaceRef<'_>, map: &Map<'_>) -> V
     where
         V: HasBottom + HasTop,
     {
@@ -710,7 +711,7 @@ impl<V: Clone + HasBottom> State<V> {
     /// Retrieve the value stored for a place index, or ⊤ if it is not tracked.
     ///
     /// This method returns ⊥ the current state is unreachable.
-    pub fn get_idx(&self, place: PlaceIndex, map: &Map) -> V
+    pub fn get_idx(&self, place: PlaceIndex, map: &Map<'_>) -> V
     where
         V: HasBottom + HasTop,
     {
@@ -746,25 +747,25 @@ impl<V: JoinSemiLattice + Clone + HasBottom> JoinSemiLattice for State<V> {
 /// - For iteration, every [`PlaceInfo`] contains an intrusive linked list of its children.
 /// - To directly get the child for a specific projection, there is a `projections` map.
 #[derive(Debug)]
-pub struct Map {
+pub struct Map<'tcx> {
     locals: IndexVec<Local, Option<PlaceIndex>>,
     projections: FxHashMap<(PlaceIndex, TrackElem), PlaceIndex>,
-    places: IndexVec<PlaceIndex, PlaceInfo>,
+    places: IndexVec<PlaceIndex, PlaceInfo<'tcx>>,
     value_count: usize,
     // The Range corresponds to a slice into `inner_values_buffer`.
     inner_values: IndexVec<PlaceIndex, Range<usize>>,
     inner_values_buffer: Vec<ValueIndex>,
 }
 
-impl Map {
+impl<'tcx> Map<'tcx> {
     /// Returns a map that only tracks places whose type has scalar layout.
     ///
     /// This is currently the only way to create a [`Map`]. The way in which the tracked places are
     /// chosen is an implementation detail and may not be relied upon (other than that their type
     /// are scalars).
-    pub fn new<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, value_limit: Option<usize>) -> Self {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &Body<'tcx>, value_limit: Option<usize>) -> Self {
         let mut map = Self {
-            locals: IndexVec::new(),
+            locals: IndexVec::from_elem(None, &body.local_decls),
             projections: FxHashMap::default(),
             places: IndexVec::new(),
             value_count: 0,
@@ -778,18 +779,15 @@ impl Map {
     }
 
     /// Register all non-excluded places that have scalar layout.
-    fn register<'tcx>(
+    #[tracing::instrument(level = "trace", skip(self, tcx, body))]
+    fn register(
         &mut self,
         tcx: TyCtxt<'tcx>,
         body: &Body<'tcx>,
         exclude: BitSet<Local>,
         value_limit: Option<usize>,
     ) {
-        let mut worklist = VecDeque::with_capacity(value_limit.unwrap_or(body.local_decls.len()));
-        let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
-
         // Start by constructing the places for each bare local.
-        self.locals = IndexVec::from_elem(None, &body.local_decls);
         for (local, decl) in body.local_decls.iter_enumerated() {
             if exclude.contains(local) {
                 continue;
@@ -797,16 +795,60 @@ impl Map {
 
             // Create a place for the local.
             debug_assert!(self.locals[local].is_none());
-            let place = self.places.push(PlaceInfo::new(None));
+            let place = self.places.push(PlaceInfo::new(decl.ty, None));
             self.locals[local] = Some(place);
+        }
+
+        // Collect syntactic places and assignments between them.
+        let mut collector =
+            PlaceCollector { tcx, body, map: self, assignments: Default::default() };
+        collector.visit_body(body);
+        let PlaceCollector { mut assignments, .. } = collector;
+
+        // Just collecting syntactic places is not enough. We may need to propagate this pattern:
+        //      _1 = (const 5u32, const 13i64);
+        //      _2 = _1;
+        //      _3 = (_2.0 as u32);
+        //
+        // `_1.0` does not appear, but we still need to track it. This is achieved by propagating
+        // projections from assignments. We recorded an assignment between `_2` and `_1`, so we
+        // want `_1` and `_2` to have the same sub-places.
+        //
+        // This is what this fixpoint loop does. While we are still creating places, run through
+        // all the assignments, and register places for children.
+        let mut num_places = 0;
+        while num_places < self.places.len() {
+            num_places = self.places.len();
+
+            for assign in 0.. {
+                let Some(&(lhs, rhs)) = assignments.get_index(assign) else { break };
+
+                // Mirror children from `lhs` in `rhs`.
+                let mut child = self.places[lhs].first_child;
+                while let Some(lhs_child) = child {
+                    let PlaceInfo { ty, proj_elem, next_sibling, .. } = self.places[lhs_child];
+                    let rhs_child =
+                        self.register_place(ty, rhs, proj_elem.expect("child is not a projection"));
+                    assignments.insert((lhs_child, rhs_child));
+                    child = next_sibling;
+                }
 
-            // And push the eventual children places to the worklist.
-            self.register_children(tcx, param_env, place, decl.ty, &mut worklist);
+                // Conversely, mirror children from `rhs` in `lhs`.
+                let mut child = self.places[rhs].first_child;
+                while let Some(rhs_child) = child {
+                    let PlaceInfo { ty, proj_elem, next_sibling, .. } = self.places[rhs_child];
+                    let lhs_child =
+                        self.register_place(ty, lhs, proj_elem.expect("child is not a projection"));
+                    assignments.insert((lhs_child, rhs_child));
+                    child = next_sibling;
+                }
+            }
         }
+        drop(assignments);
 
-        // `place.elem1.elem2` with type `ty`.
-        // `elem1` is either `Some(Variant(i))` or `None`.
-        while let Some((mut place, elem1, elem2, ty)) = worklist.pop_front() {
+        // Create values for places whose type have scalar layout.
+        let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
+        for place_info in self.places.iter_mut() {
             // The user requires a bound on the number of created values.
             if let Some(value_limit) = value_limit
                 && self.value_count >= value_limit
@@ -814,19 +856,18 @@ impl Map {
                 break;
             }
 
-            // Create a place for this projection.
-            for elem in [elem1, Some(elem2)].into_iter().flatten() {
-                place = *self.projections.entry((place, 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[place].first_child;
-                    self.places[place].first_child = Some(next);
-                    next
-                });
+            if let Ok(ty) = tcx.try_normalize_erasing_regions(param_env, place_info.ty) {
+                place_info.ty = ty;
             }
 
-            // And push the eventual children places to the worklist.
-            self.register_children(tcx, param_env, place, ty, &mut worklist);
+            // Allocate a value slot if it doesn't have one, and the user requested one.
+            assert!(place_info.value_index.is_none());
+            if let Ok(layout) = tcx.layout_of(param_env.and(place_info.ty))
+                && layout.abi.is_scalar()
+            {
+                place_info.value_index = Some(self.value_count.into());
+                self.value_count += 1;
+            }
         }
 
         // Pre-compute the tree of ValueIndex nested in each PlaceIndex.
@@ -852,68 +893,14 @@ impl Map {
         self.projections.retain(|_, child| !self.inner_values[*child].is_empty());
     }
 
-    /// Potentially register the (local, projection) place and its fields, recursively.
-    ///
-    /// Invariant: The projection must only contain trackable elements.
-    fn register_children<'tcx>(
-        &mut self,
-        tcx: TyCtxt<'tcx>,
-        param_env: ty::ParamEnv<'tcx>,
-        place: PlaceIndex,
-        ty: Ty<'tcx>,
-        worklist: &mut VecDeque<(PlaceIndex, Option<TrackElem>, TrackElem, Ty<'tcx>)>,
-    ) {
-        // Allocate a value slot if it doesn't have one, and the user requested one.
-        assert!(self.places[place].value_index.is_none());
-        if tcx.layout_of(param_env.and(ty)).is_ok_and(|layout| layout.abi.is_scalar()) {
-            self.places[place].value_index = Some(self.value_count.into());
-            self.value_count += 1;
-        }
-
-        // For enums, directly create the `Discriminant`, as that's their main use.
-        if ty.is_enum() {
-            // Prepend new child to the linked list.
-            let discr = self.places.push(PlaceInfo::new(Some(TrackElem::Discriminant)));
-            self.places[discr].next_sibling = self.places[place].first_child;
-            self.places[place].first_child = Some(discr);
-            let old = self.projections.insert((place, TrackElem::Discriminant), discr);
-            assert!(old.is_none());
-
-            // Allocate a value slot since it doesn't have one.
-            assert!(self.places[discr].value_index.is_none());
-            self.places[discr].value_index = Some(self.value_count.into());
-            self.value_count += 1;
-        }
-
-        if let ty::Ref(_, ref_ty, _) | ty::RawPtr(ref_ty, _) = ty.kind()
-            && let ty::Slice(..) = ref_ty.kind()
-            // The user may have written a predicate like `[T]: Sized` in their where clauses,
-            // which makes slices scalars.
-            && self.places[place].value_index.is_none()
-        {
-            // Prepend new child to the linked list.
-            let len = self.places.push(PlaceInfo::new(Some(TrackElem::DerefLen)));
-            self.places[len].next_sibling = self.places[place].first_child;
-            self.places[place].first_child = Some(len);
-
-            let old = self.projections.insert((place, TrackElem::DerefLen), len);
-            assert!(old.is_none());
-
-            // Allocate a value slot since it doesn't have one.
-            assert!(self.places[len].value_index.is_none());
-            self.places[len].value_index = Some(self.value_count.into());
-            self.value_count += 1;
-        }
-
-        // Recurse with all fields of this place.
-        iter_fields(ty, tcx, param_env, |variant, field, ty| {
-            worklist.push_back((
-                place,
-                variant.map(TrackElem::Variant),
-                TrackElem::Field(field),
-                ty,
-            ))
-        });
+    #[tracing::instrument(level = "trace", skip(self), ret)]
+    fn register_place(&mut self, ty: Ty<'tcx>, base: PlaceIndex, elem: TrackElem) -> PlaceIndex {
+        *self.projections.entry((base, elem)).or_insert_with(|| {
+            let next = self.places.push(PlaceInfo::new(ty, Some(elem)));
+            self.places[next].next_sibling = self.places[base].first_child;
+            self.places[base].first_child = Some(next);
+            next
+        })
     }
 
     /// Precompute the list of values inside `root` and store it inside
@@ -934,7 +921,108 @@ impl Map {
         let end = self.inner_values_buffer.len();
         self.inner_values[root] = start..end;
     }
+}
+
+struct PlaceCollector<'a, 'b, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    body: &'b Body<'tcx>,
+    map: &'a mut Map<'tcx>,
+    assignments: FxIndexSet<(PlaceIndex, PlaceIndex)>,
+}
+
+impl<'tcx> PlaceCollector<'_, '_, 'tcx> {
+    #[tracing::instrument(level = "trace", skip(self))]
+    fn register_place(&mut self, place: Place<'tcx>) -> Option<PlaceIndex> {
+        // Create a place for this projection.
+        let mut place_index = self.map.locals[place.local]?;
+        let mut ty = PlaceTy::from_ty(self.body.local_decls[place.local].ty);
+        tracing::trace!(?place_index, ?ty);
+
+        if let ty::Ref(_, ref_ty, _) | ty::RawPtr(ref_ty, _) = ty.ty.kind()
+            && let ty::Slice(..) = ref_ty.kind()
+        {
+            self.map.register_place(self.tcx.types.usize, place_index, TrackElem::DerefLen);
+        } else if ty.ty.is_enum() {
+            let discriminant_ty = ty.ty.discriminant_ty(self.tcx);
+            self.map.register_place(discriminant_ty, place_index, TrackElem::Discriminant);
+        }
+
+        for proj in place.projection {
+            let track_elem = proj.try_into().ok()?;
+            ty = ty.projection_ty(self.tcx, proj);
+            place_index = self.map.register_place(ty.ty, place_index, track_elem);
+            tracing::trace!(?proj, ?place_index, ?ty);
+
+            if let ty::Ref(_, ref_ty, _) | ty::RawPtr(ref_ty, _) = ty.ty.kind()
+                && let ty::Slice(..) = ref_ty.kind()
+            {
+                self.map.register_place(self.tcx.types.usize, place_index, TrackElem::DerefLen);
+            } else if ty.ty.is_enum() {
+                let discriminant_ty = ty.ty.discriminant_ty(self.tcx);
+                self.map.register_place(discriminant_ty, place_index, TrackElem::Discriminant);
+            }
+        }
+
+        Some(place_index)
+    }
+}
 
+impl<'tcx> Visitor<'tcx> for PlaceCollector<'_, '_, 'tcx> {
+    #[tracing::instrument(level = "trace", skip(self))]
+    fn visit_place(&mut self, place: &Place<'tcx>, ctxt: PlaceContext, _: Location) {
+        if !ctxt.is_use() {
+            return;
+        }
+
+        self.register_place(*place);
+    }
+
+    fn visit_assign(&mut self, lhs: &Place<'tcx>, rhs: &Rvalue<'tcx>, location: Location) {
+        self.super_assign(lhs, rhs, location);
+
+        match rhs {
+            Rvalue::Use(Operand::Move(rhs) | Operand::Copy(rhs)) | Rvalue::CopyForDeref(rhs) => {
+                let Some(lhs) = self.register_place(*lhs) else { return };
+                let Some(rhs) = self.register_place(*rhs) else { return };
+                self.assignments.insert((lhs, rhs));
+            }
+            Rvalue::Aggregate(kind, fields) => {
+                let Some(mut lhs) = self.register_place(*lhs) else { return };
+                match **kind {
+                    // Do not propagate unions.
+                    AggregateKind::Adt(_, _, _, _, Some(_)) => return,
+                    AggregateKind::Adt(_, variant, _, _, None) => {
+                        let ty = self.map.places[lhs].ty;
+                        if ty.is_enum() {
+                            lhs = self.map.register_place(ty, lhs, TrackElem::Variant(variant));
+                        }
+                    }
+                    AggregateKind::RawPtr(..)
+                    | AggregateKind::Array(_)
+                    | AggregateKind::Tuple
+                    | AggregateKind::Closure(..)
+                    | AggregateKind::Coroutine(..)
+                    | AggregateKind::CoroutineClosure(..) => {}
+                }
+                for (index, field) in fields.iter_enumerated() {
+                    if let Some(rhs) = field.place()
+                        && let Some(rhs) = self.register_place(rhs)
+                    {
+                        let lhs = self.map.register_place(
+                            self.map.places[rhs].ty,
+                            lhs,
+                            TrackElem::Field(index),
+                        );
+                        self.assignments.insert((lhs, rhs));
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+}
+
+impl<'tcx> Map<'tcx> {
     /// Applies a single projection element, yielding the corresponding child.
     pub fn apply(&self, place: PlaceIndex, elem: TrackElem) -> Option<PlaceIndex> {
         self.projections.get(&(place, elem)).copied()
@@ -974,7 +1062,10 @@ impl Map {
     }
 
     /// Iterate over all direct children.
-    fn children(&self, parent: PlaceIndex) -> impl Iterator<Item = PlaceIndex> + '_ {
+    fn children(
+        &self,
+        parent: PlaceIndex,
+    ) -> impl Iterator<Item = PlaceIndex> + Captures<'_> + Captures<'tcx> {
         Children::new(self, parent)
     }
 
@@ -1081,7 +1172,10 @@ impl Map {
 /// Together, `first_child` and `next_sibling` form an intrusive linked list, which is used to
 /// model a tree structure (a replacement for a member like `children: Vec<PlaceIndex>`).
 #[derive(Debug)]
-struct PlaceInfo {
+struct PlaceInfo<'tcx> {
+    /// Type of the referenced place.
+    ty: Ty<'tcx>,
+
     /// We store a [`ValueIndex`] if and only if the placed is tracked by the analysis.
     value_index: Option<ValueIndex>,
 
@@ -1095,24 +1189,24 @@ struct PlaceInfo {
     next_sibling: Option<PlaceIndex>,
 }
 
-impl PlaceInfo {
-    fn new(proj_elem: Option<TrackElem>) -> Self {
-        Self { next_sibling: None, first_child: None, proj_elem, value_index: None }
+impl<'tcx> PlaceInfo<'tcx> {
+    fn new(ty: Ty<'tcx>, proj_elem: Option<TrackElem>) -> Self {
+        Self { ty, next_sibling: None, first_child: None, proj_elem, value_index: None }
     }
 }
 
-struct Children<'a> {
-    map: &'a Map,
+struct Children<'a, 'tcx> {
+    map: &'a Map<'tcx>,
     next: Option<PlaceIndex>,
 }
 
-impl<'a> Children<'a> {
-    fn new(map: &'a Map, parent: PlaceIndex) -> Self {
+impl<'a, 'tcx> Children<'a, 'tcx> {
+    fn new(map: &'a Map<'tcx>, parent: PlaceIndex) -> Self {
         Self { map, next: map.places[parent].first_child }
     }
 }
 
-impl<'a> Iterator for Children<'a> {
+impl Iterator for Children<'_, '_> {
     type Item = PlaceIndex;
 
     fn next(&mut self) -> Option<Self::Item> {
@@ -1261,7 +1355,7 @@ fn debug_with_context_rec<V: Debug + Eq + HasBottom>(
     place_str: &str,
     new: &StateData<V>,
     old: Option<&StateData<V>>,
-    map: &Map,
+    map: &Map<'_>,
     f: &mut Formatter<'_>,
 ) -> std::fmt::Result {
     if let Some(value) = map.places[place].value_index {
@@ -1305,7 +1399,7 @@ fn debug_with_context_rec<V: Debug + Eq + HasBottom>(
 fn debug_with_context<V: Debug + Eq + HasBottom>(
     new: &StateData<V>,
     old: Option<&StateData<V>>,
-    map: &Map,
+    map: &Map<'_>,
     f: &mut Formatter<'_>,
 ) -> std::fmt::Result {
     for (local, place) in map.locals.iter_enumerated() {
diff --git a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
index 8b965f4d18e..8303ef039d1 100644
--- a/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
+++ b/compiler/rustc_mir_transform/src/dataflow_const_prop.rs
@@ -66,7 +66,7 @@ impl<'tcx> MirPass<'tcx> for DataflowConstProp {
 }
 
 struct ConstAnalysis<'a, 'tcx> {
-    map: Map,
+    map: Map<'tcx>,
     tcx: TyCtxt<'tcx>,
     local_decls: &'a LocalDecls<'tcx>,
     ecx: InterpCx<'tcx, DummyMachine>,
@@ -78,7 +78,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
 
     const NAME: &'static str = "ConstAnalysis";
 
-    fn map(&self) -> &Map {
+    fn map(&self) -> &Map<'tcx> {
         &self.map
     }
 
@@ -330,7 +330,7 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
 }
 
 impl<'a, 'tcx> ConstAnalysis<'a, 'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map) -> Self {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, map: Map<'tcx>) -> Self {
         let param_env = tcx.param_env_reveal_all_normalized(body.source.def_id());
         Self {
             map,
@@ -560,12 +560,13 @@ impl<'tcx, 'locals> Collector<'tcx, 'locals> {
         Self { patch: Patch::new(tcx), local_decls }
     }
 
+    #[instrument(level = "trace", skip(self, ecx, map), ret)]
     fn try_make_constant(
         &self,
         ecx: &mut InterpCx<'tcx, DummyMachine>,
         place: Place<'tcx>,
         state: &State<FlatSet<Scalar>>,
-        map: &Map,
+        map: &Map<'tcx>,
     ) -> Option<Const<'tcx>> {
         let ty = place.ty(self.local_decls, self.patch.tcx).ty;
         let layout = ecx.layout_of(ty).ok()?;
@@ -598,10 +599,11 @@ impl<'tcx, 'locals> Collector<'tcx, 'locals> {
     }
 }
 
+#[instrument(level = "trace", skip(map), ret)]
 fn propagatable_scalar(
     place: PlaceIndex,
     state: &State<FlatSet<Scalar>>,
-    map: &Map,
+    map: &Map<'_>,
 ) -> Option<Scalar> {
     if let FlatSet::Elem(value) = state.get_idx(place, map)
         && value.try_to_scalar_int().is_ok()
@@ -613,14 +615,14 @@ fn propagatable_scalar(
     }
 }
 
-#[instrument(level = "trace", skip(ecx, state, map))]
+#[instrument(level = "trace", skip(ecx, state, map), ret)]
 fn try_write_constant<'tcx>(
     ecx: &mut InterpCx<'tcx, DummyMachine>,
     dest: &PlaceTy<'tcx>,
     place: PlaceIndex,
     ty: Ty<'tcx>,
     state: &State<FlatSet<Scalar>>,
-    map: &Map,
+    map: &Map<'tcx>,
 ) -> InterpResult<'tcx> {
     let layout = ecx.layout_of(ty)?;
 
@@ -719,6 +721,7 @@ impl<'mir, 'tcx>
 {
     type FlowState = State<FlatSet<Scalar>>;
 
+    #[instrument(level = "trace", skip(self, results, statement))]
     fn visit_statement_before_primary_effect(
         &mut self,
         results: &mut Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>,
@@ -740,6 +743,7 @@ impl<'mir, 'tcx>
         }
     }
 
+    #[instrument(level = "trace", skip(self, results, statement))]
     fn visit_statement_after_primary_effect(
         &mut self,
         results: &mut Results<'tcx, ValueAnalysisWrapper<ConstAnalysis<'_, 'tcx>>>,
@@ -834,7 +838,7 @@ struct OperandCollector<'tcx, 'map, 'locals, 'a> {
     state: &'a State<FlatSet<Scalar>>,
     visitor: &'a mut Collector<'tcx, 'locals>,
     ecx: &'map mut InterpCx<'tcx, DummyMachine>,
-    map: &'map Map,
+    map: &'map Map<'tcx>,
 }
 
 impl<'tcx> Visitor<'tcx> for OperandCollector<'tcx, '_, '_, '_> {
diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs
index 97ec0cb39de..2100f4b4a1a 100644
--- a/compiler/rustc_mir_transform/src/jump_threading.rs
+++ b/compiler/rustc_mir_transform/src/jump_threading.rs
@@ -123,7 +123,7 @@ struct TOFinder<'tcx, 'a> {
     param_env: ty::ParamEnv<'tcx>,
     ecx: InterpCx<'tcx, DummyMachine>,
     body: &'a Body<'tcx>,
-    map: &'a Map,
+    map: &'a Map<'tcx>,
     loop_headers: &'a BitSet<BasicBlock>,
     /// We use an arena to avoid cloning the slices when cloning `state`.
     arena: &'a DroplessArena,
diff --git a/tests/coverage/closure.cov-map b/tests/coverage/closure.cov-map
index f36ef7af7ac..6edd35921fe 100644
--- a/tests/coverage/closure.cov-map
+++ b/tests/coverage/closure.cov-map
@@ -31,16 +31,18 @@ Number of file 0 mappings: 24
     = (c0 - c1)
 - Code(Counter(0)) at (prev + 1, 5) to (start + 3, 2)
 
-Function name: closure::main::{closure#0} (unused)
-Raw bytes (24): 0x[01, 01, 00, 04, 00, 28, 05, 02, 14, 00, 02, 15, 02, 0a, 00, 02, 0a, 00, 0b, 00, 01, 09, 01, 06]
+Function name: closure::main::{closure#0}
+Raw bytes (26): 0x[01, 01, 01, 01, 05, 04, 01, 28, 05, 02, 14, 05, 02, 15, 02, 0a, 02, 02, 0a, 00, 0b, 01, 01, 09, 01, 06]
 Number of files: 1
 - file 0 => global file 1
-Number of expressions: 0
+Number of expressions: 1
+- expression 0 operands: lhs = Counter(0), rhs = Counter(1)
 Number of file 0 mappings: 4
-- Code(Zero) at (prev + 40, 5) to (start + 2, 20)
-- Code(Zero) at (prev + 2, 21) to (start + 2, 10)
-- Code(Zero) at (prev + 2, 10) to (start + 0, 11)
-- Code(Zero) at (prev + 1, 9) to (start + 1, 6)
+- Code(Counter(0)) at (prev + 40, 5) to (start + 2, 20)
+- Code(Counter(1)) at (prev + 2, 21) to (start + 2, 10)
+- Code(Expression(0, Sub)) at (prev + 2, 10) to (start + 0, 11)
+    = (c0 - c1)
+- Code(Counter(0)) at (prev + 1, 9) to (start + 1, 6)
 
 Function name: closure::main::{closure#10} (unused)
 Raw bytes (10): 0x[01, 01, 00, 01, 00, 9b, 01, 07, 00, 21]
diff --git a/tests/coverage/try_error_result.cov-map b/tests/coverage/try_error_result.cov-map
index 9c18827d8e6..11a99f5395e 100644
--- a/tests/coverage/try_error_result.cov-map
+++ b/tests/coverage/try_error_result.cov-map
@@ -81,101 +81,130 @@ Number of file 0 mappings: 11
     = (((c4 + Zero) + Zero) + c3)
 
 Function name: try_error_result::test2
-Raw bytes (280): 0x[01, 01, 24, 01, 07, 00, 09, 03, 0d, 41, 00, 1e, 00, 41, 00, 1e, 00, 41, 00, 4a, 00, 4e, 00, 52, 41, 03, 0d, 52, 41, 03, 0d, 4e, 00, 52, 41, 03, 0d, 4a, 00, 4e, 00, 52, 41, 03, 0d, 66, 00, 45, 00, 45, 00, 66, 00, 45, 00, 7a, 00, 4d, 00, 4d, 00, 7a, 00, 4d, 00, 83, 01, 0d, 87, 01, 00, 00, 8b, 01, 8f, 01, 00, 19, 00, 28, 01, 3d, 01, 03, 17, 03, 08, 09, 00, 0e, 52, 02, 09, 04, 1a, 41, 06, 0d, 00, 2f, 00, 00, 2f, 00, 30, 1e, 00, 31, 03, 35, 00, 04, 11, 00, 12, 1a, 02, 11, 04, 12, 00, 05, 11, 00, 14, 1a, 00, 17, 00, 41, 19, 00, 41, 00, 42, 00, 00, 43, 00, 5f, 00, 00, 5f, 00, 60, 00, 01, 0d, 00, 20, 00, 01, 11, 00, 14, 00, 00, 17, 00, 41, 00, 00, 41, 00, 42, 00, 00, 43, 00, 60, 00, 00, 60, 00, 61, 00, 01, 0d, 00, 20, 46, 04, 11, 00, 14, 4e, 00, 17, 00, 42, 00, 00, 42, 00, 43, 4a, 00, 44, 00, 61, 00, 00, 61, 00, 62, 46, 01, 0d, 00, 20, 62, 01, 11, 00, 14, 45, 00, 17, 01, 36, 00, 01, 36, 00, 37, 66, 01, 12, 00, 2f, 00, 00, 2f, 00, 30, 62, 01, 0d, 00, 20, 76, 01, 11, 00, 14, 4d, 00, 17, 01, 36, 00, 02, 11, 00, 12, 7a, 01, 12, 00, 2f, 00, 01, 11, 00, 12, 76, 02, 0d, 00, 20, 0d, 03, 05, 00, 0b, 7f, 01, 01, 00, 02]
+Raw bytes (358): 0x[01, 01, 3b, 01, 07, 05, 09, 03, 0d, 41, 11, 4a, 15, 41, 11, 42, 1d, 46, 19, 4a, 15, 41, 11, 4a, 15, 41, 11, 46, 19, 4a, 15, 41, 11, 42, 1d, 46, 19, 4a, 15, 41, 11, 5e, 25, 49, 21, 49, 21, 5e, 25, 49, 21, 8a, 01, 2d, 8e, 01, 29, 92, 01, 41, 03, 0d, 92, 01, 41, 03, 0d, 8e, 01, 29, 92, 01, 41, 03, 0d, 8a, 01, 2d, 8e, 01, 29, 92, 01, 41, 03, 0d, a6, 01, 35, 45, 31, 45, 31, a6, 01, 35, 45, 31, ba, 01, 3d, 4d, 39, 4d, 39, ba, 01, 3d, 4d, 39, c3, 01, 0d, c7, 01, db, 01, cb, 01, cf, 01, 11, 15, d3, 01, d7, 01, 19, 1d, 21, 25, df, 01, e3, 01, 29, 2d, e7, 01, eb, 01, 31, 35, 39, 3d, 28, 01, 3d, 01, 03, 17, 03, 08, 09, 00, 0e, 92, 01, 02, 09, 04, 1a, 41, 06, 0d, 00, 2f, 11, 00, 2f, 00, 30, 4a, 00, 31, 03, 35, 15, 04, 11, 00, 12, 46, 02, 11, 04, 12, 3e, 05, 11, 00, 14, 46, 00, 17, 00, 41, 19, 00, 41, 00, 42, 42, 00, 43, 00, 5f, 1d, 00, 5f, 00, 60, 3e, 01, 0d, 00, 20, 5a, 01, 11, 00, 14, 49, 00, 17, 00, 41, 21, 00, 41, 00, 42, 5e, 00, 43, 00, 60, 25, 00, 60, 00, 61, 5a, 01, 0d, 00, 20, 86, 01, 04, 11, 00, 14, 8e, 01, 00, 17, 00, 42, 29, 00, 42, 00, 43, 8a, 01, 00, 44, 00, 61, 2d, 00, 61, 00, 62, 86, 01, 01, 0d, 00, 20, a2, 01, 01, 11, 00, 14, 45, 00, 17, 01, 36, 31, 01, 36, 00, 37, a6, 01, 01, 12, 00, 2f, 35, 00, 2f, 00, 30, a2, 01, 01, 0d, 00, 20, b6, 01, 01, 11, 00, 14, 4d, 00, 17, 01, 36, 39, 02, 11, 00, 12, ba, 01, 01, 12, 00, 2f, 3d, 01, 11, 00, 12, b6, 01, 02, 0d, 00, 20, 0d, 03, 05, 00, 0b, bf, 01, 01, 01, 00, 02]
 Number of files: 1
 - file 0 => global file 1
-Number of expressions: 36
+Number of expressions: 59
 - expression 0 operands: lhs = Counter(0), rhs = Expression(1, Add)
-- expression 1 operands: lhs = Zero, rhs = Counter(2)
+- expression 1 operands: lhs = Counter(1), rhs = Counter(2)
 - expression 2 operands: lhs = Expression(0, Add), rhs = Counter(3)
-- expression 3 operands: lhs = Counter(16), rhs = Zero
-- expression 4 operands: lhs = Expression(7, Sub), rhs = Zero
-- expression 5 operands: lhs = Counter(16), rhs = Zero
-- expression 6 operands: lhs = Expression(7, Sub), rhs = Zero
-- expression 7 operands: lhs = Counter(16), rhs = Zero
-- expression 8 operands: lhs = Expression(18, Sub), rhs = Zero
-- expression 9 operands: lhs = Expression(19, Sub), rhs = Zero
-- expression 10 operands: lhs = Expression(20, Sub), rhs = Counter(16)
-- expression 11 operands: lhs = Expression(0, Add), rhs = Counter(3)
-- expression 12 operands: lhs = Expression(20, Sub), rhs = Counter(16)
-- expression 13 operands: lhs = Expression(0, Add), rhs = Counter(3)
-- expression 14 operands: lhs = Expression(19, Sub), rhs = Zero
-- expression 15 operands: lhs = Expression(20, Sub), rhs = Counter(16)
-- expression 16 operands: lhs = Expression(0, Add), rhs = Counter(3)
-- expression 17 operands: lhs = Expression(18, Sub), rhs = Zero
-- expression 18 operands: lhs = Expression(19, Sub), rhs = Zero
-- expression 19 operands: lhs = Expression(20, Sub), rhs = Counter(16)
-- expression 20 operands: lhs = Expression(0, Add), rhs = Counter(3)
-- expression 21 operands: lhs = Expression(25, Sub), rhs = Zero
-- expression 22 operands: lhs = Counter(17), rhs = Zero
-- expression 23 operands: lhs = Counter(17), rhs = Zero
-- expression 24 operands: lhs = Expression(25, Sub), rhs = Zero
-- expression 25 operands: lhs = Counter(17), rhs = Zero
-- expression 26 operands: lhs = Expression(30, Sub), rhs = Zero
-- expression 27 operands: lhs = Counter(19), rhs = Zero
-- expression 28 operands: lhs = Counter(19), rhs = Zero
-- expression 29 operands: lhs = Expression(30, Sub), rhs = Zero
-- expression 30 operands: lhs = Counter(19), rhs = Zero
-- expression 31 operands: lhs = Expression(32, Add), rhs = Counter(3)
-- expression 32 operands: lhs = Expression(33, Add), rhs = Zero
-- expression 33 operands: lhs = Zero, rhs = Expression(34, Add)
-- expression 34 operands: lhs = Expression(35, Add), rhs = Zero
-- expression 35 operands: lhs = Counter(6), rhs = Zero
+- expression 3 operands: lhs = Counter(16), rhs = Counter(4)
+- expression 4 operands: lhs = Expression(18, Sub), rhs = Counter(5)
+- expression 5 operands: lhs = Counter(16), rhs = Counter(4)
+- expression 6 operands: lhs = Expression(16, Sub), rhs = Counter(7)
+- expression 7 operands: lhs = Expression(17, Sub), rhs = Counter(6)
+- expression 8 operands: lhs = Expression(18, Sub), rhs = Counter(5)
+- expression 9 operands: lhs = Counter(16), rhs = Counter(4)
+- expression 10 operands: lhs = Expression(18, Sub), rhs = Counter(5)
+- expression 11 operands: lhs = Counter(16), rhs = Counter(4)
+- expression 12 operands: lhs = Expression(17, Sub), rhs = Counter(6)
+- expression 13 operands: lhs = Expression(18, Sub), rhs = Counter(5)
+- expression 14 operands: lhs = Counter(16), rhs = Counter(4)
+- expression 15 operands: lhs = Expression(16, Sub), rhs = Counter(7)
+- expression 16 operands: lhs = Expression(17, Sub), rhs = Counter(6)
+- expression 17 operands: lhs = Expression(18, Sub), rhs = Counter(5)
+- expression 18 operands: lhs = Counter(16), rhs = Counter(4)
+- expression 19 operands: lhs = Expression(23, Sub), rhs = Counter(9)
+- expression 20 operands: lhs = Counter(18), rhs = Counter(8)
+- expression 21 operands: lhs = Counter(18), rhs = Counter(8)
+- expression 22 operands: lhs = Expression(23, Sub), rhs = Counter(9)
+- expression 23 operands: lhs = Counter(18), rhs = Counter(8)
+- expression 24 operands: lhs = Expression(34, Sub), rhs = Counter(11)
+- expression 25 operands: lhs = Expression(35, Sub), rhs = Counter(10)
+- expression 26 operands: lhs = Expression(36, Sub), rhs = Counter(16)
+- expression 27 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 28 operands: lhs = Expression(36, Sub), rhs = Counter(16)
+- expression 29 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 30 operands: lhs = Expression(35, Sub), rhs = Counter(10)
+- expression 31 operands: lhs = Expression(36, Sub), rhs = Counter(16)
+- expression 32 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 33 operands: lhs = Expression(34, Sub), rhs = Counter(11)
+- expression 34 operands: lhs = Expression(35, Sub), rhs = Counter(10)
+- expression 35 operands: lhs = Expression(36, Sub), rhs = Counter(16)
+- expression 36 operands: lhs = Expression(0, Add), rhs = Counter(3)
+- expression 37 operands: lhs = Expression(41, Sub), rhs = Counter(13)
+- expression 38 operands: lhs = Counter(17), rhs = Counter(12)
+- expression 39 operands: lhs = Counter(17), rhs = Counter(12)
+- expression 40 operands: lhs = Expression(41, Sub), rhs = Counter(13)
+- expression 41 operands: lhs = Counter(17), rhs = Counter(12)
+- expression 42 operands: lhs = Expression(46, Sub), rhs = Counter(15)
+- expression 43 operands: lhs = Counter(19), rhs = Counter(14)
+- expression 44 operands: lhs = Counter(19), rhs = Counter(14)
+- expression 45 operands: lhs = Expression(46, Sub), rhs = Counter(15)
+- expression 46 operands: lhs = Counter(19), rhs = Counter(14)
+- expression 47 operands: lhs = Expression(48, Add), rhs = Counter(3)
+- expression 48 operands: lhs = Expression(49, Add), rhs = Expression(54, Add)
+- expression 49 operands: lhs = Expression(50, Add), rhs = Expression(51, Add)
+- expression 50 operands: lhs = Counter(4), rhs = Counter(5)
+- expression 51 operands: lhs = Expression(52, Add), rhs = Expression(53, Add)
+- expression 52 operands: lhs = Counter(6), rhs = Counter(7)
+- expression 53 operands: lhs = Counter(8), rhs = Counter(9)
+- expression 54 operands: lhs = Expression(55, Add), rhs = Expression(56, Add)
+- expression 55 operands: lhs = Counter(10), rhs = Counter(11)
+- expression 56 operands: lhs = Expression(57, Add), rhs = Expression(58, Add)
+- expression 57 operands: lhs = Counter(12), rhs = Counter(13)
+- expression 58 operands: lhs = Counter(14), rhs = Counter(15)
 Number of file 0 mappings: 40
 - Code(Counter(0)) at (prev + 61, 1) to (start + 3, 23)
 - Code(Expression(0, Add)) at (prev + 8, 9) to (start + 0, 14)
-    = (c0 + (Zero + c2))
-- Code(Expression(20, Sub)) at (prev + 2, 9) to (start + 4, 26)
-    = ((c0 + (Zero + c2)) - c3)
+    = (c0 + (c1 + c2))
+- Code(Expression(36, Sub)) at (prev + 2, 9) to (start + 4, 26)
+    = ((c0 + (c1 + c2)) - c3)
 - Code(Counter(16)) at (prev + 6, 13) to (start + 0, 47)
-- Code(Zero) at (prev + 0, 47) to (start + 0, 48)
-- Code(Expression(7, Sub)) at (prev + 0, 49) to (start + 3, 53)
-    = (c16 - Zero)
-- Code(Zero) at (prev + 4, 17) to (start + 0, 18)
-- Code(Expression(6, Sub)) at (prev + 2, 17) to (start + 4, 18)
-    = ((c16 - Zero) - Zero)
-- Code(Zero) at (prev + 5, 17) to (start + 0, 20)
-- Code(Expression(6, Sub)) at (prev + 0, 23) to (start + 0, 65)
-    = ((c16 - Zero) - Zero)
+- Code(Counter(4)) at (prev + 0, 47) to (start + 0, 48)
+- Code(Expression(18, Sub)) at (prev + 0, 49) to (start + 3, 53)
+    = (c16 - c4)
+- Code(Counter(5)) at (prev + 4, 17) to (start + 0, 18)
+- Code(Expression(17, Sub)) at (prev + 2, 17) to (start + 4, 18)
+    = ((c16 - c4) - c5)
+- Code(Expression(15, Sub)) at (prev + 5, 17) to (start + 0, 20)
+    = ((((c16 - c4) - c5) - c6) - c7)
+- Code(Expression(17, Sub)) at (prev + 0, 23) to (start + 0, 65)
+    = ((c16 - c4) - c5)
 - Code(Counter(6)) at (prev + 0, 65) to (start + 0, 66)
-- Code(Zero) at (prev + 0, 67) to (start + 0, 95)
-- Code(Zero) at (prev + 0, 95) to (start + 0, 96)
-- Code(Zero) at (prev + 1, 13) to (start + 0, 32)
-- Code(Zero) at (prev + 1, 17) to (start + 0, 20)
-- Code(Zero) at (prev + 0, 23) to (start + 0, 65)
-- Code(Zero) at (prev + 0, 65) to (start + 0, 66)
-- Code(Zero) at (prev + 0, 67) to (start + 0, 96)
-- Code(Zero) at (prev + 0, 96) to (start + 0, 97)
-- Code(Zero) at (prev + 1, 13) to (start + 0, 32)
-- Code(Expression(17, Sub)) at (prev + 4, 17) to (start + 0, 20)
-    = (((((c0 + (Zero + c2)) - c3) - c16) - Zero) - Zero)
-- Code(Expression(19, Sub)) at (prev + 0, 23) to (start + 0, 66)
-    = (((c0 + (Zero + c2)) - c3) - c16)
-- Code(Zero) at (prev + 0, 66) to (start + 0, 67)
-- Code(Expression(18, Sub)) at (prev + 0, 68) to (start + 0, 97)
-    = ((((c0 + (Zero + c2)) - c3) - c16) - Zero)
-- Code(Zero) at (prev + 0, 97) to (start + 0, 98)
-- Code(Expression(17, Sub)) at (prev + 1, 13) to (start + 0, 32)
-    = (((((c0 + (Zero + c2)) - c3) - c16) - Zero) - Zero)
-- Code(Expression(24, Sub)) at (prev + 1, 17) to (start + 0, 20)
-    = ((c17 - Zero) - Zero)
+- Code(Expression(16, Sub)) at (prev + 0, 67) to (start + 0, 95)
+    = (((c16 - c4) - c5) - c6)
+- Code(Counter(7)) at (prev + 0, 95) to (start + 0, 96)
+- Code(Expression(15, Sub)) at (prev + 1, 13) to (start + 0, 32)
+    = ((((c16 - c4) - c5) - c6) - c7)
+- Code(Expression(22, Sub)) at (prev + 1, 17) to (start + 0, 20)
+    = ((c18 - c8) - c9)
+- Code(Counter(18)) at (prev + 0, 23) to (start + 0, 65)
+- Code(Counter(8)) at (prev + 0, 65) to (start + 0, 66)
+- Code(Expression(23, Sub)) at (prev + 0, 67) to (start + 0, 96)
+    = (c18 - c8)
+- Code(Counter(9)) at (prev + 0, 96) to (start + 0, 97)
+- Code(Expression(22, Sub)) at (prev + 1, 13) to (start + 0, 32)
+    = ((c18 - c8) - c9)
+- Code(Expression(33, Sub)) at (prev + 4, 17) to (start + 0, 20)
+    = (((((c0 + (c1 + c2)) - c3) - c16) - c10) - c11)
+- Code(Expression(35, Sub)) at (prev + 0, 23) to (start + 0, 66)
+    = (((c0 + (c1 + c2)) - c3) - c16)
+- Code(Counter(10)) at (prev + 0, 66) to (start + 0, 67)
+- Code(Expression(34, Sub)) at (prev + 0, 68) to (start + 0, 97)
+    = ((((c0 + (c1 + c2)) - c3) - c16) - c10)
+- Code(Counter(11)) at (prev + 0, 97) to (start + 0, 98)
+- Code(Expression(33, Sub)) at (prev + 1, 13) to (start + 0, 32)
+    = (((((c0 + (c1 + c2)) - c3) - c16) - c10) - c11)
+- Code(Expression(40, Sub)) at (prev + 1, 17) to (start + 0, 20)
+    = ((c17 - c12) - c13)
 - Code(Counter(17)) at (prev + 0, 23) to (start + 1, 54)
-- Code(Zero) at (prev + 1, 54) to (start + 0, 55)
-- Code(Expression(25, Sub)) at (prev + 1, 18) to (start + 0, 47)
-    = (c17 - Zero)
-- Code(Zero) at (prev + 0, 47) to (start + 0, 48)
-- Code(Expression(24, Sub)) at (prev + 1, 13) to (start + 0, 32)
-    = ((c17 - Zero) - Zero)
-- Code(Expression(29, Sub)) at (prev + 1, 17) to (start + 0, 20)
-    = ((c19 - Zero) - Zero)
+- Code(Counter(12)) at (prev + 1, 54) to (start + 0, 55)
+- Code(Expression(41, Sub)) at (prev + 1, 18) to (start + 0, 47)
+    = (c17 - c12)
+- Code(Counter(13)) at (prev + 0, 47) to (start + 0, 48)
+- Code(Expression(40, Sub)) at (prev + 1, 13) to (start + 0, 32)
+    = ((c17 - c12) - c13)
+- Code(Expression(45, Sub)) at (prev + 1, 17) to (start + 0, 20)
+    = ((c19 - c14) - c15)
 - Code(Counter(19)) at (prev + 0, 23) to (start + 1, 54)
-- Code(Zero) at (prev + 2, 17) to (start + 0, 18)
-- Code(Expression(30, Sub)) at (prev + 1, 18) to (start + 0, 47)
-    = (c19 - Zero)
-- Code(Zero) at (prev + 1, 17) to (start + 0, 18)
-- Code(Expression(29, Sub)) at (prev + 2, 13) to (start + 0, 32)
-    = ((c19 - Zero) - Zero)
+- Code(Counter(14)) at (prev + 2, 17) to (start + 0, 18)
+- Code(Expression(46, Sub)) at (prev + 1, 18) to (start + 0, 47)
+    = (c19 - c14)
+- Code(Counter(15)) at (prev + 1, 17) to (start + 0, 18)
+- Code(Expression(45, Sub)) at (prev + 2, 13) to (start + 0, 32)
+    = ((c19 - c14) - c15)
 - Code(Counter(3)) at (prev + 3, 5) to (start + 0, 11)
-- Code(Expression(31, Add)) at (prev + 1, 1) to (start + 0, 2)
-    = (((Zero + ((c6 + Zero) + Zero)) + Zero) + c3)
+- Code(Expression(47, Add)) at (prev + 1, 1) to (start + 0, 2)
+    = ((((c4 + c5) + ((c6 + c7) + (c8 + c9))) + ((c10 + c11) + ((c12 + c13) + (c14 + c15)))) + c3)
 
diff --git a/tests/mir-opt/dataflow-const-prop/aggregate_copy.foo.DataflowConstProp.diff b/tests/mir-opt/dataflow-const-prop/aggregate_copy.foo.DataflowConstProp.diff
new file mode 100644
index 00000000000..1aeaaff21dc
--- /dev/null
+++ b/tests/mir-opt/dataflow-const-prop/aggregate_copy.foo.DataflowConstProp.diff
@@ -0,0 +1,65 @@
+- // MIR for `foo` before DataflowConstProp
++ // MIR for `foo` after DataflowConstProp
+  
+  fn foo() -> u32 {
+      let mut _0: u32;
+      let _1: (u32, u32);
+      let mut _4: bool;
+      let mut _5: u32;
+      scope 1 {
+          debug a => _1;
+          let _2: (u32, u32);
+          scope 2 {
+              debug b => _2;
+              let _3: u32;
+              scope 3 {
+                  debug c => _3;
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);
+          _1 = const Foo;
+          StorageLive(_2);
+-         _2 = _1;
++         _2 = const (5_u32, 3_u32);
+          StorageLive(_3);
+-         _3 = (_2.1: u32);
++         _3 = const 3_u32;
+          StorageLive(_4);
+          StorageLive(_5);
+-         _5 = _3;
+-         _4 = Ge(move _5, const 2_u32);
+-         switchInt(move _4) -> [0: bb2, otherwise: bb1];
++         _5 = const 3_u32;
++         _4 = const true;
++         switchInt(const true) -> [0: bb2, otherwise: bb1];
+      }
+  
+      bb1: {
+          StorageDead(_5);
+-         _0 = (_2.0: u32);
++         _0 = const 5_u32;
+          goto -> bb3;
+      }
+  
+      bb2: {
+          StorageDead(_5);
+          _0 = const 13_u32;
+          goto -> bb3;
+      }
+  
+      bb3: {
+          StorageDead(_4);
+          StorageDead(_3);
+          StorageDead(_2);
+          StorageDead(_1);
+          return;
+      }
++ }
++ 
++ ALLOC0 (size: 8, align: 4) {
++     05 00 00 00 03 00 00 00                         │ ........
+  }
+  
diff --git a/tests/mir-opt/dataflow-const-prop/aggregate_copy.rs b/tests/mir-opt/dataflow-const-prop/aggregate_copy.rs
new file mode 100644
index 00000000000..aca5f047222
--- /dev/null
+++ b/tests/mir-opt/dataflow-const-prop/aggregate_copy.rs
@@ -0,0 +1,42 @@
+//! Verify that we manage to propagate the value of aggregate `a` even without directly mentioning
+//! the contained scalars.
+//@ test-mir-pass: DataflowConstProp
+
+const Foo: (u32, u32) = (5, 3);
+
+fn foo() -> u32 {
+    // CHECK-LABEL: fn foo(
+    // CHECK: debug a => [[a:_.*]];
+    // CHECK: debug b => [[b:_.*]];
+    // CHECK: debug c => [[c:_.*]];
+
+    // CHECK:bb0: {
+    // CHECK:    [[a]] = const Foo;
+    // CHECK:    [[b]] = const (5_u32, 3_u32);
+    // CHECK:    [[c]] = const 3_u32;
+    // CHECK:    {{_.*}} = const 3_u32;
+    // CHECK:    {{_.*}} = const true;
+    // CHECK:    switchInt(const true) -> [0: bb2, otherwise: bb1];
+
+    // CHECK:bb1: {
+    // CHECK:    _0 = const 5_u32;
+    // CHECK:    goto -> bb3;
+
+    // CHECK:bb2: {
+    // CHECK:    _0 = const 13_u32;
+    // CHECK:    goto -> bb3;
+
+    let a = Foo;
+    // This copies the struct in `a`. We want to ensure that we do track the contents of `a`
+    // because we need to read `b` later.
+    let b = a;
+    let c = b.1;
+    if c >= 2 { b.0 } else { 13 }
+}
+
+fn main() {
+    // CHECK-LABEL: fn main(
+    foo();
+}
+
+// EMIT_MIR aggregate_copy.foo.DataflowConstProp.diff
diff --git a/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.32bit.diff b/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.32bit.diff
index a6da1483c1a..5e89382ea8f 100644
--- a/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.32bit.diff
+++ b/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.32bit.diff
@@ -106,8 +106,7 @@
 -         _7 = (_10.0: f32);
 +         _7 = const 4f32;
           StorageLive(_8);
--         _8 = (_10.1: std::option::Option<S>);
-+         _8 = const Option::<S>::Some(S(1_i32));
+          _8 = (_10.1: std::option::Option<S>);
           StorageLive(_9);
           _9 = (_10.2: &[f32]);
           StorageDead(_10);
@@ -157,8 +156,7 @@
 +         _23 = const 82f32;
           StorageLive(_24);
           _37 = deref_copy (*_26);
--         _24 = ((*_37).1: std::option::Option<S>);
-+         _24 = const Option::<S>::Some(S(35_i32));
+          _24 = ((*_37).1: std::option::Option<S>);
           StorageLive(_25);
           _38 = deref_copy (*_26);
           _25 = ((*_38).2: &[f32]);
@@ -168,12 +166,11 @@
 -         _28 = _23;
 +         _28 = const 82f32;
           StorageLive(_29);
--         _29 = _24;
-+         _29 = const Option::<S>::Some(S(35_i32));
+          _29 = _24;
           StorageLive(_30);
           _30 = _25;
 -         _27 = BigStruct(move _28, move _29, move _30);
-+         _27 = BigStruct(const 82f32, const Option::<S>::Some(S(35_i32)), move _30);
++         _27 = BigStruct(const 82f32, move _29, move _30);
           StorageDead(_30);
           StorageDead(_29);
           StorageDead(_28);
@@ -199,29 +196,21 @@
       }
   }
   
-+ ALLOC2 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC3 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC4 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC5 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC6 (size: 4, align: 4) { .. }
++ ALLOC2 (size: 4, align: 4) { .. }
 + 
   ALLOC1 (static: BIG_STAT, size: 4, align: 4) { .. }
   
 - ALLOC2 (size: 20, align: 4) { .. }
-+ ALLOC7 (size: 20, align: 4) { .. }
++ ALLOC3 (size: 20, align: 4) { .. }
   
 - ALLOC3 (size: 8, align: 4) { .. }
-+ ALLOC8 (size: 8, align: 4) { .. }
++ ALLOC4 (size: 8, align: 4) { .. }
   
   ALLOC0 (static: SMALL_STAT, size: 4, align: 4) { .. }
   
 - ALLOC4 (size: 20, align: 4) { .. }
-+ ALLOC9 (size: 20, align: 4) { .. }
++ ALLOC5 (size: 20, align: 4) { .. }
   
 - ALLOC5 (size: 4, align: 4) { .. }
-+ ALLOC10 (size: 4, align: 4) { .. }
++ ALLOC6 (size: 4, align: 4) { .. }
   
diff --git a/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.64bit.diff b/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.64bit.diff
index 7ca25e44299..a707d7e5e76 100644
--- a/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.64bit.diff
+++ b/tests/mir-opt/dataflow-const-prop/struct.main.DataflowConstProp.64bit.diff
@@ -106,8 +106,7 @@
 -         _7 = (_10.0: f32);
 +         _7 = const 4f32;
           StorageLive(_8);
--         _8 = (_10.1: std::option::Option<S>);
-+         _8 = const Option::<S>::Some(S(1_i32));
+          _8 = (_10.1: std::option::Option<S>);
           StorageLive(_9);
           _9 = (_10.2: &[f32]);
           StorageDead(_10);
@@ -157,8 +156,7 @@
 +         _23 = const 82f32;
           StorageLive(_24);
           _37 = deref_copy (*_26);
--         _24 = ((*_37).1: std::option::Option<S>);
-+         _24 = const Option::<S>::Some(S(35_i32));
+          _24 = ((*_37).1: std::option::Option<S>);
           StorageLive(_25);
           _38 = deref_copy (*_26);
           _25 = ((*_38).2: &[f32]);
@@ -168,12 +166,11 @@
 -         _28 = _23;
 +         _28 = const 82f32;
           StorageLive(_29);
--         _29 = _24;
-+         _29 = const Option::<S>::Some(S(35_i32));
+          _29 = _24;
           StorageLive(_30);
           _30 = _25;
 -         _27 = BigStruct(move _28, move _29, move _30);
-+         _27 = BigStruct(const 82f32, const Option::<S>::Some(S(35_i32)), move _30);
++         _27 = BigStruct(const 82f32, move _29, move _30);
           StorageDead(_30);
           StorageDead(_29);
           StorageDead(_28);
@@ -199,29 +196,21 @@
       }
   }
   
-+ ALLOC2 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC3 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC4 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC5 (size: 8, align: 4) { .. }
-+ 
-+ ALLOC6 (size: 4, align: 4) { .. }
++ ALLOC2 (size: 4, align: 4) { .. }
 + 
   ALLOC1 (static: BIG_STAT, size: 8, align: 8) { .. }
   
 - ALLOC2 (size: 32, align: 8) { .. }
-+ ALLOC7 (size: 32, align: 8) { .. }
++ ALLOC3 (size: 32, align: 8) { .. }
   
 - ALLOC3 (size: 8, align: 4) { .. }
-+ ALLOC8 (size: 8, align: 4) { .. }
++ ALLOC4 (size: 8, align: 4) { .. }
   
   ALLOC0 (static: SMALL_STAT, size: 8, align: 8) { .. }
   
 - ALLOC4 (size: 32, align: 8) { .. }
-+ ALLOC9 (size: 32, align: 8) { .. }
++ ALLOC5 (size: 32, align: 8) { .. }
   
 - ALLOC5 (size: 4, align: 4) { .. }
-+ ALLOC10 (size: 4, align: 4) { .. }
++ ALLOC6 (size: 4, align: 4) { .. }
   
diff --git a/tests/mir-opt/dataflow-const-prop/struct.rs b/tests/mir-opt/dataflow-const-prop/struct.rs
index 4b160c3dab7..89ad1b87029 100644
--- a/tests/mir-opt/dataflow-const-prop/struct.rs
+++ b/tests/mir-opt/dataflow-const-prop/struct.rs
@@ -46,7 +46,7 @@ fn main() {
     const SMALL_VAL: SmallStruct = SmallStruct(4., Some(S(1)), &[]);
 
     // CHECK: [[a1]] = const 4f32;
-    // CHECK: [[b1]] = const Option::<S>::Some(S(1_i32));
+    // CHECK: [[b1]] = ({{_.*}}.1: std::option::Option<S>);
     // CHECK: [[c1]] = ({{_.*}}.2: &[f32]);
     let SmallStruct(a1, b1, c1) = SMALL_VAL;
 
@@ -69,12 +69,12 @@ fn main() {
 
     static BIG_STAT: &BigStruct = &BigStruct(82., Some(S(35)), &[45., 72.]);
     // CHECK: [[a4]] = const 82f32;
-    // CHECK: [[b4]] = const Option::<S>::Some(S(35_i32));
+    // CHECK: [[b4]] = ((*{{_.*}}).1: std::option::Option<S>);
     // CHECK: [[c4]] = ((*{{_.*}}).2: &[f32]);
     let BigStruct(a4, b4, c4) = *BIG_STAT;
 
     // We arbitrarily limit the size of synthetized values to 4 pointers.
     // `BigStruct` can be read, but we will keep a MIR aggregate for this.
-    // CHECK: [[bs]] = BigStruct(const 82f32, const Option::<S>::Some(S(35_i32)), move {{_.*}});
+    // CHECK: [[bs]] = BigStruct(const 82f32, move {{.*}}, move {{_.*}});
     let bs = BigStruct(a4, b4, c4);
 }
diff --git a/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.32bit.diff b/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.32bit.diff
index 44dd4017409..a023243ad9d 100644
--- a/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.32bit.diff
+++ b/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.32bit.diff
@@ -7,10 +7,20 @@
   
       bb0: {
           StorageLive(_1);
-          _1 = Less;
-          _0 = move _1 as i8 (Transmute);
+-         _1 = Less;
+-         _0 = move _1 as i8 (Transmute);
++         _1 = const Less;
++         _0 = const std::cmp::Ordering::Less as i8 (Transmute);
           StorageDead(_1);
           return;
       }
++ }
++ 
++ ALLOC0 (size: 1, align: 1) {
++     ff                                              │ .
++ }
++ 
++ ALLOC1 (size: 1, align: 1) {
++     ff                                              │ .
   }
   
diff --git a/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.64bit.diff b/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.64bit.diff
index 44dd4017409..a023243ad9d 100644
--- a/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.64bit.diff
+++ b/tests/mir-opt/dataflow-const-prop/transmute.less_as_i8.DataflowConstProp.64bit.diff
@@ -7,10 +7,20 @@
   
       bb0: {
           StorageLive(_1);
-          _1 = Less;
-          _0 = move _1 as i8 (Transmute);
+-         _1 = Less;
+-         _0 = move _1 as i8 (Transmute);
++         _1 = const Less;
++         _0 = const std::cmp::Ordering::Less as i8 (Transmute);
           StorageDead(_1);
           return;
       }
++ }
++ 
++ ALLOC0 (size: 1, align: 1) {
++     ff                                              │ .
++ }
++ 
++ ALLOC1 (size: 1, align: 1) {
++     ff                                              │ .
   }
   
diff --git a/tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-abort.diff b/tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-abort.diff
new file mode 100644
index 00000000000..0c8e04a1e74
--- /dev/null
+++ b/tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-abort.diff
@@ -0,0 +1,56 @@
+- // MIR for `aggregate_copy` before JumpThreading
++ // MIR for `aggregate_copy` after JumpThreading
+  
+  fn aggregate_copy() -> u32 {
+      let mut _0: u32;
+      let _1: (u32, u32);
+      let mut _4: bool;
+      let mut _5: u32;
+      scope 1 {
+          debug a => _1;
+          let _2: (u32, u32);
+          scope 2 {
+              debug b => _2;
+              let _3: u32;
+              scope 3 {
+                  debug c => _3;
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);
+          _1 = const aggregate_copy::Foo;
+          StorageLive(_2);
+          _2 = _1;
+          StorageLive(_3);
+          _3 = (_2.1: u32);
+          StorageLive(_4);
+          StorageLive(_5);
+          _5 = _3;
+          _4 = Eq(move _5, const 2_u32);
+-         switchInt(move _4) -> [0: bb2, otherwise: bb1];
++         goto -> bb2;
+      }
+  
+      bb1: {
+          StorageDead(_5);
+          _0 = (_2.0: u32);
+          goto -> bb3;
+      }
+  
+      bb2: {
+          StorageDead(_5);
+          _0 = const 13_u32;
+          goto -> bb3;
+      }
+  
+      bb3: {
+          StorageDead(_4);
+          StorageDead(_3);
+          StorageDead(_2);
+          StorageDead(_1);
+          return;
+      }
+  }
+  
diff --git a/tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-unwind.diff b/tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-unwind.diff
new file mode 100644
index 00000000000..0c8e04a1e74
--- /dev/null
+++ b/tests/mir-opt/jump_threading.aggregate_copy.JumpThreading.panic-unwind.diff
@@ -0,0 +1,56 @@
+- // MIR for `aggregate_copy` before JumpThreading
++ // MIR for `aggregate_copy` after JumpThreading
+  
+  fn aggregate_copy() -> u32 {
+      let mut _0: u32;
+      let _1: (u32, u32);
+      let mut _4: bool;
+      let mut _5: u32;
+      scope 1 {
+          debug a => _1;
+          let _2: (u32, u32);
+          scope 2 {
+              debug b => _2;
+              let _3: u32;
+              scope 3 {
+                  debug c => _3;
+              }
+          }
+      }
+  
+      bb0: {
+          StorageLive(_1);
+          _1 = const aggregate_copy::Foo;
+          StorageLive(_2);
+          _2 = _1;
+          StorageLive(_3);
+          _3 = (_2.1: u32);
+          StorageLive(_4);
+          StorageLive(_5);
+          _5 = _3;
+          _4 = Eq(move _5, const 2_u32);
+-         switchInt(move _4) -> [0: bb2, otherwise: bb1];
++         goto -> bb2;
+      }
+  
+      bb1: {
+          StorageDead(_5);
+          _0 = (_2.0: u32);
+          goto -> bb3;
+      }
+  
+      bb2: {
+          StorageDead(_5);
+          _0 = const 13_u32;
+          goto -> bb3;
+      }
+  
+      bb3: {
+          StorageDead(_4);
+          StorageDead(_3);
+          StorageDead(_2);
+          StorageDead(_1);
+          return;
+      }
+  }
+  
diff --git a/tests/mir-opt/jump_threading.rs b/tests/mir-opt/jump_threading.rs
index b4c13371680..de290c1ef44 100644
--- a/tests/mir-opt/jump_threading.rs
+++ b/tests/mir-opt/jump_threading.rs
@@ -506,6 +506,21 @@ fn assume(a: u8, b: bool) -> u8 {
     }
 }
 
+/// Verify that jump threading succeeds seeing through copies of aggregates.
+fn aggregate_copy() -> u32 {
+    // CHECK-LABEL: fn aggregate_copy(
+    // CHECK-NOT: switchInt(
+
+    const Foo: (u32, u32) = (5, 3);
+
+    let a = Foo;
+    // This copies a tuple, we want to ensure that the threading condition on `b.1` propagates to a
+    // condition on `a.1`.
+    let b = a;
+    let c = b.1;
+    if c == 2 { b.0 } else { 13 }
+}
+
 fn main() {
     // CHECK-LABEL: fn main(
     too_complex(Ok(0));
@@ -534,3 +549,4 @@ fn main() {
 // EMIT_MIR jump_threading.disappearing_bb.JumpThreading.diff
 // EMIT_MIR jump_threading.aggregate.JumpThreading.diff
 // EMIT_MIR jump_threading.assume.JumpThreading.diff
+// EMIT_MIR jump_threading.aggregate_copy.JumpThreading.diff