about summary refs log tree commit diff
path: root/compiler/rustc_mir_dataflow/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_mir_dataflow/src')
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/direction.rs173
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/engine.rs41
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/fmt.rs32
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/graphviz.rs6
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/lattice.rs94
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/mod.rs111
-rw-r--r--compiler/rustc_mir_dataflow/src/framework/tests.rs7
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs65
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/initialized.rs778
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/liveness.rs90
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/mod.rs760
-rw-r--r--compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs83
-rw-r--r--compiler/rustc_mir_dataflow/src/lib.rs4
-rw-r--r--compiler/rustc_mir_dataflow/src/value_analysis.rs59
14 files changed, 1198 insertions, 1105 deletions
diff --git a/compiler/rustc_mir_dataflow/src/framework/direction.rs b/compiler/rustc_mir_dataflow/src/framework/direction.rs
index 804b44a6bf0..8a9e37c5a4f 100644
--- a/compiler/rustc_mir_dataflow/src/framework/direction.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/direction.rs
@@ -1,11 +1,10 @@
-use rustc_middle::mir::{self, BasicBlock, Location, SwitchTargets, UnwindAction};
-use rustc_middle::ty::TyCtxt;
+use rustc_middle::mir::{
+    self, BasicBlock, CallReturnPlaces, Location, SwitchTargets, TerminatorEdges, UnwindAction,
+};
 use std::ops::RangeInclusive;
 
 use super::visitor::{ResultsVisitable, ResultsVisitor};
-use super::{
-    Analysis, CallReturnPlaces, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget,
-};
+use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet, SwitchIntTarget};
 
 pub trait Direction {
     const IS_FORWARD: bool;
@@ -24,15 +23,17 @@ pub trait Direction {
     ) where
         A: Analysis<'tcx>;
 
-    fn apply_effects_in_block<'tcx, A>(
+    fn apply_effects_in_block<'mir, 'tcx, A>(
         analysis: &mut A,
         state: &mut A::Domain,
         block: BasicBlock,
-        block_data: &mir::BasicBlockData<'tcx>,
-    ) where
+        block_data: &'mir mir::BasicBlockData<'tcx>,
+        statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
+    ) -> TerminatorEdges<'mir, 'tcx>
+    where
         A: Analysis<'tcx>;
 
-    fn gen_kill_effects_in_block<'tcx, A>(
+    fn gen_kill_statement_effects_in_block<'tcx, A>(
         analysis: &mut A,
         trans: &mut GenKillSet<A::Idx>,
         block: BasicBlock,
@@ -51,10 +52,10 @@ pub trait Direction {
 
     fn join_state_into_successors_of<'tcx, A>(
         analysis: &mut A,
-        tcx: TyCtxt<'tcx>,
         body: &mir::Body<'tcx>,
         exit_state: &mut A::Domain,
-        block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
+        block: BasicBlock,
+        edges: TerminatorEdges<'_, 'tcx>,
         propagate: impl FnMut(BasicBlock, &A::Domain),
     ) where
         A: Analysis<'tcx>;
@@ -66,27 +67,33 @@ pub struct Backward;
 impl Direction for Backward {
     const IS_FORWARD: bool = false;
 
-    fn apply_effects_in_block<'tcx, A>(
+    fn apply_effects_in_block<'mir, 'tcx, A>(
         analysis: &mut A,
         state: &mut A::Domain,
         block: BasicBlock,
-        block_data: &mir::BasicBlockData<'tcx>,
-    ) where
+        block_data: &'mir mir::BasicBlockData<'tcx>,
+        statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
+    ) -> TerminatorEdges<'mir, 'tcx>
+    where
         A: Analysis<'tcx>,
     {
         let terminator = block_data.terminator();
         let location = Location { block, statement_index: block_data.statements.len() };
         analysis.apply_before_terminator_effect(state, terminator, location);
-        analysis.apply_terminator_effect(state, terminator, location);
-
-        for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
-            let location = Location { block, statement_index };
-            analysis.apply_before_statement_effect(state, statement, location);
-            analysis.apply_statement_effect(state, statement, location);
+        let edges = analysis.apply_terminator_effect(state, terminator, location);
+        if let Some(statement_effect) = statement_effect {
+            statement_effect(block, state)
+        } else {
+            for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
+                let location = Location { block, statement_index };
+                analysis.apply_before_statement_effect(state, statement, location);
+                analysis.apply_statement_effect(state, statement, location);
+            }
         }
+        edges
     }
 
-    fn gen_kill_effects_in_block<'tcx, A>(
+    fn gen_kill_statement_effects_in_block<'tcx, A>(
         analysis: &mut A,
         trans: &mut GenKillSet<A::Idx>,
         block: BasicBlock,
@@ -94,11 +101,6 @@ impl Direction for Backward {
     ) where
         A: GenKillAnalysis<'tcx>,
     {
-        let terminator = block_data.terminator();
-        let location = Location { block, statement_index: block_data.statements.len() };
-        analysis.before_terminator_effect(trans, terminator, location);
-        analysis.terminator_effect(trans, terminator, location);
-
         for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
             let location = Location { block, statement_index };
             analysis.before_statement_effect(trans, statement, location);
@@ -217,10 +219,10 @@ impl Direction for Backward {
 
     fn join_state_into_successors_of<'tcx, A>(
         analysis: &mut A,
-        _tcx: TyCtxt<'tcx>,
         body: &mir::Body<'tcx>,
         exit_state: &mut A::Domain,
-        (bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
+        bb: BasicBlock,
+        _edges: TerminatorEdges<'_, 'tcx>,
         mut propagate: impl FnMut(BasicBlock, &A::Domain),
     ) where
         A: Analysis<'tcx>,
@@ -254,7 +256,11 @@ impl Direction for Backward {
 
                 mir::TerminatorKind::Yield { resume, resume_arg, .. } if resume == bb => {
                     let mut tmp = exit_state.clone();
-                    analysis.apply_yield_resume_effect(&mut tmp, resume, resume_arg);
+                    analysis.apply_call_return_effect(
+                        &mut tmp,
+                        resume,
+                        CallReturnPlaces::Yield(resume_arg),
+                    );
                     propagate(pred, &tmp);
                 }
 
@@ -318,27 +324,33 @@ pub struct Forward;
 impl Direction for Forward {
     const IS_FORWARD: bool = true;
 
-    fn apply_effects_in_block<'tcx, A>(
+    fn apply_effects_in_block<'mir, 'tcx, A>(
         analysis: &mut A,
         state: &mut A::Domain,
         block: BasicBlock,
-        block_data: &mir::BasicBlockData<'tcx>,
-    ) where
+        block_data: &'mir mir::BasicBlockData<'tcx>,
+        statement_effect: Option<&dyn Fn(BasicBlock, &mut A::Domain)>,
+    ) -> TerminatorEdges<'mir, 'tcx>
+    where
         A: Analysis<'tcx>,
     {
-        for (statement_index, statement) in block_data.statements.iter().enumerate() {
-            let location = Location { block, statement_index };
-            analysis.apply_before_statement_effect(state, statement, location);
-            analysis.apply_statement_effect(state, statement, location);
+        if let Some(statement_effect) = statement_effect {
+            statement_effect(block, state)
+        } else {
+            for (statement_index, statement) in block_data.statements.iter().enumerate() {
+                let location = Location { block, statement_index };
+                analysis.apply_before_statement_effect(state, statement, location);
+                analysis.apply_statement_effect(state, statement, location);
+            }
         }
 
         let terminator = block_data.terminator();
         let location = Location { block, statement_index: block_data.statements.len() };
         analysis.apply_before_terminator_effect(state, terminator, location);
-        analysis.apply_terminator_effect(state, terminator, location);
+        analysis.apply_terminator_effect(state, terminator, location)
     }
 
-    fn gen_kill_effects_in_block<'tcx, A>(
+    fn gen_kill_statement_effects_in_block<'tcx, A>(
         analysis: &mut A,
         trans: &mut GenKillSet<A::Idx>,
         block: BasicBlock,
@@ -351,11 +363,6 @@ impl Direction for Forward {
             analysis.before_statement_effect(trans, statement, location);
             analysis.statement_effect(trans, statement, location);
         }
-
-        let terminator = block_data.terminator();
-        let location = Location { block, statement_index: block_data.statements.len() };
-        analysis.before_terminator_effect(trans, terminator, location);
-        analysis.terminator_effect(trans, terminator, location);
     }
 
     fn apply_effects_in_range<'tcx, A>(
@@ -464,86 +471,32 @@ impl Direction for Forward {
 
     fn join_state_into_successors_of<'tcx, A>(
         analysis: &mut A,
-        _tcx: TyCtxt<'tcx>,
         _body: &mir::Body<'tcx>,
         exit_state: &mut A::Domain,
-        (bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
+        bb: BasicBlock,
+        edges: TerminatorEdges<'_, 'tcx>,
         mut propagate: impl FnMut(BasicBlock, &A::Domain),
     ) where
         A: Analysis<'tcx>,
     {
-        use mir::TerminatorKind::*;
-        match bb_data.terminator().kind {
-            Return | Resume | Terminate | GeneratorDrop | Unreachable => {}
-
-            Goto { target } => propagate(target, exit_state),
-
-            Assert { target, unwind, expected: _, msg: _, cond: _ }
-            | Drop { target, unwind, place: _, replace: _ }
-            | FalseUnwind { real_target: target, unwind } => {
-                if let UnwindAction::Cleanup(unwind) = unwind {
-                    propagate(unwind, exit_state);
-                }
-
+        match edges {
+            TerminatorEdges::None => {}
+            TerminatorEdges::Single(target) => propagate(target, exit_state),
+            TerminatorEdges::Double(target, unwind) => {
                 propagate(target, exit_state);
+                propagate(unwind, exit_state);
             }
-
-            FalseEdge { real_target, imaginary_target } => {
-                propagate(real_target, exit_state);
-                propagate(imaginary_target, exit_state);
-            }
-
-            Yield { resume: target, drop, resume_arg, value: _ } => {
-                if let Some(drop) = drop {
-                    propagate(drop, exit_state);
-                }
-
-                analysis.apply_yield_resume_effect(exit_state, target, resume_arg);
-                propagate(target, exit_state);
-            }
-
-            Call { unwind, destination, target, func: _, args: _, call_source: _, fn_span: _ } => {
-                if let UnwindAction::Cleanup(unwind) = unwind {
-                    propagate(unwind, exit_state);
-                }
-
-                if let Some(target) = target {
-                    // N.B.: This must be done *last*, otherwise the unwind path will see the call
-                    // return effect.
-                    analysis.apply_call_return_effect(
-                        exit_state,
-                        bb,
-                        CallReturnPlaces::Call(destination),
-                    );
-                    propagate(target, exit_state);
-                }
-            }
-
-            InlineAsm {
-                template: _,
-                ref operands,
-                options: _,
-                line_spans: _,
-                destination,
-                unwind,
-            } => {
+            TerminatorEdges::AssignOnReturn { return_, unwind, place } => {
+                // This must be done *first*, otherwise the unwind path will see the assignments.
                 if let UnwindAction::Cleanup(unwind) = unwind {
                     propagate(unwind, exit_state);
                 }
-
-                if let Some(target) = destination {
-                    // N.B.: This must be done *last*, otherwise the unwind path will see the call
-                    // return effect.
-                    analysis.apply_call_return_effect(
-                        exit_state,
-                        bb,
-                        CallReturnPlaces::InlineAsm(operands),
-                    );
-                    propagate(target, exit_state);
+                if let Some(return_) = return_ {
+                    analysis.apply_call_return_effect(exit_state, bb, place);
+                    propagate(return_, exit_state);
                 }
             }
-
-            SwitchInt { ref targets, ref discr } => {
+            TerminatorEdges::SwitchInt { targets, discr } => {
                 let mut applier = ForwardSwitchIntEdgeEffectsApplier {
                     exit_state,
                     targets,
diff --git a/compiler/rustc_mir_dataflow/src/framework/engine.rs b/compiler/rustc_mir_dataflow/src/framework/engine.rs
index c755d7588c2..a29962d7717 100644
--- a/compiler/rustc_mir_dataflow/src/framework/engine.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/engine.rs
@@ -144,7 +144,7 @@ where
     // gen/kill problems on cyclic CFGs. This is not ideal, but it doesn't seem to degrade
     // performance in practice. I've tried a few ways to avoid this, but they have downsides. See
     // the message for the commit that added this FIXME for more information.
-    apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
+    apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
 }
 
 impl<'a, 'tcx, A, D, T> Engine<'a, 'tcx, A>
@@ -165,12 +165,17 @@ where
 
         // Otherwise, compute and store the cumulative transfer function for each block.
 
-        let identity = GenKillSet::identity(analysis.bottom_value(body).domain_size());
+        let identity = GenKillSet::identity(analysis.domain_size(body));
         let mut trans_for_block = IndexVec::from_elem(identity, &body.basic_blocks);
 
         for (block, block_data) in body.basic_blocks.iter_enumerated() {
             let trans = &mut trans_for_block[block];
-            A::Direction::gen_kill_effects_in_block(&mut analysis, trans, block, block_data);
+            A::Direction::gen_kill_statement_effects_in_block(
+                &mut analysis,
+                trans,
+                block,
+                block_data,
+            );
         }
 
         let apply_trans = Box::new(move |bb: BasicBlock, state: &mut A::Domain| {
@@ -199,17 +204,18 @@ where
         tcx: TyCtxt<'tcx>,
         body: &'a mir::Body<'tcx>,
         analysis: A,
-        apply_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
+        apply_statement_trans_for_block: Option<Box<dyn Fn(BasicBlock, &mut A::Domain)>>,
     ) -> Self {
-        let bottom_value = analysis.bottom_value(body);
-        let mut entry_sets = IndexVec::from_elem(bottom_value.clone(), &body.basic_blocks);
+        let mut entry_sets =
+            IndexVec::from_fn_n(|_| analysis.bottom_value(body), body.basic_blocks.len());
         analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
 
-        if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != bottom_value {
+        if A::Direction::IS_BACKWARD && entry_sets[mir::START_BLOCK] != analysis.bottom_value(body)
+        {
             bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
         }
 
-        Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_trans_for_block }
+        Engine { analysis, tcx, body, pass_name: None, entry_sets, apply_statement_trans_for_block }
     }
 
     /// Adds an identifier to the graphviz output for this particular run of a dataflow analysis.
@@ -231,7 +237,7 @@ where
             body,
             mut entry_sets,
             tcx,
-            apply_trans_for_block,
+            apply_statement_trans_for_block,
             pass_name,
             ..
         } = self;
@@ -263,19 +269,20 @@ where
             state.clone_from(&entry_sets[bb]);
 
             // Apply the block transfer function, using the cached one if it exists.
-            match &apply_trans_for_block {
-                Some(apply) => apply(bb, &mut state),
-                None => {
-                    A::Direction::apply_effects_in_block(&mut analysis, &mut state, bb, bb_data)
-                }
-            }
+            let edges = A::Direction::apply_effects_in_block(
+                &mut analysis,
+                &mut state,
+                bb,
+                bb_data,
+                apply_statement_trans_for_block.as_deref(),
+            );
 
             A::Direction::join_state_into_successors_of(
                 &mut analysis,
-                tcx,
                 body,
                 &mut state,
-                (bb, bb_data),
+                bb,
+                edges,
                 |target: BasicBlock, state: &A::Domain| {
                     let set_changed = entry_sets[target].join(state);
                     if set_changed {
diff --git a/compiler/rustc_mir_dataflow/src/framework/fmt.rs b/compiler/rustc_mir_dataflow/src/framework/fmt.rs
index 6a256fae3ca..e3a66bd952c 100644
--- a/compiler/rustc_mir_dataflow/src/framework/fmt.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/fmt.rs
@@ -1,6 +1,7 @@
 //! Custom formatting traits used when outputting Graphviz diagrams with the results of a dataflow
 //! analysis.
 
+use super::lattice::MaybeReachable;
 use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
 use rustc_index::Idx;
 use std::fmt;
@@ -124,6 +125,37 @@ where
     }
 }
 
+impl<S, C> DebugWithContext<C> for MaybeReachable<S>
+where
+    S: DebugWithContext<C>,
+{
+    fn fmt_with(&self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match self {
+            MaybeReachable::Unreachable => {
+                write!(f, "unreachable")
+            }
+            MaybeReachable::Reachable(set) => set.fmt_with(ctxt, f),
+        }
+    }
+
+    fn fmt_diff_with(&self, old: &Self, ctxt: &C, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match (self, old) {
+            (MaybeReachable::Unreachable, MaybeReachable::Unreachable) => Ok(()),
+            (MaybeReachable::Unreachable, MaybeReachable::Reachable(set)) => {
+                write!(f, "\u{001f}+")?;
+                set.fmt_with(ctxt, f)
+            }
+            (MaybeReachable::Reachable(set), MaybeReachable::Unreachable) => {
+                write!(f, "\u{001f}-")?;
+                set.fmt_with(ctxt, f)
+            }
+            (MaybeReachable::Reachable(this), MaybeReachable::Reachable(old)) => {
+                this.fmt_diff_with(old, ctxt, f)
+            }
+        }
+    }
+}
+
 fn fmt_diff<T, C>(
     inserted: &HybridBitSet<T>,
     removed: &HybridBitSet<T>,
diff --git a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
index e331533c371..1421d9b45cd 100644
--- a/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/graphviz.rs
@@ -269,7 +269,11 @@ where
                 self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
                     let state_on_generator_drop = this.results.get().clone();
                     this.results.apply_custom_effect(|analysis, state| {
-                        analysis.apply_yield_resume_effect(state, resume, resume_arg);
+                        analysis.apply_call_return_effect(
+                            state,
+                            resume,
+                            CallReturnPlaces::Yield(resume_arg),
+                        );
                     });
 
                     write!(
diff --git a/compiler/rustc_mir_dataflow/src/framework/lattice.rs b/compiler/rustc_mir_dataflow/src/framework/lattice.rs
index 3952f44ad48..3b89598d289 100644
--- a/compiler/rustc_mir_dataflow/src/framework/lattice.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/lattice.rs
@@ -187,10 +187,6 @@ impl<T: Idx> MeetSemiLattice for ChunkedBitSet<T> {
 pub struct Dual<T>(pub T);
 
 impl<T: Idx> BitSetExt<T> for Dual<BitSet<T>> {
-    fn domain_size(&self) -> usize {
-        self.0.domain_size()
-    }
-
     fn contains(&self, elem: T) -> bool {
         self.0.contains(elem)
     }
@@ -276,3 +272,93 @@ impl<T> HasBottom for FlatSet<T> {
 impl<T> HasTop for FlatSet<T> {
     const TOP: Self = Self::Top;
 }
+
+/// Extend a lattice with a bottom value to represent an unreachable execution.
+///
+/// The only useful action on an unreachable state is joining it with a reachable one to make it
+/// reachable. All other actions, gen/kill for instance, are no-ops.
+#[derive(PartialEq, Eq, Debug)]
+pub enum MaybeReachable<T> {
+    Unreachable,
+    Reachable(T),
+}
+
+impl<T> MaybeReachable<T> {
+    pub fn is_reachable(&self) -> bool {
+        matches!(self, MaybeReachable::Reachable(_))
+    }
+}
+
+impl<T> HasBottom for MaybeReachable<T> {
+    const BOTTOM: Self = MaybeReachable::Unreachable;
+}
+
+impl<T: HasTop> HasTop for MaybeReachable<T> {
+    const TOP: Self = MaybeReachable::Reachable(T::TOP);
+}
+
+impl<S> MaybeReachable<S> {
+    /// Return whether the current state contains the given element. If the state is unreachable,
+    /// it does no contain anything.
+    pub fn contains<T>(&self, elem: T) -> bool
+    where
+        S: BitSetExt<T>,
+    {
+        match self {
+            MaybeReachable::Unreachable => false,
+            MaybeReachable::Reachable(set) => set.contains(elem),
+        }
+    }
+}
+
+impl<T, S: BitSetExt<T>> BitSetExt<T> for MaybeReachable<S> {
+    fn contains(&self, elem: T) -> bool {
+        self.contains(elem)
+    }
+
+    fn union(&mut self, other: &HybridBitSet<T>) {
+        match self {
+            MaybeReachable::Unreachable => {}
+            MaybeReachable::Reachable(set) => set.union(other),
+        }
+    }
+
+    fn subtract(&mut self, other: &HybridBitSet<T>) {
+        match self {
+            MaybeReachable::Unreachable => {}
+            MaybeReachable::Reachable(set) => set.subtract(other),
+        }
+    }
+}
+
+impl<V: Clone> Clone for MaybeReachable<V> {
+    fn clone(&self) -> Self {
+        match self {
+            MaybeReachable::Reachable(x) => MaybeReachable::Reachable(x.clone()),
+            MaybeReachable::Unreachable => MaybeReachable::Unreachable,
+        }
+    }
+
+    fn clone_from(&mut self, source: &Self) {
+        match (&mut *self, source) {
+            (MaybeReachable::Reachable(x), MaybeReachable::Reachable(y)) => {
+                x.clone_from(&y);
+            }
+            _ => *self = source.clone(),
+        }
+    }
+}
+
+impl<T: JoinSemiLattice + Clone> JoinSemiLattice for MaybeReachable<T> {
+    fn join(&mut self, other: &Self) -> bool {
+        // Unreachable acts as a bottom.
+        match (&mut *self, &other) {
+            (_, MaybeReachable::Unreachable) => false,
+            (MaybeReachable::Unreachable, _) => {
+                *self = other.clone();
+                true
+            }
+            (MaybeReachable::Reachable(this), MaybeReachable::Reachable(other)) => this.join(other),
+        }
+    }
+}
diff --git a/compiler/rustc_mir_dataflow/src/framework/mod.rs b/compiler/rustc_mir_dataflow/src/framework/mod.rs
index 58df9b9a768..ce30c642fcc 100644
--- a/compiler/rustc_mir_dataflow/src/framework/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/mod.rs
@@ -34,7 +34,7 @@ use std::cmp::Ordering;
 
 use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
 use rustc_index::Idx;
-use rustc_middle::mir::{self, BasicBlock, Location};
+use rustc_middle::mir::{self, BasicBlock, CallReturnPlaces, Location, TerminatorEdges};
 use rustc_middle::ty::TyCtxt;
 
 mod cursor;
@@ -48,23 +48,18 @@ mod visitor;
 pub use self::cursor::{AnalysisResults, ResultsClonedCursor, ResultsCursor, ResultsRefCursor};
 pub use self::direction::{Backward, Direction, Forward};
 pub use self::engine::{Engine, EntrySets, Results, ResultsCloned};
-pub use self::lattice::{JoinSemiLattice, MeetSemiLattice};
+pub use self::lattice::{JoinSemiLattice, MaybeReachable, MeetSemiLattice};
 pub use self::visitor::{visit_results, ResultsVisitable, ResultsVisitor};
 
 /// Analysis domains are all bitsets of various kinds. This trait holds
 /// operations needed by all of them.
 pub trait BitSetExt<T> {
-    fn domain_size(&self) -> usize;
     fn contains(&self, elem: T) -> bool;
     fn union(&mut self, other: &HybridBitSet<T>);
     fn subtract(&mut self, other: &HybridBitSet<T>);
 }
 
 impl<T: Idx> BitSetExt<T> for BitSet<T> {
-    fn domain_size(&self) -> usize {
-        self.domain_size()
-    }
-
     fn contains(&self, elem: T) -> bool {
         self.contains(elem)
     }
@@ -79,10 +74,6 @@ impl<T: Idx> BitSetExt<T> for BitSet<T> {
 }
 
 impl<T: Idx> BitSetExt<T> for ChunkedBitSet<T> {
-    fn domain_size(&self) -> usize {
-        self.domain_size()
-    }
-
     fn contains(&self, elem: T) -> bool {
         self.contains(elem)
     }
@@ -172,12 +163,12 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
     /// in this function. That should go in `apply_call_return_effect`. For example, in the
     /// `InitializedPlaces` analyses, the return place for a function call is not marked as
     /// initialized here.
-    fn apply_terminator_effect(
+    fn apply_terminator_effect<'mir>(
         &mut self,
         state: &mut Self::Domain,
-        terminator: &mir::Terminator<'tcx>,
+        terminator: &'mir mir::Terminator<'tcx>,
         location: Location,
-    );
+    ) -> TerminatorEdges<'mir, 'tcx>;
 
     /// Updates the current dataflow state with an effect that occurs immediately *before* the
     /// given terminator.
@@ -207,20 +198,6 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
         return_places: CallReturnPlaces<'_, 'tcx>,
     );
 
-    /// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
-    ///
-    /// This is similar to `apply_call_return_effect` in that it only takes place after the
-    /// generator is resumed, not when it is dropped.
-    ///
-    /// By default, no effects happen.
-    fn apply_yield_resume_effect(
-        &mut self,
-        _state: &mut Self::Domain,
-        _resume_block: BasicBlock,
-        _resume_place: mir::Place<'tcx>,
-    ) {
-    }
-
     /// Updates the current dataflow state with the effect of taking a particular branch in a
     /// `SwitchInt` terminator.
     ///
@@ -295,6 +272,8 @@ where
 pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
     type Idx: Idx;
 
+    fn domain_size(&self, body: &mir::Body<'tcx>) -> usize;
+
     /// See `Analysis::apply_statement_effect`.
     fn statement_effect(
         &mut self,
@@ -313,12 +292,12 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
     }
 
     /// See `Analysis::apply_terminator_effect`.
-    fn terminator_effect(
+    fn terminator_effect<'mir>(
         &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        terminator: &mir::Terminator<'tcx>,
+        trans: &mut Self::Domain,
+        terminator: &'mir mir::Terminator<'tcx>,
         location: Location,
-    );
+    ) -> TerminatorEdges<'mir, 'tcx>;
 
     /// See `Analysis::apply_before_terminator_effect`.
     fn before_terminator_effect(
@@ -339,15 +318,6 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
         return_places: CallReturnPlaces<'_, 'tcx>,
     );
 
-    /// See `Analysis::apply_yield_resume_effect`.
-    fn yield_resume_effect(
-        &mut self,
-        _trans: &mut impl GenKill<Self::Idx>,
-        _resume_block: BasicBlock,
-        _resume_place: mir::Place<'tcx>,
-    ) {
-    }
-
     /// See `Analysis::apply_switch_int_edge_effects`.
     fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
         &mut self,
@@ -381,13 +351,13 @@ where
         self.before_statement_effect(state, statement, location);
     }
 
-    fn apply_terminator_effect(
+    fn apply_terminator_effect<'mir>(
         &mut self,
         state: &mut A::Domain,
-        terminator: &mir::Terminator<'tcx>,
+        terminator: &'mir mir::Terminator<'tcx>,
         location: Location,
-    ) {
-        self.terminator_effect(state, terminator, location);
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        self.terminator_effect(state, terminator, location)
     }
 
     fn apply_before_terminator_effect(
@@ -410,15 +380,6 @@ where
         self.call_return_effect(state, block, return_places);
     }
 
-    fn apply_yield_resume_effect(
-        &mut self,
-        state: &mut A::Domain,
-        resume_block: BasicBlock,
-        resume_place: mir::Place<'tcx>,
-    ) {
-        self.yield_resume_effect(state, resume_block, resume_place);
-    }
-
     fn apply_switch_int_edge_effects(
         &mut self,
         block: BasicBlock,
@@ -531,6 +492,24 @@ impl<T: Idx> GenKill<T> for ChunkedBitSet<T> {
     }
 }
 
+impl<T, S: GenKill<T>> GenKill<T> for MaybeReachable<S> {
+    fn gen(&mut self, elem: T) {
+        match self {
+            // If the state is not reachable, adding an element does nothing.
+            MaybeReachable::Unreachable => {}
+            MaybeReachable::Reachable(set) => set.gen(elem),
+        }
+    }
+
+    fn kill(&mut self, elem: T) {
+        match self {
+            // If the state is not reachable, killing an element does nothing.
+            MaybeReachable::Unreachable => {}
+            MaybeReachable::Reachable(set) => set.kill(elem),
+        }
+    }
+}
+
 impl<T: Idx> GenKill<T> for lattice::Dual<BitSet<T>> {
     fn gen(&mut self, elem: T) {
         self.0.insert(elem);
@@ -612,29 +591,5 @@ pub trait SwitchIntEdgeEffects<D> {
     fn apply(&mut self, apply_edge_effect: impl FnMut(&mut D, SwitchIntTarget));
 }
 
-/// List of places that are written to after a successful (non-unwind) return
-/// from a `Call` or `InlineAsm`.
-pub enum CallReturnPlaces<'a, 'tcx> {
-    Call(mir::Place<'tcx>),
-    InlineAsm(&'a [mir::InlineAsmOperand<'tcx>]),
-}
-
-impl<'tcx> CallReturnPlaces<'_, 'tcx> {
-    pub fn for_each(&self, mut f: impl FnMut(mir::Place<'tcx>)) {
-        match *self {
-            Self::Call(place) => f(place),
-            Self::InlineAsm(operands) => {
-                for op in operands {
-                    match *op {
-                        mir::InlineAsmOperand::Out { place: Some(place), .. }
-                        | mir::InlineAsmOperand::InOut { out_place: Some(place), .. } => f(place),
-                        _ => {}
-                    }
-                }
-            }
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests;
diff --git a/compiler/rustc_mir_dataflow/src/framework/tests.rs b/compiler/rustc_mir_dataflow/src/framework/tests.rs
index cb0ec144ef0..9cce5b26cd3 100644
--- a/compiler/rustc_mir_dataflow/src/framework/tests.rs
+++ b/compiler/rustc_mir_dataflow/src/framework/tests.rs
@@ -198,14 +198,15 @@ impl<'tcx, D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
         assert!(state.insert(idx));
     }
 
-    fn apply_terminator_effect(
+    fn apply_terminator_effect<'mir>(
         &mut self,
         state: &mut Self::Domain,
-        _terminator: &mir::Terminator<'tcx>,
+        terminator: &'mir mir::Terminator<'tcx>,
         location: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         let idx = self.effect(Effect::Primary.at_index(location.statement_index));
         assert!(state.insert(idx));
+        terminator.edges()
     }
 
     fn apply_before_terminator_effect(
diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
index b88ed32b687..8d7b50796bb 100644
--- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs
@@ -1,9 +1,9 @@
-use super::*;
-
-use crate::{AnalysisDomain, CallReturnPlaces, GenKill, GenKillAnalysis};
+use rustc_index::bit_set::BitSet;
 use rustc_middle::mir::visit::Visitor;
 use rustc_middle::mir::*;
 
+use crate::{AnalysisDomain, GenKill, GenKillAnalysis};
+
 /// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points
 /// to a given local.
 ///
@@ -14,7 +14,7 @@ use rustc_middle::mir::*;
 pub struct MaybeBorrowedLocals;
 
 impl MaybeBorrowedLocals {
-    fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> {
+    pub(super) fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T> {
         TransferFunction { trans }
     }
 }
@@ -23,12 +23,12 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals {
     type Domain = BitSet<Local>;
     const NAME: &'static str = "maybe_borrowed_locals";
 
-    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
         // bottom = unborrowed
         BitSet::new_empty(body.local_decls().len())
     }
 
-    fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut Self::Domain) {
+    fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
         // No locals are aliased on function entry
     }
 }
@@ -36,35 +36,40 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeBorrowedLocals {
 impl<'tcx> GenKillAnalysis<'tcx> for MaybeBorrowedLocals {
     type Idx = Local;
 
+    fn domain_size(&self, body: &Body<'tcx>) -> usize {
+        body.local_decls.len()
+    }
+
     fn statement_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
-        statement: &mir::Statement<'tcx>,
+        statement: &Statement<'tcx>,
         location: Location,
     ) {
         self.transfer_function(trans).visit_statement(statement, location);
     }
 
-    fn terminator_effect(
+    fn terminator_effect<'mir>(
         &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        terminator: &mir::Terminator<'tcx>,
+        trans: &mut Self::Domain,
+        terminator: &'mir Terminator<'tcx>,
         location: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         self.transfer_function(trans).visit_terminator(terminator, location);
+        terminator.edges()
     }
 
     fn call_return_effect(
         &mut self,
         _trans: &mut impl GenKill<Self::Idx>,
-        _block: mir::BasicBlock,
+        _block: BasicBlock,
         _return_places: CallReturnPlaces<'_, 'tcx>,
     ) {
     }
 }
 
 /// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`.
-struct TransferFunction<'a, T> {
+pub(super) struct TransferFunction<'a, T> {
     trans: &'a mut T,
 }
 
@@ -82,37 +87,37 @@ where
         }
     }
 
-    fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
+    fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
         self.super_rvalue(rvalue, location);
 
         match rvalue {
-            mir::Rvalue::AddressOf(_, borrowed_place) | mir::Rvalue::Ref(_, _, borrowed_place) => {
+            Rvalue::AddressOf(_, borrowed_place) | Rvalue::Ref(_, _, borrowed_place) => {
                 if !borrowed_place.is_indirect() {
                     self.trans.gen(borrowed_place.local);
                 }
             }
 
-            mir::Rvalue::Cast(..)
-            | mir::Rvalue::ShallowInitBox(..)
-            | mir::Rvalue::Use(..)
-            | mir::Rvalue::ThreadLocalRef(..)
-            | mir::Rvalue::Repeat(..)
-            | mir::Rvalue::Len(..)
-            | mir::Rvalue::BinaryOp(..)
-            | mir::Rvalue::CheckedBinaryOp(..)
-            | mir::Rvalue::NullaryOp(..)
-            | mir::Rvalue::UnaryOp(..)
-            | mir::Rvalue::Discriminant(..)
-            | mir::Rvalue::Aggregate(..)
-            | mir::Rvalue::CopyForDeref(..) => {}
+            Rvalue::Cast(..)
+            | Rvalue::ShallowInitBox(..)
+            | Rvalue::Use(..)
+            | Rvalue::ThreadLocalRef(..)
+            | Rvalue::Repeat(..)
+            | Rvalue::Len(..)
+            | Rvalue::BinaryOp(..)
+            | Rvalue::CheckedBinaryOp(..)
+            | Rvalue::NullaryOp(..)
+            | Rvalue::UnaryOp(..)
+            | Rvalue::Discriminant(..)
+            | Rvalue::Aggregate(..)
+            | Rvalue::CopyForDeref(..) => {}
         }
     }
 
-    fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
+    fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
         self.super_terminator(terminator, location);
 
         match terminator.kind {
-            mir::TerminatorKind::Drop { place: dropped_place, .. } => {
+            TerminatorKind::Drop { place: dropped_place, .. } => {
                 // Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut
                 // self` as a parameter. In the general case, a drop impl could launder that
                 // reference into the surrounding environment through a raw pointer, thus creating
diff --git a/compiler/rustc_mir_dataflow/src/impls/initialized.rs b/compiler/rustc_mir_dataflow/src/impls/initialized.rs
new file mode 100644
index 00000000000..e6d383d626a
--- /dev/null
+++ b/compiler/rustc_mir_dataflow/src/impls/initialized.rs
@@ -0,0 +1,778 @@
+use rustc_index::bit_set::{BitSet, ChunkedBitSet};
+use rustc_index::Idx;
+use rustc_middle::mir::{self, Body, CallReturnPlaces, Location, TerminatorEdges};
+use rustc_middle::ty::{self, TyCtxt};
+
+use crate::drop_flag_effects_for_function_entry;
+use crate::drop_flag_effects_for_location;
+use crate::elaborate_drops::DropFlagState;
+use crate::framework::SwitchIntEdgeEffects;
+use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
+use crate::on_lookup_result_bits;
+use crate::MoveDataParamEnv;
+use crate::{drop_flag_effects, on_all_children_bits, on_all_drop_children_bits};
+use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis, MaybeReachable};
+
+/// `MaybeInitializedPlaces` tracks all places that might be
+/// initialized upon reaching a particular point in the control flow
+/// for a function.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) {                        // maybe-init:
+///                                             // {}
+///     let a = S; let mut b = S; let c; let d; // {a, b}
+///
+///     if pred {
+///         drop(a);                            // {   b}
+///         b = S;                              // {   b}
+///
+///     } else {
+///         drop(b);                            // {a}
+///         d = S;                              // {a,       d}
+///
+///     }                                       // {a, b,    d}
+///
+///     c = S;                                  // {a, b, c, d}
+/// }
+/// ```
+///
+/// To determine whether a place *must* be initialized at a
+/// particular control-flow point, one can take the set-difference
+/// between this data and the data from `MaybeUninitializedPlaces` at the
+/// corresponding control-flow point.
+///
+/// Similarly, at a given `drop` statement, the set-intersection
+/// between this data and `MaybeUninitializedPlaces` yields the set of
+/// places that would require a dynamic drop-flag at that statement.
+pub struct MaybeInitializedPlaces<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    body: &'a Body<'tcx>,
+    mdpe: &'a MoveDataParamEnv<'tcx>,
+    skip_unreachable_unwind: bool,
+}
+
+impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+        MaybeInitializedPlaces { tcx, body, mdpe, skip_unreachable_unwind: false }
+    }
+
+    pub fn skipping_unreachable_unwind(mut self) -> Self {
+        self.skip_unreachable_unwind = true;
+        self
+    }
+
+    pub fn is_unwind_dead(
+        &self,
+        place: mir::Place<'tcx>,
+        state: &MaybeReachable<ChunkedBitSet<MovePathIndex>>,
+    ) -> bool {
+        if let LookupResult::Exact(path) = self.move_data().rev_lookup.find(place.as_ref()) {
+            let mut maybe_live = false;
+            on_all_drop_children_bits(self.tcx, self.body, self.mdpe, path, |child| {
+                maybe_live |= state.contains(child);
+            });
+            !maybe_live
+        } else {
+            false
+        }
+    }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
+    fn move_data(&self) -> &MoveData<'tcx> {
+        &self.mdpe.move_data
+    }
+}
+
+/// `MaybeUninitializedPlaces` tracks all places that might be
+/// uninitialized upon reaching a particular point in the control flow
+/// for a function.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) {                        // maybe-uninit:
+///                                             // {a, b, c, d}
+///     let a = S; let mut b = S; let c; let d; // {      c, d}
+///
+///     if pred {
+///         drop(a);                            // {a,    c, d}
+///         b = S;                              // {a,    c, d}
+///
+///     } else {
+///         drop(b);                            // {   b, c, d}
+///         d = S;                              // {   b, c   }
+///
+///     }                                       // {a, b, c, d}
+///
+///     c = S;                                  // {a, b,    d}
+/// }
+/// ```
+///
+/// To determine whether a place *must* be uninitialized at a
+/// particular control-flow point, one can take the set-difference
+/// between this data and the data from `MaybeInitializedPlaces` at the
+/// corresponding control-flow point.
+///
+/// Similarly, at a given `drop` statement, the set-intersection
+/// between this data and `MaybeInitializedPlaces` yields the set of
+/// places that would require a dynamic drop-flag at that statement.
+pub struct MaybeUninitializedPlaces<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    body: &'a Body<'tcx>,
+    mdpe: &'a MoveDataParamEnv<'tcx>,
+
+    mark_inactive_variants_as_uninit: bool,
+    skip_unreachable_unwind: BitSet<mir::BasicBlock>,
+}
+
+impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+        MaybeUninitializedPlaces {
+            tcx,
+            body,
+            mdpe,
+            mark_inactive_variants_as_uninit: false,
+            skip_unreachable_unwind: BitSet::new_empty(body.basic_blocks.len()),
+        }
+    }
+
+    /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
+    /// enum discriminant.
+    ///
+    /// This is correct in a vacuum but is not the default because it causes problems in the borrow
+    /// checker, where this information gets propagated along `FakeEdge`s.
+    pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
+        self.mark_inactive_variants_as_uninit = true;
+        self
+    }
+
+    pub fn skipping_unreachable_unwind(
+        mut self,
+        unreachable_unwind: BitSet<mir::BasicBlock>,
+    ) -> Self {
+        self.skip_unreachable_unwind = unreachable_unwind;
+        self
+    }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
+    fn move_data(&self) -> &MoveData<'tcx> {
+        &self.mdpe.move_data
+    }
+}
+
+/// `DefinitelyInitializedPlaces` tracks all places that are definitely
+/// initialized upon reaching a particular point in the control flow
+/// for a function.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) {                        // definite-init:
+///                                             // {          }
+///     let a = S; let mut b = S; let c; let d; // {a, b      }
+///
+///     if pred {
+///         drop(a);                            // {   b,     }
+///         b = S;                              // {   b,     }
+///
+///     } else {
+///         drop(b);                            // {a,        }
+///         d = S;                              // {a,       d}
+///
+///     }                                       // {          }
+///
+///     c = S;                                  // {       c  }
+/// }
+/// ```
+///
+/// To determine whether a place *may* be uninitialized at a
+/// particular control-flow point, one can take the set-complement
+/// of this data.
+///
+/// Similarly, at a given `drop` statement, the set-difference between
+/// this data and `MaybeInitializedPlaces` yields the set of places
+/// that would require a dynamic drop-flag at that statement.
+pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
+    tcx: TyCtxt<'tcx>,
+    body: &'a Body<'tcx>,
+    mdpe: &'a MoveDataParamEnv<'tcx>,
+}
+
+impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+        DefinitelyInitializedPlaces { tcx, body, mdpe }
+    }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
+    fn move_data(&self) -> &MoveData<'tcx> {
+        &self.mdpe.move_data
+    }
+}
+
+/// `EverInitializedPlaces` tracks all places that might have ever been
+/// initialized upon reaching a particular point in the control flow
+/// for a function, without an intervening `StorageDead`.
+///
+/// This dataflow is used to determine if an immutable local variable may
+/// be assigned to.
+///
+/// For example, in code like the following, we have corresponding
+/// dataflow information shown in the right-hand comments.
+///
+/// ```rust
+/// struct S;
+/// fn foo(pred: bool) {                        // ever-init:
+///                                             // {          }
+///     let a = S; let mut b = S; let c; let d; // {a, b      }
+///
+///     if pred {
+///         drop(a);                            // {a, b,     }
+///         b = S;                              // {a, b,     }
+///
+///     } else {
+///         drop(b);                            // {a, b,      }
+///         d = S;                              // {a, b,    d }
+///
+///     }                                       // {a, b,    d }
+///
+///     c = S;                                  // {a, b, c, d }
+/// }
+/// ```
+pub struct EverInitializedPlaces<'a, 'tcx> {
+    #[allow(dead_code)]
+    tcx: TyCtxt<'tcx>,
+    body: &'a Body<'tcx>,
+    mdpe: &'a MoveDataParamEnv<'tcx>,
+}
+
+impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
+    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
+        EverInitializedPlaces { tcx, body, mdpe }
+    }
+}
+
+impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
+    fn move_data(&self) -> &MoveData<'tcx> {
+        &self.mdpe.move_data
+    }
+}
+
+impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
+    fn update_bits(
+        trans: &mut impl GenKill<MovePathIndex>,
+        path: MovePathIndex,
+        state: DropFlagState,
+    ) {
+        match state {
+            DropFlagState::Absent => trans.kill(path),
+            DropFlagState::Present => trans.gen(path),
+        }
+    }
+}
+
+impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
+    fn update_bits(
+        trans: &mut impl GenKill<MovePathIndex>,
+        path: MovePathIndex,
+        state: DropFlagState,
+    ) {
+        match state {
+            DropFlagState::Absent => trans.gen(path),
+            DropFlagState::Present => trans.kill(path),
+        }
+    }
+}
+
+impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
+    fn update_bits(
+        trans: &mut impl GenKill<MovePathIndex>,
+        path: MovePathIndex,
+        state: DropFlagState,
+    ) {
+        match state {
+            DropFlagState::Absent => trans.kill(path),
+            DropFlagState::Present => trans.gen(path),
+        }
+    }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
+    type Domain = MaybeReachable<ChunkedBitSet<MovePathIndex>>;
+    const NAME: &'static str = "maybe_init";
+
+    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+        // bottom = uninitialized
+        MaybeReachable::Unreachable
+    }
+
+    fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
+        *state =
+            MaybeReachable::Reachable(ChunkedBitSet::new_empty(self.move_data().move_paths.len()));
+        drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
+            assert!(s == DropFlagState::Present);
+            state.gen(path);
+        });
+    }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
+    type Idx = MovePathIndex;
+
+    fn domain_size(&self, _: &Body<'tcx>) -> usize {
+        self.move_data().move_paths.len()
+    }
+
+    fn statement_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        statement: &mir::Statement<'tcx>,
+        location: Location,
+    ) {
+        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+            Self::update_bits(trans, path, s)
+        });
+
+        // Mark all places as "maybe init" if they are mutably borrowed. See #90752.
+        if self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration
+            && let Some((_, rvalue)) = statement.kind.as_assign()
+            && let mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place)
+                // FIXME: Does `&raw const foo` allow mutation? See #90413.
+                | mir::Rvalue::AddressOf(_, place) = rvalue
+            && let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref())
+        {
+            on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
+                trans.gen(child);
+            })
+        }
+    }
+
+    fn terminator_effect<'mir>(
+        &mut self,
+        state: &mut Self::Domain,
+        terminator: &'mir mir::Terminator<'tcx>,
+        location: Location,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        let mut edges = terminator.edges();
+        if self.skip_unreachable_unwind
+            && let mir::TerminatorKind::Drop { target, unwind, place, replace: _ } = terminator.kind
+            && matches!(unwind, mir::UnwindAction::Cleanup(_))
+            && self.is_unwind_dead(place, state)
+        {
+            edges = TerminatorEdges::Single(target);
+        }
+        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+            Self::update_bits(state, path, s)
+        });
+        edges
+    }
+
+    fn call_return_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        _block: mir::BasicBlock,
+        return_places: CallReturnPlaces<'_, 'tcx>,
+    ) {
+        return_places.for_each(|place| {
+            // when a call returns successfully, that means we need to set
+            // the bits for that dest_place to 1 (initialized).
+            on_lookup_result_bits(
+                self.tcx,
+                self.body,
+                self.move_data(),
+                self.move_data().rev_lookup.find(place.as_ref()),
+                |mpi| {
+                    trans.gen(mpi);
+                },
+            );
+        });
+    }
+
+    fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
+        &mut self,
+        block: mir::BasicBlock,
+        discr: &mir::Operand<'tcx>,
+        edge_effects: &mut impl SwitchIntEdgeEffects<G>,
+    ) {
+        if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
+            return;
+        }
+
+        let enum_ = discr.place().and_then(|discr| {
+            switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
+        });
+
+        let Some((enum_place, enum_def)) = enum_ else {
+            return;
+        };
+
+        let mut discriminants = enum_def.discriminants(self.tcx);
+        edge_effects.apply(|trans, edge| {
+            let Some(value) = edge.value else {
+                return;
+            };
+
+            // MIR building adds discriminants to the `values` array in the same order as they
+            // are yielded by `AdtDef::discriminants`. We rely on this to match each
+            // discriminant in `values` to its corresponding variant in linear time.
+            let (variant, _) = discriminants
+                .find(|&(_, discr)| discr.val == value)
+                .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
+
+            // Kill all move paths that correspond to variants we know to be inactive along this
+            // particular outgoing edge of a `SwitchInt`.
+            drop_flag_effects::on_all_inactive_variants(
+                self.tcx,
+                self.body,
+                self.move_data(),
+                enum_place,
+                variant,
+                |mpi| trans.kill(mpi),
+            );
+        });
+    }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
+    type Domain = ChunkedBitSet<MovePathIndex>;
+
+    const NAME: &'static str = "maybe_uninit";
+
+    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+        // bottom = initialized (start_block_effect counters this at outset)
+        ChunkedBitSet::new_empty(self.move_data().move_paths.len())
+    }
+
+    // sets on_entry bits for Arg places
+    fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
+        // set all bits to 1 (uninit) before gathering counter-evidence
+        state.insert_all();
+
+        drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
+            assert!(s == DropFlagState::Present);
+            state.remove(path);
+        });
+    }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
+    type Idx = MovePathIndex;
+
+    fn domain_size(&self, _: &Body<'tcx>) -> usize {
+        self.move_data().move_paths.len()
+    }
+
+    fn statement_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        _statement: &mir::Statement<'tcx>,
+        location: Location,
+    ) {
+        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+            Self::update_bits(trans, path, s)
+        });
+
+        // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
+        // mutable borrow occurs. Places cannot become uninitialized through a mutable reference.
+    }
+
+    fn terminator_effect<'mir>(
+        &mut self,
+        trans: &mut Self::Domain,
+        terminator: &'mir mir::Terminator<'tcx>,
+        location: Location,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+            Self::update_bits(trans, path, s)
+        });
+        if self.skip_unreachable_unwind.contains(location.block) {
+            let mir::TerminatorKind::Drop { target, unwind, .. } = terminator.kind else { bug!() };
+            assert!(matches!(unwind, mir::UnwindAction::Cleanup(_)));
+            TerminatorEdges::Single(target)
+        } else {
+            terminator.edges()
+        }
+    }
+
+    fn call_return_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        _block: mir::BasicBlock,
+        return_places: CallReturnPlaces<'_, 'tcx>,
+    ) {
+        return_places.for_each(|place| {
+            // when a call returns successfully, that means we need to set
+            // the bits for that dest_place to 0 (initialized).
+            on_lookup_result_bits(
+                self.tcx,
+                self.body,
+                self.move_data(),
+                self.move_data().rev_lookup.find(place.as_ref()),
+                |mpi| {
+                    trans.kill(mpi);
+                },
+            );
+        });
+    }
+
+    fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
+        &mut self,
+        block: mir::BasicBlock,
+        discr: &mir::Operand<'tcx>,
+        edge_effects: &mut impl SwitchIntEdgeEffects<G>,
+    ) {
+        if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
+            return;
+        }
+
+        if !self.mark_inactive_variants_as_uninit {
+            return;
+        }
+
+        let enum_ = discr.place().and_then(|discr| {
+            switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
+        });
+
+        let Some((enum_place, enum_def)) = enum_ else {
+            return;
+        };
+
+        let mut discriminants = enum_def.discriminants(self.tcx);
+        edge_effects.apply(|trans, edge| {
+            let Some(value) = edge.value else {
+                return;
+            };
+
+            // MIR building adds discriminants to the `values` array in the same order as they
+            // are yielded by `AdtDef::discriminants`. We rely on this to match each
+            // discriminant in `values` to its corresponding variant in linear time.
+            let (variant, _) = discriminants
+                .find(|&(_, discr)| discr.val == value)
+                .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
+
+            // Mark all move paths that correspond to variants other than this one as maybe
+            // uninitialized (in reality, they are *definitely* uninitialized).
+            drop_flag_effects::on_all_inactive_variants(
+                self.tcx,
+                self.body,
+                self.move_data(),
+                enum_place,
+                variant,
+                |mpi| trans.gen(mpi),
+            );
+        });
+    }
+}
+
+impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
+    /// Use set intersection as the join operator.
+    type Domain = lattice::Dual<BitSet<MovePathIndex>>;
+
+    const NAME: &'static str = "definite_init";
+
+    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+        // bottom = initialized (start_block_effect counters this at outset)
+        lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
+    }
+
+    // sets on_entry bits for Arg places
+    fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
+        state.0.clear();
+
+        drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
+            assert!(s == DropFlagState::Present);
+            state.0.insert(path);
+        });
+    }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
+    type Idx = MovePathIndex;
+
+    fn domain_size(&self, _: &Body<'tcx>) -> usize {
+        self.move_data().move_paths.len()
+    }
+
+    fn statement_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        _statement: &mir::Statement<'tcx>,
+        location: Location,
+    ) {
+        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+            Self::update_bits(trans, path, s)
+        })
+    }
+
+    fn terminator_effect<'mir>(
+        &mut self,
+        trans: &mut Self::Domain,
+        terminator: &'mir mir::Terminator<'tcx>,
+        location: Location,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
+            Self::update_bits(trans, path, s)
+        });
+        terminator.edges()
+    }
+
+    fn call_return_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        _block: mir::BasicBlock,
+        return_places: CallReturnPlaces<'_, 'tcx>,
+    ) {
+        return_places.for_each(|place| {
+            // when a call returns successfully, that means we need to set
+            // the bits for that dest_place to 1 (initialized).
+            on_lookup_result_bits(
+                self.tcx,
+                self.body,
+                self.move_data(),
+                self.move_data().rev_lookup.find(place.as_ref()),
+                |mpi| {
+                    trans.gen(mpi);
+                },
+            );
+        });
+    }
+}
+
+impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
+    type Domain = ChunkedBitSet<InitIndex>;
+
+    const NAME: &'static str = "ever_init";
+
+    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
+        // bottom = no initialized variables by default
+        ChunkedBitSet::new_empty(self.move_data().inits.len())
+    }
+
+    fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
+        for arg_init in 0..body.arg_count {
+            state.insert(InitIndex::new(arg_init));
+        }
+    }
+}
+
+impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
+    type Idx = InitIndex;
+
+    fn domain_size(&self, _: &Body<'tcx>) -> usize {
+        self.move_data().inits.len()
+    }
+
+    #[instrument(skip(self, trans), level = "debug")]
+    fn statement_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        stmt: &mir::Statement<'tcx>,
+        location: Location,
+    ) {
+        let move_data = self.move_data();
+        let init_path_map = &move_data.init_path_map;
+        let init_loc_map = &move_data.init_loc_map;
+        let rev_lookup = &move_data.rev_lookup;
+
+        debug!("initializes move_indexes {:?}", &init_loc_map[location]);
+        trans.gen_all(init_loc_map[location].iter().copied());
+
+        if let mir::StatementKind::StorageDead(local) = stmt.kind {
+            // End inits for StorageDead, so that an immutable variable can
+            // be reinitialized on the next iteration of the loop.
+            let move_path_index = rev_lookup.find_local(local);
+            debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
+            trans.kill_all(init_path_map[move_path_index].iter().copied());
+        }
+    }
+
+    #[instrument(skip(self, trans, terminator), level = "debug")]
+    fn terminator_effect<'mir>(
+        &mut self,
+        trans: &mut Self::Domain,
+        terminator: &'mir mir::Terminator<'tcx>,
+        location: Location,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        let (body, move_data) = (self.body, self.move_data());
+        let term = body[location.block].terminator();
+        let init_loc_map = &move_data.init_loc_map;
+        debug!(?term);
+        debug!("initializes move_indexes {:?}", init_loc_map[location]);
+        trans.gen_all(
+            init_loc_map[location]
+                .iter()
+                .filter(|init_index| {
+                    move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
+                })
+                .copied(),
+        );
+        terminator.edges()
+    }
+
+    fn call_return_effect(
+        &mut self,
+        trans: &mut impl GenKill<Self::Idx>,
+        block: mir::BasicBlock,
+        _return_places: CallReturnPlaces<'_, 'tcx>,
+    ) {
+        let move_data = self.move_data();
+        let init_loc_map = &move_data.init_loc_map;
+
+        let call_loc = self.body.terminator_loc(block);
+        for init_index in &init_loc_map[call_loc] {
+            trans.gen(*init_index);
+        }
+    }
+}
+
+/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
+/// an enum discriminant.
+///
+/// We expect such blocks to have a call to `discriminant` as their last statement like so:
+///
+/// ```text
+/// ...
+/// _42 = discriminant(_1)
+/// SwitchInt(_42, ..)
+/// ```
+///
+/// If the basic block matches this pattern, this function returns the place corresponding to the
+/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
+fn switch_on_enum_discriminant<'mir, 'tcx>(
+    tcx: TyCtxt<'tcx>,
+    body: &'mir mir::Body<'tcx>,
+    block: &'mir mir::BasicBlockData<'tcx>,
+    switch_on: mir::Place<'tcx>,
+) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> {
+    for statement in block.statements.iter().rev() {
+        match &statement.kind {
+            mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated)))
+                if *lhs == switch_on =>
+            {
+                match discriminated.ty(body, tcx).ty.kind() {
+                    ty::Adt(def, _) => return Some((*discriminated, *def)),
+
+                    // `Rvalue::Discriminant` is also used to get the active yield point for a
+                    // generator, but we do not need edge-specific effects in that case. This may
+                    // change in the future.
+                    ty::Generator(..) => return None,
+
+                    t => bug!("`discriminant` called on unexpected type {:?}", t),
+                }
+            }
+            mir::StatementKind::Coverage(_) => continue,
+            _ => return None,
+        }
+    }
+    None
+}
diff --git a/compiler/rustc_mir_dataflow/src/impls/liveness.rs b/compiler/rustc_mir_dataflow/src/impls/liveness.rs
index 34e0834a68b..5aa73c7a906 100644
--- a/compiler/rustc_mir_dataflow/src/impls/liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/liveness.rs
@@ -1,8 +1,10 @@
 use rustc_index::bit_set::{BitSet, ChunkedBitSet};
 use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
-use rustc_middle::mir::{self, Local, Location, Place, StatementKind};
+use rustc_middle::mir::{
+    self, CallReturnPlaces, Local, Location, Place, StatementKind, TerminatorEdges,
+};
 
-use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKillAnalysis};
+use crate::{Analysis, AnalysisDomain, Backward, GenKill, GenKillAnalysis};
 
 /// A [live-variable dataflow analysis][liveness].
 ///
@@ -43,6 +45,10 @@ impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals {
 impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
     type Idx = Local;
 
+    fn domain_size(&self, body: &mir::Body<'tcx>) -> usize {
+        body.local_decls.len()
+    }
+
     fn statement_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
@@ -52,13 +58,14 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
         TransferFunction(trans).visit_statement(statement, location);
     }
 
-    fn terminator_effect(
+    fn terminator_effect<'mir>(
         &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        terminator: &mir::Terminator<'tcx>,
+        trans: &mut Self::Domain,
+        terminator: &'mir mir::Terminator<'tcx>,
         location: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         TransferFunction(trans).visit_terminator(terminator, location);
+        terminator.edges()
     }
 
     fn call_return_effect(
@@ -67,24 +74,19 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
         _block: mir::BasicBlock,
         return_places: CallReturnPlaces<'_, 'tcx>,
     ) {
-        return_places.for_each(|place| {
-            if let Some(local) = place.as_local() {
-                trans.kill(local);
-            }
-        });
-    }
-
-    fn yield_resume_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _resume_block: mir::BasicBlock,
-        resume_place: mir::Place<'tcx>,
-    ) {
-        YieldResumeEffect(trans).visit_place(
-            &resume_place,
-            PlaceContext::MutatingUse(MutatingUseContext::Yield),
-            Location::START,
-        )
+        if let CallReturnPlaces::Yield(resume_place) = return_places {
+            YieldResumeEffect(trans).visit_place(
+                &resume_place,
+                PlaceContext::MutatingUse(MutatingUseContext::Yield),
+                Location::START,
+            )
+        } else {
+            return_places.for_each(|place| {
+                if let Some(local) = place.as_local() {
+                    trans.kill(local);
+                }
+            });
+        }
     }
 }
 
@@ -97,7 +99,7 @@ where
     fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
         if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context {
             // The resume place is evaluated and assigned to only after generator resumes, so its
-            // effect is handled separately in `yield_resume_effect`.
+            // effect is handled separately in `call_resume_effect`.
             return;
         }
 
@@ -283,13 +285,14 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
         TransferFunction(trans).visit_statement(statement, location);
     }
 
-    fn apply_terminator_effect(
+    fn apply_terminator_effect<'mir>(
         &mut self,
         trans: &mut Self::Domain,
-        terminator: &mir::Terminator<'tcx>,
+        terminator: &'mir mir::Terminator<'tcx>,
         location: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         TransferFunction(trans).visit_terminator(terminator, location);
+        terminator.edges()
     }
 
     fn apply_call_return_effect(
@@ -298,23 +301,18 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
         _block: mir::BasicBlock,
         return_places: CallReturnPlaces<'_, 'tcx>,
     ) {
-        return_places.for_each(|place| {
-            if let Some(local) = place.as_local() {
-                trans.remove(local);
-            }
-        });
-    }
-
-    fn apply_yield_resume_effect(
-        &mut self,
-        trans: &mut Self::Domain,
-        _resume_block: mir::BasicBlock,
-        resume_place: mir::Place<'tcx>,
-    ) {
-        YieldResumeEffect(trans).visit_place(
-            &resume_place,
-            PlaceContext::MutatingUse(MutatingUseContext::Yield),
-            Location::START,
-        )
+        if let CallReturnPlaces::Yield(resume_place) = return_places {
+            YieldResumeEffect(trans).visit_place(
+                &resume_place,
+                PlaceContext::MutatingUse(MutatingUseContext::Yield),
+                Location::START,
+            )
+        } else {
+            return_places.for_each(|place| {
+                if let Some(local) = place.as_local() {
+                    trans.remove(local);
+                }
+            });
+        }
     }
 }
diff --git a/compiler/rustc_mir_dataflow/src/impls/mod.rs b/compiler/rustc_mir_dataflow/src/impls/mod.rs
index 7ddd01e34aa..f8db18fc1f8 100644
--- a/compiler/rustc_mir_dataflow/src/impls/mod.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/mod.rs
@@ -2,768 +2,18 @@
 //! bitvectors attached to each basic block, represented via a
 //! zero-sized structure.
 
-use rustc_index::bit_set::{BitSet, ChunkedBitSet};
-use rustc_index::Idx;
-use rustc_middle::mir::visit::{MirVisitable, Visitor};
-use rustc_middle::mir::{self, Body, Location};
-use rustc_middle::ty::{self, TyCtxt};
-
-use crate::drop_flag_effects_for_function_entry;
-use crate::drop_flag_effects_for_location;
-use crate::elaborate_drops::DropFlagState;
-use crate::framework::{CallReturnPlaces, SwitchIntEdgeEffects};
-use crate::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
-use crate::on_lookup_result_bits;
-use crate::MoveDataParamEnv;
-use crate::{drop_flag_effects, on_all_children_bits};
-use crate::{lattice, AnalysisDomain, GenKill, GenKillAnalysis};
-
 mod borrowed_locals;
+mod initialized;
 mod liveness;
 mod storage_liveness;
 
 pub use self::borrowed_locals::borrowed_locals;
 pub use self::borrowed_locals::MaybeBorrowedLocals;
+pub use self::initialized::{
+    DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeInitializedPlaces,
+    MaybeUninitializedPlaces,
+};
 pub use self::liveness::MaybeLiveLocals;
 pub use self::liveness::MaybeTransitiveLiveLocals;
 pub use self::liveness::TransferFunction as LivenessTransferFunction;
 pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageDead, MaybeStorageLive};
-
-/// `MaybeInitializedPlaces` tracks all places that might be
-/// initialized upon reaching a particular point in the control flow
-/// for a function.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) {                        // maybe-init:
-///                                             // {}
-///     let a = S; let mut b = S; let c; let d; // {a, b}
-///
-///     if pred {
-///         drop(a);                            // {   b}
-///         b = S;                              // {   b}
-///
-///     } else {
-///         drop(b);                            // {a}
-///         d = S;                              // {a,       d}
-///
-///     }                                       // {a, b,    d}
-///
-///     c = S;                                  // {a, b, c, d}
-/// }
-/// ```
-///
-/// To determine whether a place *must* be initialized at a
-/// particular control-flow point, one can take the set-difference
-/// between this data and the data from `MaybeUninitializedPlaces` at the
-/// corresponding control-flow point.
-///
-/// Similarly, at a given `drop` statement, the set-intersection
-/// between this data and `MaybeUninitializedPlaces` yields the set of
-/// places that would require a dynamic drop-flag at that statement.
-pub struct MaybeInitializedPlaces<'a, 'tcx> {
-    tcx: TyCtxt<'tcx>,
-    body: &'a Body<'tcx>,
-    mdpe: &'a MoveDataParamEnv<'tcx>,
-}
-
-impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
-        MaybeInitializedPlaces { tcx, body, mdpe }
-    }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
-    fn move_data(&self) -> &MoveData<'tcx> {
-        &self.mdpe.move_data
-    }
-}
-
-/// `MaybeUninitializedPlaces` tracks all places that might be
-/// uninitialized upon reaching a particular point in the control flow
-/// for a function.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) {                        // maybe-uninit:
-///                                             // {a, b, c, d}
-///     let a = S; let mut b = S; let c; let d; // {      c, d}
-///
-///     if pred {
-///         drop(a);                            // {a,    c, d}
-///         b = S;                              // {a,    c, d}
-///
-///     } else {
-///         drop(b);                            // {   b, c, d}
-///         d = S;                              // {   b, c   }
-///
-///     }                                       // {a, b, c, d}
-///
-///     c = S;                                  // {a, b,    d}
-/// }
-/// ```
-///
-/// To determine whether a place *must* be uninitialized at a
-/// particular control-flow point, one can take the set-difference
-/// between this data and the data from `MaybeInitializedPlaces` at the
-/// corresponding control-flow point.
-///
-/// Similarly, at a given `drop` statement, the set-intersection
-/// between this data and `MaybeInitializedPlaces` yields the set of
-/// places that would require a dynamic drop-flag at that statement.
-pub struct MaybeUninitializedPlaces<'a, 'tcx> {
-    tcx: TyCtxt<'tcx>,
-    body: &'a Body<'tcx>,
-    mdpe: &'a MoveDataParamEnv<'tcx>,
-
-    mark_inactive_variants_as_uninit: bool,
-}
-
-impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
-        MaybeUninitializedPlaces { tcx, body, mdpe, mark_inactive_variants_as_uninit: false }
-    }
-
-    /// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
-    /// enum discriminant.
-    ///
-    /// This is correct in a vacuum but is not the default because it causes problems in the borrow
-    /// checker, where this information gets propagated along `FakeEdge`s.
-    pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
-        self.mark_inactive_variants_as_uninit = true;
-        self
-    }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
-    fn move_data(&self) -> &MoveData<'tcx> {
-        &self.mdpe.move_data
-    }
-}
-
-/// `DefinitelyInitializedPlaces` tracks all places that are definitely
-/// initialized upon reaching a particular point in the control flow
-/// for a function.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) {                        // definite-init:
-///                                             // {          }
-///     let a = S; let mut b = S; let c; let d; // {a, b      }
-///
-///     if pred {
-///         drop(a);                            // {   b,     }
-///         b = S;                              // {   b,     }
-///
-///     } else {
-///         drop(b);                            // {a,        }
-///         d = S;                              // {a,       d}
-///
-///     }                                       // {          }
-///
-///     c = S;                                  // {       c  }
-/// }
-/// ```
-///
-/// To determine whether a place *may* be uninitialized at a
-/// particular control-flow point, one can take the set-complement
-/// of this data.
-///
-/// Similarly, at a given `drop` statement, the set-difference between
-/// this data and `MaybeInitializedPlaces` yields the set of places
-/// that would require a dynamic drop-flag at that statement.
-pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
-    tcx: TyCtxt<'tcx>,
-    body: &'a Body<'tcx>,
-    mdpe: &'a MoveDataParamEnv<'tcx>,
-}
-
-impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
-        DefinitelyInitializedPlaces { tcx, body, mdpe }
-    }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
-    fn move_data(&self) -> &MoveData<'tcx> {
-        &self.mdpe.move_data
-    }
-}
-
-/// `EverInitializedPlaces` tracks all places that might have ever been
-/// initialized upon reaching a particular point in the control flow
-/// for a function, without an intervening `StorageDead`.
-///
-/// This dataflow is used to determine if an immutable local variable may
-/// be assigned to.
-///
-/// For example, in code like the following, we have corresponding
-/// dataflow information shown in the right-hand comments.
-///
-/// ```rust
-/// struct S;
-/// fn foo(pred: bool) {                        // ever-init:
-///                                             // {          }
-///     let a = S; let mut b = S; let c; let d; // {a, b      }
-///
-///     if pred {
-///         drop(a);                            // {a, b,     }
-///         b = S;                              // {a, b,     }
-///
-///     } else {
-///         drop(b);                            // {a, b,      }
-///         d = S;                              // {a, b,    d }
-///
-///     }                                       // {a, b,    d }
-///
-///     c = S;                                  // {a, b, c, d }
-/// }
-/// ```
-pub struct EverInitializedPlaces<'a, 'tcx> {
-    #[allow(dead_code)]
-    tcx: TyCtxt<'tcx>,
-    body: &'a Body<'tcx>,
-    mdpe: &'a MoveDataParamEnv<'tcx>,
-}
-
-impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
-    pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
-        EverInitializedPlaces { tcx, body, mdpe }
-    }
-}
-
-impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
-    fn move_data(&self) -> &MoveData<'tcx> {
-        &self.mdpe.move_data
-    }
-}
-
-impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
-    fn update_bits(
-        trans: &mut impl GenKill<MovePathIndex>,
-        path: MovePathIndex,
-        state: DropFlagState,
-    ) {
-        match state {
-            DropFlagState::Absent => trans.kill(path),
-            DropFlagState::Present => trans.gen(path),
-        }
-    }
-}
-
-impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
-    fn update_bits(
-        trans: &mut impl GenKill<MovePathIndex>,
-        path: MovePathIndex,
-        state: DropFlagState,
-    ) {
-        match state {
-            DropFlagState::Absent => trans.gen(path),
-            DropFlagState::Present => trans.kill(path),
-        }
-    }
-}
-
-impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
-    fn update_bits(
-        trans: &mut impl GenKill<MovePathIndex>,
-        path: MovePathIndex,
-        state: DropFlagState,
-    ) {
-        match state {
-            DropFlagState::Absent => trans.kill(path),
-            DropFlagState::Present => trans.gen(path),
-        }
-    }
-}
-
-impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
-    type Domain = ChunkedBitSet<MovePathIndex>;
-    const NAME: &'static str = "maybe_init";
-
-    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
-        // bottom = uninitialized
-        ChunkedBitSet::new_empty(self.move_data().move_paths.len())
-    }
-
-    fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
-        drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
-            assert!(s == DropFlagState::Present);
-            state.insert(path);
-        });
-    }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
-    type Idx = MovePathIndex;
-
-    fn statement_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        statement: &mir::Statement<'tcx>,
-        location: Location,
-    ) {
-        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
-            Self::update_bits(trans, path, s)
-        });
-
-        if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
-            return;
-        }
-
-        // Mark all places as "maybe init" if they are mutably borrowed. See #90752.
-        for_each_mut_borrow(statement, location, |place| {
-            let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else {
-                return;
-            };
-            on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
-                trans.gen(child);
-            })
-        })
-    }
-
-    fn terminator_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        terminator: &mir::Terminator<'tcx>,
-        location: Location,
-    ) {
-        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
-            Self::update_bits(trans, path, s)
-        });
-
-        if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
-            return;
-        }
-
-        for_each_mut_borrow(terminator, location, |place| {
-            let LookupResult::Exact(mpi) = self.move_data().rev_lookup.find(place.as_ref()) else {
-                return;
-            };
-            on_all_children_bits(self.tcx, self.body, self.move_data(), mpi, |child| {
-                trans.gen(child);
-            })
-        })
-    }
-
-    fn call_return_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _block: mir::BasicBlock,
-        return_places: CallReturnPlaces<'_, 'tcx>,
-    ) {
-        return_places.for_each(|place| {
-            // when a call returns successfully, that means we need to set
-            // the bits for that dest_place to 1 (initialized).
-            on_lookup_result_bits(
-                self.tcx,
-                self.body,
-                self.move_data(),
-                self.move_data().rev_lookup.find(place.as_ref()),
-                |mpi| {
-                    trans.gen(mpi);
-                },
-            );
-        });
-    }
-
-    fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
-        &mut self,
-        block: mir::BasicBlock,
-        discr: &mir::Operand<'tcx>,
-        edge_effects: &mut impl SwitchIntEdgeEffects<G>,
-    ) {
-        if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
-            return;
-        }
-
-        let enum_ = discr.place().and_then(|discr| {
-            switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
-        });
-
-        let Some((enum_place, enum_def)) = enum_ else {
-            return;
-        };
-
-        let mut discriminants = enum_def.discriminants(self.tcx);
-        edge_effects.apply(|trans, edge| {
-            let Some(value) = edge.value else {
-                return;
-            };
-
-            // MIR building adds discriminants to the `values` array in the same order as they
-            // are yielded by `AdtDef::discriminants`. We rely on this to match each
-            // discriminant in `values` to its corresponding variant in linear time.
-            let (variant, _) = discriminants
-                .find(|&(_, discr)| discr.val == value)
-                .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
-
-            // Kill all move paths that correspond to variants we know to be inactive along this
-            // particular outgoing edge of a `SwitchInt`.
-            drop_flag_effects::on_all_inactive_variants(
-                self.tcx,
-                self.body,
-                self.move_data(),
-                enum_place,
-                variant,
-                |mpi| trans.kill(mpi),
-            );
-        });
-    }
-}
-
-impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
-    type Domain = ChunkedBitSet<MovePathIndex>;
-
-    const NAME: &'static str = "maybe_uninit";
-
-    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
-        // bottom = initialized (start_block_effect counters this at outset)
-        ChunkedBitSet::new_empty(self.move_data().move_paths.len())
-    }
-
-    // sets on_entry bits for Arg places
-    fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
-        // set all bits to 1 (uninit) before gathering counter-evidence
-        state.insert_all();
-
-        drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
-            assert!(s == DropFlagState::Present);
-            state.remove(path);
-        });
-    }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
-    type Idx = MovePathIndex;
-
-    fn statement_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _statement: &mir::Statement<'tcx>,
-        location: Location,
-    ) {
-        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
-            Self::update_bits(trans, path, s)
-        });
-
-        // Unlike in `MaybeInitializedPlaces` above, we don't need to change the state when a
-        // mutable borrow occurs. Places cannot become uninitialized through a mutable reference.
-    }
-
-    fn terminator_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _terminator: &mir::Terminator<'tcx>,
-        location: Location,
-    ) {
-        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
-            Self::update_bits(trans, path, s)
-        });
-    }
-
-    fn call_return_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _block: mir::BasicBlock,
-        return_places: CallReturnPlaces<'_, 'tcx>,
-    ) {
-        return_places.for_each(|place| {
-            // when a call returns successfully, that means we need to set
-            // the bits for that dest_place to 0 (initialized).
-            on_lookup_result_bits(
-                self.tcx,
-                self.body,
-                self.move_data(),
-                self.move_data().rev_lookup.find(place.as_ref()),
-                |mpi| {
-                    trans.kill(mpi);
-                },
-            );
-        });
-    }
-
-    fn switch_int_edge_effects<G: GenKill<Self::Idx>>(
-        &mut self,
-        block: mir::BasicBlock,
-        discr: &mir::Operand<'tcx>,
-        edge_effects: &mut impl SwitchIntEdgeEffects<G>,
-    ) {
-        if !self.tcx.sess.opts.unstable_opts.precise_enum_drop_elaboration {
-            return;
-        }
-
-        if !self.mark_inactive_variants_as_uninit {
-            return;
-        }
-
-        let enum_ = discr.place().and_then(|discr| {
-            switch_on_enum_discriminant(self.tcx, &self.body, &self.body[block], discr)
-        });
-
-        let Some((enum_place, enum_def)) = enum_ else {
-            return;
-        };
-
-        let mut discriminants = enum_def.discriminants(self.tcx);
-        edge_effects.apply(|trans, edge| {
-            let Some(value) = edge.value else {
-                return;
-            };
-
-            // MIR building adds discriminants to the `values` array in the same order as they
-            // are yielded by `AdtDef::discriminants`. We rely on this to match each
-            // discriminant in `values` to its corresponding variant in linear time.
-            let (variant, _) = discriminants
-                .find(|&(_, discr)| discr.val == value)
-                .expect("Order of `AdtDef::discriminants` differed from `SwitchInt::values`");
-
-            // Mark all move paths that correspond to variants other than this one as maybe
-            // uninitialized (in reality, they are *definitely* uninitialized).
-            drop_flag_effects::on_all_inactive_variants(
-                self.tcx,
-                self.body,
-                self.move_data(),
-                enum_place,
-                variant,
-                |mpi| trans.gen(mpi),
-            );
-        });
-    }
-}
-
-impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
-    /// Use set intersection as the join operator.
-    type Domain = lattice::Dual<BitSet<MovePathIndex>>;
-
-    const NAME: &'static str = "definite_init";
-
-    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
-        // bottom = initialized (start_block_effect counters this at outset)
-        lattice::Dual(BitSet::new_filled(self.move_data().move_paths.len()))
-    }
-
-    // sets on_entry bits for Arg places
-    fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut Self::Domain) {
-        state.0.clear();
-
-        drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
-            assert!(s == DropFlagState::Present);
-            state.0.insert(path);
-        });
-    }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
-    type Idx = MovePathIndex;
-
-    fn statement_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _statement: &mir::Statement<'tcx>,
-        location: Location,
-    ) {
-        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
-            Self::update_bits(trans, path, s)
-        })
-    }
-
-    fn terminator_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _terminator: &mir::Terminator<'tcx>,
-        location: Location,
-    ) {
-        drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
-            Self::update_bits(trans, path, s)
-        })
-    }
-
-    fn call_return_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _block: mir::BasicBlock,
-        return_places: CallReturnPlaces<'_, 'tcx>,
-    ) {
-        return_places.for_each(|place| {
-            // when a call returns successfully, that means we need to set
-            // the bits for that dest_place to 1 (initialized).
-            on_lookup_result_bits(
-                self.tcx,
-                self.body,
-                self.move_data(),
-                self.move_data().rev_lookup.find(place.as_ref()),
-                |mpi| {
-                    trans.gen(mpi);
-                },
-            );
-        });
-    }
-}
-
-impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
-    type Domain = ChunkedBitSet<InitIndex>;
-
-    const NAME: &'static str = "ever_init";
-
-    fn bottom_value(&self, _: &mir::Body<'tcx>) -> Self::Domain {
-        // bottom = no initialized variables by default
-        ChunkedBitSet::new_empty(self.move_data().inits.len())
-    }
-
-    fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain) {
-        for arg_init in 0..body.arg_count {
-            state.insert(InitIndex::new(arg_init));
-        }
-    }
-}
-
-impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
-    type Idx = InitIndex;
-
-    #[instrument(skip(self, trans), level = "debug")]
-    fn statement_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        stmt: &mir::Statement<'tcx>,
-        location: Location,
-    ) {
-        let move_data = self.move_data();
-        let init_path_map = &move_data.init_path_map;
-        let init_loc_map = &move_data.init_loc_map;
-        let rev_lookup = &move_data.rev_lookup;
-
-        debug!("initializes move_indexes {:?}", &init_loc_map[location]);
-        trans.gen_all(init_loc_map[location].iter().copied());
-
-        if let mir::StatementKind::StorageDead(local) = stmt.kind {
-            // End inits for StorageDead, so that an immutable variable can
-            // be reinitialized on the next iteration of the loop.
-            let move_path_index = rev_lookup.find_local(local);
-            debug!("clears the ever initialized status of {:?}", init_path_map[move_path_index]);
-            trans.kill_all(init_path_map[move_path_index].iter().copied());
-        }
-    }
-
-    #[instrument(skip(self, trans, _terminator), level = "debug")]
-    fn terminator_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _terminator: &mir::Terminator<'tcx>,
-        location: Location,
-    ) {
-        let (body, move_data) = (self.body, self.move_data());
-        let term = body[location.block].terminator();
-        let init_loc_map = &move_data.init_loc_map;
-        debug!(?term);
-        debug!("initializes move_indexes {:?}", init_loc_map[location]);
-        trans.gen_all(
-            init_loc_map[location]
-                .iter()
-                .filter(|init_index| {
-                    move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
-                })
-                .copied(),
-        );
-    }
-
-    fn call_return_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        block: mir::BasicBlock,
-        _return_places: CallReturnPlaces<'_, 'tcx>,
-    ) {
-        let move_data = self.move_data();
-        let init_loc_map = &move_data.init_loc_map;
-
-        let call_loc = self.body.terminator_loc(block);
-        for init_index in &init_loc_map[call_loc] {
-            trans.gen(*init_index);
-        }
-    }
-}
-
-/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
-/// an enum discriminant.
-///
-/// We expect such blocks to have a call to `discriminant` as their last statement like so:
-///
-/// ```text
-/// ...
-/// _42 = discriminant(_1)
-/// SwitchInt(_42, ..)
-/// ```
-///
-/// If the basic block matches this pattern, this function returns the place corresponding to the
-/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
-fn switch_on_enum_discriminant<'mir, 'tcx>(
-    tcx: TyCtxt<'tcx>,
-    body: &'mir mir::Body<'tcx>,
-    block: &'mir mir::BasicBlockData<'tcx>,
-    switch_on: mir::Place<'tcx>,
-) -> Option<(mir::Place<'tcx>, ty::AdtDef<'tcx>)> {
-    for statement in block.statements.iter().rev() {
-        match &statement.kind {
-            mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated)))
-                if *lhs == switch_on =>
-            {
-                match discriminated.ty(body, tcx).ty.kind() {
-                    ty::Adt(def, _) => return Some((*discriminated, *def)),
-
-                    // `Rvalue::Discriminant` is also used to get the active yield point for a
-                    // generator, but we do not need edge-specific effects in that case. This may
-                    // change in the future.
-                    ty::Generator(..) => return None,
-
-                    t => bug!("`discriminant` called on unexpected type {:?}", t),
-                }
-            }
-            mir::StatementKind::Coverage(_) => continue,
-            _ => return None,
-        }
-    }
-    None
-}
-
-struct OnMutBorrow<F>(F);
-
-impl<'tcx, F> Visitor<'tcx> for OnMutBorrow<F>
-where
-    F: FnMut(&mir::Place<'tcx>),
-{
-    fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
-        // FIXME: Does `&raw const foo` allow mutation? See #90413.
-        match rvalue {
-            mir::Rvalue::Ref(_, mir::BorrowKind::Mut { .. }, place)
-            | mir::Rvalue::AddressOf(_, place) => (self.0)(place),
-
-            _ => {}
-        }
-
-        self.super_rvalue(rvalue, location)
-    }
-}
-
-/// Calls `f` for each mutable borrow or raw reference in the program.
-///
-/// This DOES NOT call `f` for a shared borrow of a type with interior mutability. That's okay for
-/// initializedness, because we cannot move from an `UnsafeCell` (outside of `core::cell`), but
-/// other analyses will likely need to check for `!Freeze`.
-fn for_each_mut_borrow<'tcx>(
-    mir: &impl MirVisitable<'tcx>,
-    location: Location,
-    f: impl FnMut(&mir::Place<'tcx>),
-) {
-    let mut vis = OnMutBorrow(f);
-
-    mir.apply(location, &mut vis);
-}
diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
index 666c8d50a8a..bea23b7f7ae 100644
--- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
+++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs
@@ -1,10 +1,12 @@
-pub use super::*;
-
-use crate::{CallReturnPlaces, GenKill, ResultsClonedCursor};
+use rustc_index::bit_set::BitSet;
 use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
 use rustc_middle::mir::*;
+
 use std::borrow::Cow;
 
+use super::MaybeBorrowedLocals;
+use crate::{GenKill, ResultsClonedCursor};
+
 #[derive(Clone)]
 pub struct MaybeStorageLive<'a> {
     always_live_locals: Cow<'a, BitSet<Local>>,
@@ -27,12 +29,12 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> {
 
     const NAME: &'static str = "maybe_storage_live";
 
-    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
         // bottom = dead
         BitSet::new_empty(body.local_decls.len())
     }
 
-    fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
+    fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
         assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
         for local in self.always_live_locals.iter() {
             on_entry.insert(local);
@@ -47,10 +49,14 @@ impl<'tcx, 'a> crate::AnalysisDomain<'tcx> for MaybeStorageLive<'a> {
 impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
     type Idx = Local;
 
+    fn domain_size(&self, body: &Body<'tcx>) -> usize {
+        body.local_decls.len()
+    }
+
     fn statement_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
-        stmt: &mir::Statement<'tcx>,
+        stmt: &Statement<'tcx>,
         _: Location,
     ) {
         match stmt.kind {
@@ -60,13 +66,14 @@ impl<'tcx, 'a> crate::GenKillAnalysis<'tcx> for MaybeStorageLive<'a> {
         }
     }
 
-    fn terminator_effect(
+    fn terminator_effect<'mir>(
         &mut self,
-        _trans: &mut impl GenKill<Self::Idx>,
-        _: &mir::Terminator<'tcx>,
+        _trans: &mut Self::Domain,
+        terminator: &'mir Terminator<'tcx>,
         _: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         // Terminators have no effect
+        terminator.edges()
     }
 
     fn call_return_effect(
@@ -95,12 +102,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
 
     const NAME: &'static str = "maybe_storage_dead";
 
-    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
         // bottom = live
         BitSet::new_empty(body.local_decls.len())
     }
 
-    fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
+    fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
         assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
         // Do not iterate on return place and args, as they are trivially always live.
         for local in body.vars_and_temps_iter() {
@@ -114,10 +121,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeStorageDead {
 impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
     type Idx = Local;
 
+    fn domain_size(&self, body: &Body<'tcx>) -> usize {
+        body.local_decls.len()
+    }
+
     fn statement_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
-        stmt: &mir::Statement<'tcx>,
+        stmt: &Statement<'tcx>,
         _: Location,
     ) {
         match stmt.kind {
@@ -127,13 +138,14 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeStorageDead {
         }
     }
 
-    fn terminator_effect(
+    fn terminator_effect<'mir>(
         &mut self,
-        _trans: &mut impl GenKill<Self::Idx>,
-        _: &mir::Terminator<'tcx>,
+        _: &mut Self::Domain,
+        terminator: &'mir Terminator<'tcx>,
         _: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         // Terminators have no effect
+        terminator.edges()
     }
 
     fn call_return_effect(
@@ -172,12 +184,12 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
 
     const NAME: &'static str = "requires_storage";
 
-    fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain {
+    fn bottom_value(&self, body: &Body<'tcx>) -> Self::Domain {
         // bottom = dead
         BitSet::new_empty(body.local_decls.len())
     }
 
-    fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut Self::Domain) {
+    fn initialize_start_block(&self, body: &Body<'tcx>, on_entry: &mut Self::Domain) {
         // The resume argument is live on function entry (we don't care about
         // the `self` argument)
         for arg in body.args_iter().skip(1) {
@@ -189,10 +201,14 @@ impl<'tcx> crate::AnalysisDomain<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
 impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
     type Idx = Local;
 
+    fn domain_size(&self, body: &Body<'tcx>) -> usize {
+        body.local_decls.len()
+    }
+
     fn before_statement_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
-        stmt: &mir::Statement<'tcx>,
+        stmt: &Statement<'tcx>,
         loc: Location,
     ) {
         // If a place is borrowed in a statement, it needs storage for that statement.
@@ -225,7 +241,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
     fn statement_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
-        _: &mir::Statement<'tcx>,
+        _: &Statement<'tcx>,
         loc: Location,
     ) {
         // If we move from a place then it only stops needing storage *after*
@@ -236,11 +252,14 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
     fn before_terminator_effect(
         &mut self,
         trans: &mut impl GenKill<Self::Idx>,
-        terminator: &mir::Terminator<'tcx>,
+        terminator: &Terminator<'tcx>,
         loc: Location,
     ) {
         // If a place is borrowed in a terminator, it needs storage for that terminator.
-        self.borrowed_locals.mut_analysis().terminator_effect(trans, terminator, loc);
+        self.borrowed_locals
+            .mut_analysis()
+            .transfer_function(trans)
+            .visit_terminator(terminator, loc);
 
         match &terminator.kind {
             TerminatorKind::Call { destination, .. } => {
@@ -286,12 +305,12 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
         }
     }
 
-    fn terminator_effect(
+    fn terminator_effect<'t>(
         &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        terminator: &mir::Terminator<'tcx>,
+        trans: &mut Self::Domain,
+        terminator: &'t Terminator<'tcx>,
         loc: Location,
-    ) {
+    ) -> TerminatorEdges<'t, 'tcx> {
         match terminator.kind {
             // For call terminators the destination requires storage for the call
             // and after the call returns successfully, but not after a panic.
@@ -323,6 +342,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
         }
 
         self.check_for_move(trans, loc);
+        terminator.edges()
     }
 
     fn call_return_effect(
@@ -333,15 +353,6 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, '_, 'tcx> {
     ) {
         return_places.for_each(|place| trans.gen(place.local));
     }
-
-    fn yield_resume_effect(
-        &mut self,
-        trans: &mut impl GenKill<Self::Idx>,
-        _resume_block: BasicBlock,
-        resume_place: mir::Place<'tcx>,
-    ) {
-        trans.gen(resume_place.local);
-    }
 }
 
 impl<'tcx> MaybeRequiresStorage<'_, '_, 'tcx> {
diff --git a/compiler/rustc_mir_dataflow/src/lib.rs b/compiler/rustc_mir_dataflow/src/lib.rs
index 900d438f8d5..0cdbee19d2c 100644
--- a/compiler/rustc_mir_dataflow/src/lib.rs
+++ b/compiler/rustc_mir_dataflow/src/lib.rs
@@ -28,8 +28,8 @@ pub use self::drop_flag_effects::{
 };
 pub use self::framework::{
     fmt, graphviz, lattice, visit_results, Analysis, AnalysisDomain, AnalysisResults, Backward,
-    CallReturnPlaces, CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis,
-    JoinSemiLattice, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
+    CloneAnalysis, Direction, Engine, Forward, GenKill, GenKillAnalysis, JoinSemiLattice,
+    MaybeReachable, Results, ResultsCloned, ResultsClonedCursor, ResultsCursor, ResultsRefCursor,
     ResultsVisitable, ResultsVisitor, SwitchIntEdgeEffects,
 };
 
diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs
index 17bb8fc37ad..766e0257efd 100644
--- a/compiler/rustc_mir_dataflow/src/value_analysis.rs
+++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs
@@ -47,8 +47,7 @@ use rustc_target::abi::{FieldIdx, VariantIdx};
 
 use crate::lattice::{HasBottom, HasTop};
 use crate::{
-    fmt::DebugWithContext, Analysis, AnalysisDomain, CallReturnPlaces, JoinSemiLattice,
-    SwitchIntEdgeEffects,
+    fmt::DebugWithContext, Analysis, AnalysisDomain, JoinSemiLattice, SwitchIntEdgeEffects,
 };
 
 pub trait ValueAnalysis<'tcx> {
@@ -242,11 +241,19 @@ pub trait ValueAnalysis<'tcx> {
 
     /// The effect of a successful function call return should not be
     /// applied here, see [`Analysis::apply_terminator_effect`].
-    fn handle_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
+    fn handle_terminator<'mir>(
+        &self,
+        terminator: &'mir Terminator<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> TerminatorEdges<'mir, 'tcx> {
         self.super_terminator(terminator, state)
     }
 
-    fn super_terminator(&self, terminator: &Terminator<'tcx>, state: &mut State<Self::Value>) {
+    fn super_terminator<'mir>(
+        &self,
+        terminator: &'mir Terminator<'tcx>,
+        state: &mut State<Self::Value>,
+    ) -> TerminatorEdges<'mir, 'tcx> {
         match &terminator.kind {
             TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } => {
                 // Effect is applied by `handle_call_return`.
@@ -258,8 +265,10 @@ pub trait ValueAnalysis<'tcx> {
                 // They would have an effect, but are not allowed in this phase.
                 bug!("encountered disallowed terminator");
             }
+            TerminatorKind::SwitchInt { discr, targets } => {
+                return self.handle_switch_int(discr, targets, state);
+            }
             TerminatorKind::Goto { .. }
-            | TerminatorKind::SwitchInt { .. }
             | TerminatorKind::Resume
             | TerminatorKind::Terminate
             | TerminatorKind::Return
@@ -271,6 +280,7 @@ pub trait ValueAnalysis<'tcx> {
                 // These terminators have no effect on the analysis.
             }
         }
+        terminator.edges()
     }
 
     fn handle_call_return(
@@ -291,19 +301,22 @@ pub trait ValueAnalysis<'tcx> {
         })
     }
 
-    fn handle_switch_int(
+    fn handle_switch_int<'mir>(
         &self,
-        discr: &Operand<'tcx>,
-        apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
-    ) {
-        self.super_switch_int(discr, apply_edge_effects)
+        discr: &'mir Operand<'tcx>,
+        targets: &'mir SwitchTargets,
+        state: &mut State<Self::Value>,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        self.super_switch_int(discr, targets, state)
     }
 
-    fn super_switch_int(
+    fn super_switch_int<'mir>(
         &self,
-        _discr: &Operand<'tcx>,
-        _apply_edge_effects: &mut impl SwitchIntEdgeEffects<State<Self::Value>>,
-    ) {
+        discr: &'mir Operand<'tcx>,
+        targets: &'mir SwitchTargets,
+        _state: &mut State<Self::Value>,
+    ) -> TerminatorEdges<'mir, 'tcx> {
+        TerminatorEdges::SwitchInt { discr, targets }
     }
 
     fn wrap(self) -> ValueAnalysisWrapper<Self>
@@ -353,14 +366,16 @@ where
         }
     }
 
-    fn apply_terminator_effect(
+    fn apply_terminator_effect<'mir>(
         &mut self,
         state: &mut Self::Domain,
-        terminator: &Terminator<'tcx>,
+        terminator: &'mir Terminator<'tcx>,
         _location: Location,
-    ) {
+    ) -> TerminatorEdges<'mir, 'tcx> {
         if state.is_reachable() {
-            self.0.handle_terminator(terminator, state);
+            self.0.handle_terminator(terminator, state)
+        } else {
+            TerminatorEdges::None
         }
     }
 
@@ -368,7 +383,7 @@ where
         &mut self,
         state: &mut Self::Domain,
         _block: BasicBlock,
-        return_places: crate::CallReturnPlaces<'_, 'tcx>,
+        return_places: CallReturnPlaces<'_, 'tcx>,
     ) {
         if state.is_reachable() {
             self.0.handle_call_return(return_places, state)
@@ -378,11 +393,9 @@ where
     fn apply_switch_int_edge_effects(
         &mut self,
         _block: BasicBlock,
-        discr: &Operand<'tcx>,
-        apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
+        _discr: &Operand<'tcx>,
+        _apply_edge_effects: &mut impl SwitchIntEdgeEffects<Self::Domain>,
     ) {
-        // FIXME: Dataflow framework provides no access to current state here.
-        self.0.handle_switch_int(discr, apply_edge_effects)
     }
 }