diff --git a/Cargo.lock b/Cargo.lock
index dfe3db5907a..b46696dde7c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4383,7 +4383,6 @@ dependencies = [
 name = "rustc_typeck"
 version = "0.0.0"
 dependencies = [
- "itertools 0.9.0",
  "rustc_arena",
  "rustc_ast",
  "rustc_attr",
@@ -4396,6 +4395,7 @@ dependencies = [
  "rustc_lint",
  "rustc_macros",
  "rustc_middle",
+ "rustc_serialize",
  "rustc_session",
  "rustc_span",
  "rustc_target",
diff --git a/compiler/rustc_typeck/Cargo.toml b/compiler/rustc_typeck/Cargo.toml
index 1da106d7002..3279c331ade 100644
--- a/compiler/rustc_typeck/Cargo.toml
+++ b/compiler/rustc_typeck/Cargo.toml
@@ -8,7 +8,6 @@ test = false
 doctest = false
 
 [dependencies]
-itertools = "0.9"
 rustc_arena = { path = "../rustc_arena" }
 tracing = "0.1"
 rustc_macros = { path = "../rustc_macros" }
@@ -28,3 +27,4 @@ rustc_infer = { path = "../rustc_infer" }
 rustc_trait_selection = { path = "../rustc_trait_selection" }
 rustc_ty_utils = { path = "../rustc_ty_utils" }
 rustc_lint = { path = "../rustc_lint" }
+rustc_serialize = { path = "../rustc_serialize" }
diff --git a/compiler/rustc_typeck/src/check/generator_interior.rs b/compiler/rustc_typeck/src/check/generator_interior.rs
index 30977293018..33533ab6189 100644
--- a/compiler/rustc_typeck/src/check/generator_interior.rs
+++ b/compiler/rustc_typeck/src/check/generator_interior.rs
@@ -3,13 +3,12 @@
 //! is calculated in `rustc_const_eval::transform::generator` and may be a subset of the
 //! types computed here.
 
-use std::mem;
-
 use crate::expr_use_visitor::{self, ExprUseVisitor};
 
+use self::drop_ranges::DropRanges;
+
 use super::FnCtxt;
 use hir::{HirIdMap, Node};
-use itertools::Itertools;
 use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
 use rustc_errors::pluralize;
 use rustc_hir as hir;
@@ -30,6 +29,8 @@ use tracing::debug;
 #[cfg(test)]
 mod tests;
 
+mod drop_ranges;
+
 struct InteriorVisitor<'a, 'tcx> {
     fcx: &'a FnCtxt<'a, 'tcx>,
     types: FxIndexSet<ty::GeneratorInteriorTypeCause<'tcx>>,
@@ -45,7 +46,7 @@ struct InteriorVisitor<'a, 'tcx> {
     guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
     guard_bindings_set: HirIdSet,
     linted_values: HirIdSet,
-    drop_ranges: HirIdMap<DropRange>,
+    drop_ranges: DropRanges,
 }
 
 impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
@@ -85,14 +86,10 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
                                 yield_data.expr_and_pat_count, self.expr_count, source_span
                             );
 
-                            match self.drop_ranges.get(&hir_id) {
-                                Some(range)
-                                    if range.is_dropped_at(yield_data.expr_and_pat_count) =>
-                                {
-                                    debug!("value is dropped at yield point; not recording");
-                                    return false;
-                                }
-                                _ => (),
+                            if self.drop_ranges.is_dropped_at(hir_id, yield_data.expr_and_pat_count)
+                            {
+                                debug!("value is dropped at yield point; not recording");
+                                return false;
                             }
 
                             // If it is a borrowing happening in the guard,
@@ -233,25 +230,27 @@ pub fn resolve_interior<'a, 'tcx>(
     let body = fcx.tcx.hir().body(body_id);
 
     let mut visitor = {
-        let mut drop_range_visitor = DropRangeVisitor {
+        let mut expr_use_visitor = ExprUseDelegate {
             hir: fcx.tcx.hir(),
             consumed_places: <_>::default(),
             borrowed_places: <_>::default(),
-            drop_ranges: <_>::default(),
-            expr_count: 0,
         };
 
         // Run ExprUseVisitor to find where values are consumed.
         ExprUseVisitor::new(
-            &mut drop_range_visitor,
+            &mut expr_use_visitor,
             &fcx.infcx,
             def_id.expect_local(),
             fcx.param_env,
             &fcx.typeck_results.borrow(),
         )
         .consume_body(body);
+
+        let mut drop_range_visitor = DropRangeVisitor::from(expr_use_visitor);
         intravisit::walk_body(&mut drop_range_visitor, body);
 
+        drop_range_visitor.drop_ranges.propagate_to_fixpoint();
+
         InteriorVisitor {
             fcx,
             types: FxIndexSet::default(),
@@ -673,29 +672,46 @@ fn check_must_not_suspend_def(
     false
 }
 
-/// This struct facilitates computing the ranges for which a place is uninitialized.
-struct DropRangeVisitor<'tcx> {
+struct ExprUseDelegate<'tcx> {
     hir: Map<'tcx>,
     /// Maps a HirId to a set of HirIds that are dropped by that node.
     consumed_places: HirIdMap<HirIdSet>,
     borrowed_places: HirIdSet,
-    drop_ranges: HirIdMap<DropRange>,
-    expr_count: usize,
 }
 
-impl DropRangeVisitor<'tcx> {
+impl<'tcx> ExprUseDelegate<'tcx> {
     fn mark_consumed(&mut self, consumer: HirId, target: HirId) {
         if !self.consumed_places.contains_key(&consumer) {
             self.consumed_places.insert(consumer, <_>::default());
         }
         self.consumed_places.get_mut(&consumer).map(|places| places.insert(target));
     }
+}
 
-    fn drop_range(&mut self, hir_id: &HirId) -> &mut DropRange {
-        if !self.drop_ranges.contains_key(hir_id) {
-            self.drop_ranges.insert(*hir_id, DropRange::empty());
+/// This struct facilitates computing the ranges for which a place is uninitialized.
+struct DropRangeVisitor<'tcx> {
+    hir: Map<'tcx>,
+    /// Maps a HirId to a set of HirIds that are dropped by that node.
+    consumed_places: HirIdMap<HirIdSet>,
+    borrowed_places: HirIdSet,
+    drop_ranges: DropRanges,
+    expr_count: usize,
+}
+
+impl<'tcx> DropRangeVisitor<'tcx> {
+    fn from(uses: ExprUseDelegate<'tcx>) -> Self {
+        debug!("consumed_places: {:?}", uses.consumed_places);
+        let drop_ranges = DropRanges::new(
+            uses.consumed_places.iter().flat_map(|(_, places)| places.iter().copied()),
+            &uses.hir,
+        );
+        Self {
+            hir: uses.hir,
+            consumed_places: uses.consumed_places,
+            borrowed_places: uses.borrowed_places,
+            drop_ranges,
+            expr_count: 0,
         }
-        self.drop_ranges.get_mut(hir_id).unwrap()
     }
 
     fn record_drop(&mut self, hir_id: HirId) {
@@ -704,41 +720,10 @@ impl DropRangeVisitor<'tcx> {
         } else {
             debug!("marking {:?} as dropped at {}", hir_id, self.expr_count);
             let count = self.expr_count;
-            self.drop_range(&hir_id).drop(count);
+            self.drop_ranges.drop_at(hir_id, count);
         }
     }
 
-    fn swap_drop_ranges(&mut self, mut other: HirIdMap<DropRange>) -> HirIdMap<DropRange> {
-        mem::swap(&mut self.drop_ranges, &mut other);
-        other
-    }
-
-    fn fork_drop_ranges(&self) -> HirIdMap<DropRange> {
-        self.drop_ranges.iter().map(|(k, v)| (*k, v.fork_at(self.expr_count))).collect()
-    }
-
-    fn intersect_drop_ranges(&mut self, drops: HirIdMap<DropRange>) {
-        drops.into_iter().for_each(|(k, v)| match self.drop_ranges.get_mut(&k) {
-            Some(ranges) => *ranges = ranges.intersect(&v),
-            None => {
-                self.drop_ranges.insert(k, v);
-            }
-        })
-    }
-
-    fn merge_drop_ranges_at(&mut self, drops: HirIdMap<DropRange>, join_point: usize) {
-        drops.into_iter().for_each(|(k, v)| {
-            if !self.drop_ranges.contains_key(&k) {
-                self.drop_ranges.insert(k, DropRange { events: vec![] });
-            }
-            self.drop_ranges.get_mut(&k).unwrap().merge_with(&v, join_point);
-        });
-    }
-
-    fn merge_drop_ranges(&mut self, drops: HirIdMap<DropRange>) {
-        self.merge_drop_ranges_at(drops, self.expr_count);
-    }
-
     /// ExprUseVisitor's consume callback doesn't go deep enough for our purposes in all
     /// expressions. This method consumes a little deeper into the expression when needed.
     fn consume_expr(&mut self, expr: &hir::Expr<'_>) {
@@ -748,18 +733,7 @@ impl DropRangeVisitor<'tcx> {
             .get(&expr.hir_id)
             .map_or(vec![], |places| places.iter().cloned().collect());
         for place in places {
-            self.record_drop(place);
-            if let Some(Node::Expr(expr)) = self.hir.find(place) {
-                match expr.kind {
-                    hir::ExprKind::Path(hir::QPath::Resolved(
-                        _,
-                        hir::Path { res: hir::def::Res::Local(hir_id), .. },
-                    )) => {
-                        self.record_drop(*hir_id);
-                    }
-                    _ => (),
-                }
-            }
+            for_each_consumable(place, self.hir.find(place), |hir_id| self.record_drop(hir_id));
         }
     }
 
@@ -771,13 +745,28 @@ impl DropRangeVisitor<'tcx> {
         {
             let location = self.expr_count;
             debug!("reinitializing {:?} at {}", hir_id, location);
-            self.drop_range(hir_id).reinit(location)
+            self.drop_ranges.reinit_at(*hir_id, location);
         } else {
             debug!("reinitializing {:?} is not supported", expr);
         }
     }
 }
 
+fn for_each_consumable(place: HirId, node: Option<Node<'_>>, mut f: impl FnMut(HirId)) {
+    f(place);
+    if let Some(Node::Expr(expr)) = node {
+        match expr.kind {
+            hir::ExprKind::Path(hir::QPath::Resolved(
+                _,
+                hir::Path { res: hir::def::Res::Local(hir_id), .. },
+            )) => {
+                f(*hir_id);
+            }
+            _ => (),
+        }
+    }
+}
+
 fn place_hir_id(place: &Place<'_>) -> Option<HirId> {
     match place.base {
         PlaceBase::Rvalue | PlaceBase::StaticItem => None,
@@ -786,7 +775,7 @@ fn place_hir_id(place: &Place<'_>) -> Option<HirId> {
     }
 }
 
-impl<'tcx> expr_use_visitor::Delegate<'tcx> for DropRangeVisitor<'tcx> {
+impl<'tcx> expr_use_visitor::Delegate<'tcx> for ExprUseDelegate<'tcx> {
     fn consume(
         &mut self,
         place_with_id: &expr_use_visitor::PlaceWithHirId<'tcx>,
@@ -839,58 +828,41 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> {
     fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
         let mut reinit = None;
         match expr.kind {
-            ExprKind::AssignOp(_op, lhs, rhs) => {
-                // These operations are weird because their order of evaluation depends on whether
-                // the operator is overloaded. In a perfect world, we'd just ask the type checker
-                // whether this is a method call, but we also need to match the expression IDs
-                // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order,
-                // so it runs both orders and picks the most conservative. We'll mirror that here.
-                let mut old_count = self.expr_count;
-                self.visit_expr(lhs);
-                self.visit_expr(rhs);
+            // ExprKind::AssignOp(_op, lhs, rhs) => {
+            //     // These operations are weird because their order of evaluation depends on whether
+            //     // the operator is overloaded. In a perfect world, we'd just ask the type checker
+            //     // whether this is a method call, but we also need to match the expression IDs
+            //     // from RegionResolutionVisitor. RegionResolutionVisitor doesn't know the order,
+            //     // so it runs both orders and picks the most conservative. We'll mirror that here.
+            //     let mut old_count = self.expr_count;
+            //     self.visit_expr(lhs);
+            //     self.visit_expr(rhs);
 
-                let old_drops = self.swap_drop_ranges(<_>::default());
-                std::mem::swap(&mut old_count, &mut self.expr_count);
-                self.visit_expr(rhs);
-                self.visit_expr(lhs);
+            //     let old_drops = self.swap_drop_ranges(<_>::default());
+            //     std::mem::swap(&mut old_count, &mut self.expr_count);
+            //     self.visit_expr(rhs);
+            //     self.visit_expr(lhs);
 
-                // We should have visited the same number of expressions in either order.
-                assert_eq!(old_count, self.expr_count);
+            //     // We should have visited the same number of expressions in either order.
+            //     assert_eq!(old_count, self.expr_count);
 
-                self.intersect_drop_ranges(old_drops);
-            }
+            //     self.intersect_drop_ranges(old_drops);
+            // }
             ExprKind::If(test, if_true, if_false) => {
                 self.visit_expr(test);
 
-                match if_false {
-                    Some(if_false) => {
-                        let mut true_ranges = self.fork_drop_ranges();
-                        let mut false_ranges = self.fork_drop_ranges();
+                let fork = self.expr_count - 1;
 
-                        true_ranges = self.swap_drop_ranges(true_ranges);
-                        self.visit_expr(if_true);
-                        true_ranges = self.swap_drop_ranges(true_ranges);
+                self.drop_ranges.add_control_edge(fork, self.expr_count);
+                self.visit_expr(if_true);
+                let true_end = self.expr_count - 1;
 
-                        false_ranges =
-                            self.swap_drop_ranges(trim_drop_ranges(&false_ranges, self.expr_count));
-                        self.visit_expr(if_false);
-                        false_ranges = self.swap_drop_ranges(false_ranges);
-
-                        self.merge_drop_ranges(true_ranges);
-                        self.merge_drop_ranges(false_ranges);
-                    }
-                    None => {
-                        let mut true_ranges = self.fork_drop_ranges();
-                        debug!("true branch drop range fork: {:?}", true_ranges);
-                        true_ranges = self.swap_drop_ranges(true_ranges);
-                        self.visit_expr(if_true);
-                        true_ranges = self.swap_drop_ranges(true_ranges);
-                        debug!("true branch computed drop_ranges: {:?}", true_ranges);
-                        debug!("drop ranges before merging: {:?}", self.drop_ranges);
-                        self.merge_drop_ranges(true_ranges);
-                        debug!("drop ranges after merging: {:?}", self.drop_ranges);
-                    }
+                if let Some(if_false) = if_false {
+                    self.drop_ranges.add_control_edge(fork, self.expr_count);
+                    self.visit_expr(if_false);
                 }
+
+                self.drop_ranges.add_control_edge(true_end, self.expr_count);
             }
             ExprKind::Assign(lhs, rhs, _) => {
                 self.visit_expr(lhs);
@@ -899,27 +871,18 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> {
                 reinit = Some(lhs);
             }
             ExprKind::Loop(body, ..) => {
-                // FIXME: we probably need to iterate this to a fixpoint.
-                let body_drop_ranges = self.fork_drop_ranges();
-                let old_drop_ranges = self.swap_drop_ranges(body_drop_ranges);
-
-                let join_point = self.expr_count;
-
+                let loop_begin = self.expr_count;
                 self.visit_block(body);
-
-                let body_drop_ranges = self.swap_drop_ranges(old_drop_ranges);
-                self.merge_drop_ranges_at(body_drop_ranges, join_point);
+                self.drop_ranges.add_control_edge(self.expr_count - 1, loop_begin);
             }
             ExprKind::Match(scrutinee, arms, ..) => {
                 self.visit_expr(scrutinee);
 
-                let forked_ranges = self.fork_drop_ranges();
+                let fork = self.expr_count - 1;
                 let arm_drops = arms
                     .iter()
-                    .map(|Arm { hir_id, pat, body, guard, .. }| {
-                        debug!("match arm {:?} starts at {}", hir_id, self.expr_count);
-                        let old_ranges = self
-                            .swap_drop_ranges(trim_drop_ranges(&forked_ranges, self.expr_count));
+                    .map(|Arm { pat, body, guard, .. }| {
+                        self.drop_ranges.add_control_edge(fork, self.expr_count);
                         self.visit_pat(pat);
                         match guard {
                             Some(Guard::If(expr)) => self.visit_expr(expr),
@@ -930,10 +893,12 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> {
                             None => (),
                         }
                         self.visit_expr(body);
-                        self.swap_drop_ranges(old_ranges)
+                        self.expr_count - 1
                     })
                     .collect::<Vec<_>>();
-                arm_drops.into_iter().for_each(|drops| self.merge_drop_ranges(drops));
+                arm_drops.into_iter().for_each(|arm_end| {
+                    self.drop_ranges.add_control_edge(arm_end, self.expr_count)
+                });
             }
             _ => intravisit::walk_expr(self, expr),
         }
@@ -952,160 +917,3 @@ impl<'tcx> Visitor<'tcx> for DropRangeVisitor<'tcx> {
         self.expr_count += 1;
     }
 }
-
-fn trim_drop_ranges(drop_ranges: &HirIdMap<DropRange>, trim_from: usize) -> HirIdMap<DropRange> {
-    drop_ranges.iter().map(|(k, v)| (*k, v.trimmed(trim_from))).collect()
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-enum Event {
-    Drop(usize),
-    Reinit(usize),
-}
-
-impl Event {
-    fn location(&self) -> usize {
-        match *self {
-            Event::Drop(i) | Event::Reinit(i) => i,
-        }
-    }
-}
-
-impl PartialOrd for Event {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        self.location().partial_cmp(&other.location())
-    }
-}
-
-#[derive(Clone, Debug, PartialEq, Eq)]
-struct DropRange {
-    events: Vec<Event>,
-}
-
-impl DropRange {
-    fn empty() -> Self {
-        Self { events: vec![] }
-    }
-
-    fn intersect(&self, other: &Self) -> Self {
-        let mut events = vec![];
-        self.events
-            .iter()
-            .merge_join_by(other.events.iter(), |a, b| a.partial_cmp(b).unwrap())
-            .fold((false, false), |(left, right), event| match event {
-                itertools::EitherOrBoth::Both(_, _) => todo!(),
-                itertools::EitherOrBoth::Left(e) => match e {
-                    Event::Drop(i) => {
-                        if !left && right {
-                            events.push(Event::Drop(*i));
-                        }
-                        (true, right)
-                    }
-                    Event::Reinit(i) => {
-                        if left && !right {
-                            events.push(Event::Reinit(*i));
-                        }
-                        (false, right)
-                    }
-                },
-                itertools::EitherOrBoth::Right(e) => match e {
-                    Event::Drop(i) => {
-                        if left && !right {
-                            events.push(Event::Drop(*i));
-                        }
-                        (left, true)
-                    }
-                    Event::Reinit(i) => {
-                        if !left && right {
-                            events.push(Event::Reinit(*i));
-                        }
-                        (left, false)
-                    }
-                },
-            });
-        Self { events }
-    }
-
-    fn is_dropped_at(&self, id: usize) -> bool {
-        let dropped = match self.events.iter().try_fold(false, |is_dropped, event| {
-            if event.location() <= id {
-                Ok(match event {
-                    Event::Drop(_) => true,
-                    Event::Reinit(_) => false,
-                })
-            } else {
-                Err(is_dropped)
-            }
-        }) {
-            Ok(is_dropped) | Err(is_dropped) => is_dropped,
-        };
-        trace!("is_dropped_at({}): events = {:?}, dropped = {}", id, self.events, dropped);
-        dropped
-    }
-
-    fn drop(&mut self, location: usize) {
-        self.events.push(Event::Drop(location))
-    }
-
-    fn reinit(&mut self, location: usize) {
-        self.events.push(Event::Reinit(location));
-    }
-
-    /// Merges another range with this one. Meant to be used at control flow join points.
-    ///
-    /// After merging, the value will be dead at the end of the range only if it was dead
-    /// at the end of both self and other.
-    #[tracing::instrument]
-    fn merge_with(&mut self, other: &DropRange, join_point: usize) {
-        let join_event = if self.is_dropped_at(join_point) && other.is_dropped_at(join_point) {
-            Event::Drop(join_point)
-        } else {
-            Event::Reinit(join_point)
-        };
-        let events: Vec<_> = self
-            .events
-            .iter()
-            .merge([join_event].iter())
-            .merge(other.events.iter())
-            .dedup()
-            .cloned()
-            .collect();
-
-        trace!("events after merging: {:?}", events);
-
-        self.events = events;
-    }
-
-    /// Creates a new DropRange from this one at the split point.
-    ///
-    /// Used to model branching control flow.
-    fn fork_at(&self, split_point: usize) -> Self {
-        let result = Self {
-            events: vec![if self.is_dropped_at(split_point) {
-                Event::Drop(split_point)
-            } else {
-                Event::Reinit(split_point)
-            }],
-        };
-        trace!("forking at {}: {:?}; result = {:?}", split_point, self.events, result);
-        result
-    }
-
-    fn trimmed(&self, trim_from: usize) -> Self {
-        let start = if self.is_dropped_at(trim_from) {
-            Event::Drop(trim_from)
-        } else {
-            Event::Reinit(trim_from)
-        };
-
-        let result = Self {
-            events: [start]
-                .iter()
-                .chain(self.events.iter().skip_while(|event| event.location() <= trim_from))
-                .cloned()
-                .collect(),
-        };
-        trace!("trimmed {:?} at {}, got {:?}", self, trim_from, result);
-        result
-    }
-}
diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs
new file mode 100644
index 00000000000..dadd9b8d753
--- /dev/null
+++ b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges.rs
@@ -0,0 +1,173 @@
+use rustc_hir::{HirId, HirIdMap};
+use rustc_index::bit_set::BitSet;
+use rustc_index::vec::IndexVec;
+use rustc_middle::hir::map::Map;
+
+use super::for_each_consumable;
+
+rustc_index::newtype_index! {
+    pub struct PostOrderId {
+        DEBUG_FORMAT = "id({})",
+    }
+}
+
+rustc_index::newtype_index! {
+    pub struct HirIdIndex {
+        DEBUG_FORMAT = "hidx({})",
+    }
+}
+
+pub struct DropRanges {
+    hir_id_map: HirIdMap<HirIdIndex>,
+    nodes: IndexVec<PostOrderId, NodeInfo>,
+}
+
+/// DropRanges keeps track of what values are definitely dropped at each point in the code.
+///
+/// Values of interest are defined by the hir_id of their place. Locations in code are identified
+/// by their index in the post-order traversal. At its core, DropRanges maps
+/// (hir_id, post_order_id) -> bool, where a true value indicates that the value is definitely
+/// dropped at the point of the node identified by post_order_id.
+impl DropRanges {
+    pub fn new(hir_ids: impl Iterator<Item = HirId>, hir: &Map<'_>) -> Self {
+        let mut hir_id_map = HirIdMap::<HirIdIndex>::default();
+        let mut next = <_>::from(0u32);
+        for hir_id in hir_ids {
+            for_each_consumable(hir_id, hir.find(hir_id), |hir_id| {
+                if !hir_id_map.contains_key(&hir_id) {
+                    hir_id_map.insert(hir_id, next);
+                    next = <_>::from(next.index() + 1);
+                }
+            });
+        }
+        debug!("hir_id_map: {:?}", hir_id_map);
+        Self { hir_id_map, nodes: <_>::default() }
+    }
+
+    fn hidx(&self, hir_id: HirId) -> HirIdIndex {
+        *self.hir_id_map.get(&hir_id).unwrap()
+    }
+
+    pub fn is_dropped_at(&mut self, hir_id: HirId, location: usize) -> bool {
+        self.hir_id_map
+            .get(&hir_id)
+            .copied()
+            .map_or(false, |hir_id| self.node(location.into()).drop_state.contains(hir_id))
+    }
+
+    /// Returns the number of values (hir_ids) that are tracked
+    fn num_values(&self) -> usize {
+        self.hir_id_map.len()
+    }
+
+    fn node(&mut self, id: PostOrderId) -> &NodeInfo {
+        let size = self.num_values();
+        self.nodes.ensure_contains_elem(id, || NodeInfo::new(size));
+        &self.nodes[id]
+    }
+
+    fn node_mut(&mut self, id: PostOrderId) -> &mut NodeInfo {
+        let size = self.num_values();
+        self.nodes.ensure_contains_elem(id, || NodeInfo::new(size));
+        &mut self.nodes[id]
+    }
+
+    pub fn add_control_edge(&mut self, from: usize, to: usize) {
+        self.node_mut(from.into()).successors.push(to.into());
+    }
+
+    pub fn drop_at(&mut self, value: HirId, location: usize) {
+        let value = self.hidx(value);
+        self.node_mut(location.into()).drops.push(value);
+    }
+
+    pub fn reinit_at(&mut self, value: HirId, location: usize) {
+        let value = match self.hir_id_map.get(&value) {
+            Some(value) => *value,
+            // If there's no value, this is never consumed and therefore is never dropped. We can
+            // ignore this.
+            None => return,
+        };
+        self.node_mut(location.into()).reinits.push(value);
+    }
+
+    pub fn propagate_to_fixpoint(&mut self) {
+        while self.propagate() {}
+    }
+
+    fn propagate(&mut self) -> bool {
+        let mut visited = BitSet::new_empty(self.nodes.len());
+
+        self.visit(&mut visited, PostOrderId::from(0usize), PostOrderId::from(0usize), false)
+    }
+
+    fn visit(
+        &mut self,
+        visited: &mut BitSet<PostOrderId>,
+        id: PostOrderId,
+        pred_id: PostOrderId,
+        mut changed: bool,
+    ) -> bool {
+        if visited.contains(id) {
+            return changed;
+        }
+        visited.insert(id);
+
+        changed &= self.nodes[id].merge_with(&self.nodes[pred_id]);
+
+        if self.nodes[id].successors.len() == 0 {
+            self.visit(visited, PostOrderId::from(id.index() + 1), id, changed)
+        } else {
+            self.nodes[id]
+                .successors
+                .iter()
+                .fold(changed, |changed, &succ| self.visit(visited, succ, id, changed))
+        }
+    }
+}
+
+struct NodeInfo {
+    /// IDs of nodes that can follow this one in the control flow
+    ///
+    /// If the vec is empty, then control proceeds to the next node.
+    successors: Vec<PostOrderId>,
+
+    /// List of hir_ids that are dropped by this node.
+    drops: Vec<HirIdIndex>,
+
+    /// List of hir_ids that are reinitialized by this node.
+    reinits: Vec<HirIdIndex>,
+
+    /// Set of values that are definitely dropped at this point.
+    drop_state: BitSet<HirIdIndex>,
+}
+
+impl NodeInfo {
+    fn new(num_values: usize) -> Self {
+        Self {
+            successors: vec![],
+            drops: vec![],
+            reinits: vec![],
+            drop_state: BitSet::new_empty(num_values),
+        }
+    }
+
+    fn merge_with(&mut self, other: &NodeInfo) -> bool {
+        let mut changed = false;
+        for place in &self.drops {
+            if !self.drop_state.contains(place) && !self.reinits.contains(&place) {
+                changed = true;
+                self.drop_state.insert(place);
+            }
+        }
+
+        for place in &self.reinits {
+            if self.drop_state.contains(place) {
+                changed = true;
+                self.drop_state.remove(place);
+            }
+        }
+
+        changed
+    }
+}