summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:11:38 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:13:23 +0000
commit20431706a863f92cb37dc512fef6e48d192aaf2c (patch)
tree2867f13f5fd5437ba628c67d7f87309ccadcd286 /compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs
parentReleasing progress-linux version 1.65.0+dfsg1-2~progress7.99u1. (diff)
downloadrustc-20431706a863f92cb37dc512fef6e48d192aaf2c.tar.xz
rustc-20431706a863f92cb37dc512fef6e48d192aaf2c.zip
Merging upstream version 1.66.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs')
-rw-r--r--compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs563
1 files changed, 0 insertions, 563 deletions
diff --git a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs b/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs
deleted file mode 100644
index 016f4056b..000000000
--- a/compiler/rustc_typeck/src/check/generator_interior/drop_ranges/cfg_build.rs
+++ /dev/null
@@ -1,563 +0,0 @@
-use super::{
- for_each_consumable, record_consumed_borrow::ConsumedAndBorrowedPlaces, DropRangesBuilder,
- NodeInfo, PostOrderId, TrackedValue, TrackedValueIndex,
-};
-use hir::{
- intravisit::{self, Visitor},
- Body, Expr, ExprKind, Guard, HirId, LoopIdError,
-};
-use rustc_data_structures::fx::{FxHashMap, FxHashSet};
-use rustc_hir as hir;
-use rustc_index::vec::IndexVec;
-use rustc_middle::{
- hir::map::Map,
- ty::{TyCtxt, TypeckResults},
-};
-use std::mem::swap;
-
-/// Traverses the body to find the control flow graph and locations for the
-/// relevant places are dropped or reinitialized.
-///
-/// The resulting structure still needs to be iterated to a fixed point, which
-/// can be done with propagate_to_fixpoint in cfg_propagate.
-pub(super) fn build_control_flow_graph<'tcx>(
- hir: Map<'tcx>,
- tcx: TyCtxt<'tcx>,
- typeck_results: &TypeckResults<'tcx>,
- consumed_borrowed_places: ConsumedAndBorrowedPlaces,
- body: &'tcx Body<'tcx>,
- num_exprs: usize,
-) -> (DropRangesBuilder, FxHashSet<HirId>) {
- let mut drop_range_visitor =
- DropRangeVisitor::new(hir, tcx, typeck_results, consumed_borrowed_places, num_exprs);
- intravisit::walk_body(&mut drop_range_visitor, body);
-
- drop_range_visitor.drop_ranges.process_deferred_edges();
- if let Some(filename) = &tcx.sess.opts.unstable_opts.dump_drop_tracking_cfg {
- super::cfg_visualize::write_graph_to_file(&drop_range_visitor.drop_ranges, filename, tcx);
- }
-
- (drop_range_visitor.drop_ranges, drop_range_visitor.places.borrowed_temporaries)
-}
-
-/// This struct is used to gather the information for `DropRanges` to determine the regions of the
-/// HIR tree for which a value is dropped.
-///
-/// We are interested in points where a variables is dropped or initialized, and the control flow
-/// of the code. We identify locations in code by their post-order traversal index, so it is
-/// important for this traversal to match that in `RegionResolutionVisitor` and `InteriorVisitor`.
-///
-/// We make several simplifying assumptions, with the goal of being more conservative than
-/// necessary rather than less conservative (since being less conservative is unsound, but more
-/// conservative is still safe). These assumptions are:
-///
-/// 1. Moving a variable `a` counts as a move of the whole variable.
-/// 2. Moving a partial path like `a.b.c` is ignored.
-/// 3. Reinitializing through a field (e.g. `a.b.c = 5`) counts as a reinitialization of all of
-/// `a`.
-///
-/// Some examples:
-///
-/// Rule 1:
-/// ```rust
-/// let mut a = (vec![0], vec![0]);
-/// drop(a);
-/// // `a` is not considered initialized.
-/// ```
-///
-/// Rule 2:
-/// ```rust
-/// let mut a = (vec![0], vec![0]);
-/// drop(a.0);
-/// drop(a.1);
-/// // `a` is still considered initialized.
-/// ```
-///
-/// Rule 3:
-/// ```compile_fail,E0382
-/// let mut a = (vec![0], vec![0]);
-/// drop(a);
-/// a.1 = vec![1];
-/// // all of `a` is considered initialized
-/// ```
-
-struct DropRangeVisitor<'a, 'tcx> {
- hir: Map<'tcx>,
- places: ConsumedAndBorrowedPlaces,
- drop_ranges: DropRangesBuilder,
- expr_index: PostOrderId,
- tcx: TyCtxt<'tcx>,
- typeck_results: &'a TypeckResults<'tcx>,
- label_stack: Vec<(Option<rustc_ast::Label>, PostOrderId)>,
-}
-
-impl<'a, 'tcx> DropRangeVisitor<'a, 'tcx> {
- fn new(
- hir: Map<'tcx>,
- tcx: TyCtxt<'tcx>,
- typeck_results: &'a TypeckResults<'tcx>,
- places: ConsumedAndBorrowedPlaces,
- num_exprs: usize,
- ) -> Self {
- debug!("consumed_places: {:?}", places.consumed);
- let drop_ranges = DropRangesBuilder::new(
- places.consumed.iter().flat_map(|(_, places)| places.iter().cloned()),
- hir,
- num_exprs,
- );
- Self {
- hir,
- places,
- drop_ranges,
- expr_index: PostOrderId::from_u32(0),
- typeck_results,
- tcx,
- label_stack: vec![],
- }
- }
-
- fn record_drop(&mut self, value: TrackedValue) {
- if self.places.borrowed.contains(&value) {
- debug!("not marking {:?} as dropped because it is borrowed at some point", value);
- } else {
- debug!("marking {:?} as dropped at {:?}", value, self.expr_index);
- let count = self.expr_index;
- self.drop_ranges.drop_at(value, 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<'_>) {
- debug!("consuming expr {:?}, count={:?}", expr.kind, self.expr_index);
- let places = self
- .places
- .consumed
- .get(&expr.hir_id)
- .map_or(vec![], |places| places.iter().cloned().collect());
- for place in places {
- trace!(?place, "consuming place");
- for_each_consumable(self.hir, place, |value| self.record_drop(value));
- }
- }
-
- /// Marks an expression as being reinitialized.
- ///
- /// Note that we always approximated on the side of things being more
- /// initialized than they actually are, as opposed to less. In cases such
- /// as `x.y = ...`, we would consider all of `x` as being initialized
- /// instead of just the `y` field.
- ///
- /// This is because it is always safe to consider something initialized
- /// even when it is not, but the other way around will cause problems.
- ///
- /// In the future, we will hopefully tighten up these rules to be more
- /// precise.
- fn reinit_expr(&mut self, expr: &hir::Expr<'_>) {
- // Walk the expression to find the base. For example, in an expression
- // like `*a[i].x`, we want to find the `a` and mark that as
- // reinitialized.
- match expr.kind {
- ExprKind::Path(hir::QPath::Resolved(
- _,
- hir::Path { res: hir::def::Res::Local(hir_id), .. },
- )) => {
- // This is the base case, where we have found an actual named variable.
-
- let location = self.expr_index;
- debug!("reinitializing {:?} at {:?}", hir_id, location);
- self.drop_ranges.reinit_at(TrackedValue::Variable(*hir_id), location);
- }
-
- ExprKind::Field(base, _) => self.reinit_expr(base),
-
- // Most expressions do not refer to something where we need to track
- // reinitializations.
- //
- // Some of these may be interesting in the future
- ExprKind::Path(..)
- | ExprKind::Box(..)
- | ExprKind::ConstBlock(..)
- | ExprKind::Array(..)
- | ExprKind::Call(..)
- | ExprKind::MethodCall(..)
- | ExprKind::Tup(..)
- | ExprKind::Binary(..)
- | ExprKind::Unary(..)
- | ExprKind::Lit(..)
- | ExprKind::Cast(..)
- | ExprKind::Type(..)
- | ExprKind::DropTemps(..)
- | ExprKind::Let(..)
- | ExprKind::If(..)
- | ExprKind::Loop(..)
- | ExprKind::Match(..)
- | ExprKind::Closure { .. }
- | ExprKind::Block(..)
- | ExprKind::Assign(..)
- | ExprKind::AssignOp(..)
- | ExprKind::Index(..)
- | ExprKind::AddrOf(..)
- | ExprKind::Break(..)
- | ExprKind::Continue(..)
- | ExprKind::Ret(..)
- | ExprKind::InlineAsm(..)
- | ExprKind::Struct(..)
- | ExprKind::Repeat(..)
- | ExprKind::Yield(..)
- | ExprKind::Err => (),
- }
- }
-
- /// For an expression with an uninhabited return type (e.g. a function that returns !),
- /// this adds a self edge to to the CFG to model the fact that the function does not
- /// return.
- fn handle_uninhabited_return(&mut self, expr: &Expr<'tcx>) {
- let ty = self.typeck_results.expr_ty(expr);
- let ty = self.tcx.erase_regions(ty);
- let m = self.tcx.parent_module(expr.hir_id).to_def_id();
- let param_env = self.tcx.param_env(m.expect_local());
- if self.tcx.is_ty_uninhabited_from(m, ty, param_env) {
- // This function will not return. We model this fact as an infinite loop.
- self.drop_ranges.add_control_edge(self.expr_index + 1, self.expr_index + 1);
- }
- }
-
- /// Map a Destination to an equivalent expression node
- ///
- /// The destination field of a Break or Continue expression can target either an
- /// expression or a block. The drop range analysis, however, only deals in
- /// expression nodes, so blocks that might be the destination of a Break or Continue
- /// will not have a PostOrderId.
- ///
- /// If the destination is an expression, this function will simply return that expression's
- /// hir_id. If the destination is a block, this function will return the hir_id of last
- /// expression in the block.
- fn find_target_expression_from_destination(
- &self,
- destination: hir::Destination,
- ) -> Result<HirId, LoopIdError> {
- destination.target_id.map(|target| {
- let node = self.hir.get(target);
- match node {
- hir::Node::Expr(_) => target,
- hir::Node::Block(b) => find_last_block_expression(b),
- hir::Node::Param(..)
- | hir::Node::Item(..)
- | hir::Node::ForeignItem(..)
- | hir::Node::TraitItem(..)
- | hir::Node::ImplItem(..)
- | hir::Node::Variant(..)
- | hir::Node::Field(..)
- | hir::Node::AnonConst(..)
- | hir::Node::Stmt(..)
- | hir::Node::PathSegment(..)
- | hir::Node::Ty(..)
- | hir::Node::TypeBinding(..)
- | hir::Node::TraitRef(..)
- | hir::Node::Pat(..)
- | hir::Node::PatField(..)
- | hir::Node::ExprField(..)
- | hir::Node::Arm(..)
- | hir::Node::Local(..)
- | hir::Node::Ctor(..)
- | hir::Node::Lifetime(..)
- | hir::Node::GenericParam(..)
- | hir::Node::Crate(..)
- | hir::Node::Infer(..) => bug!("Unsupported branch target: {:?}", node),
- }
- })
- }
-}
-
-fn find_last_block_expression(block: &hir::Block<'_>) -> HirId {
- block.expr.map_or_else(
- // If there is no tail expression, there will be at least one statement in the
- // block because the block contains a break or continue statement.
- || block.stmts.last().unwrap().hir_id,
- |expr| expr.hir_id,
- )
-}
-
-impl<'a, 'tcx> Visitor<'tcx> for DropRangeVisitor<'a, 'tcx> {
- fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
- let mut reinit = None;
- match expr.kind {
- ExprKind::Assign(lhs, rhs, _) => {
- self.visit_expr(lhs);
- self.visit_expr(rhs);
-
- reinit = Some(lhs);
- }
-
- ExprKind::If(test, if_true, if_false) => {
- self.visit_expr(test);
-
- let fork = self.expr_index;
-
- self.drop_ranges.add_control_edge(fork, self.expr_index + 1);
- self.visit_expr(if_true);
- let true_end = self.expr_index;
-
- self.drop_ranges.add_control_edge(fork, self.expr_index + 1);
- if let Some(if_false) = if_false {
- self.visit_expr(if_false);
- }
-
- self.drop_ranges.add_control_edge(true_end, self.expr_index + 1);
- }
- ExprKind::Match(scrutinee, arms, ..) => {
- // We walk through the match expression almost like a chain of if expressions.
- // Here's a diagram to follow along with:
- //
- // ┌─┐
- // match │A│ {
- // ┌───┴─┘
- // │
- // ┌▼┌───►┌─┐ ┌─┐
- // │B│ if │C│ =>│D│,
- // └─┘ ├─┴──►└─┴──────┐
- // ┌──┘ │
- // ┌──┘ │
- // │ │
- // ┌▼┌───►┌─┐ ┌─┐ │
- // │E│ if │F│ =>│G│, │
- // └─┘ ├─┴──►└─┴┐ │
- // │ │ │
- // } ▼ ▼ │
- // ┌─┐◄───────────────────┘
- // │H│
- // └─┘
- //
- // The order we want is that the scrutinee (A) flows into the first pattern (B),
- // which flows into the guard (C). Then the guard either flows into the arm body
- // (D) or into the start of the next arm (E). Finally, the body flows to the end
- // of the match block (H).
- //
- // The subsequent arms follow the same ordering. First we go to the pattern, then
- // the guard (if present, otherwise it flows straight into the body), then into
- // the body and then to the end of the match expression.
- //
- // The comments below show which edge is being added.
- self.visit_expr(scrutinee);
-
- let (guard_exit, arm_end_ids) = arms.iter().fold(
- (self.expr_index, vec![]),
- |(incoming_edge, mut arm_end_ids), hir::Arm { pat, body, guard, .. }| {
- // A -> B, or C -> E
- self.drop_ranges.add_control_edge(incoming_edge, self.expr_index + 1);
- self.visit_pat(pat);
- // B -> C and E -> F are added implicitly due to the traversal order.
- match guard {
- Some(Guard::If(expr)) => self.visit_expr(expr),
- Some(Guard::IfLet(let_expr)) => {
- self.visit_let_expr(let_expr);
- }
- None => (),
- }
- // Likewise, C -> D and F -> G are added implicitly.
-
- // Save C, F, so we can add the other outgoing edge.
- let to_next_arm = self.expr_index;
-
- // The default edge does not get added since we also have an explicit edge,
- // so we also need to add an edge to the next node as well.
- //
- // This adds C -> D, F -> G
- self.drop_ranges.add_control_edge(self.expr_index, self.expr_index + 1);
- self.visit_expr(body);
-
- // Save the end of the body so we can add the exit edge once we know where
- // the exit is.
- arm_end_ids.push(self.expr_index);
-
- // Pass C to the next iteration, as well as vec![D]
- //
- // On the last round through, we pass F and vec![D, G] so that we can
- // add all the exit edges.
- (to_next_arm, arm_end_ids)
- },
- );
- // F -> H
- self.drop_ranges.add_control_edge(guard_exit, self.expr_index + 1);
-
- arm_end_ids.into_iter().for_each(|arm_end| {
- // D -> H, G -> H
- self.drop_ranges.add_control_edge(arm_end, self.expr_index + 1)
- });
- }
-
- ExprKind::Loop(body, label, ..) => {
- let loop_begin = self.expr_index + 1;
- self.label_stack.push((label, loop_begin));
- if body.stmts.is_empty() && body.expr.is_none() {
- // For empty loops we won't have updated self.expr_index after visiting the
- // body, meaning we'd get an edge from expr_index to expr_index + 1, but
- // instead we want an edge from expr_index + 1 to expr_index + 1.
- self.drop_ranges.add_control_edge(loop_begin, loop_begin);
- } else {
- self.visit_block(body);
- self.drop_ranges.add_control_edge(self.expr_index, loop_begin);
- }
- self.label_stack.pop();
- }
- // Find the loop entry by searching through the label stack for either the last entry
- // (if label is none), or the first entry where the label matches this one. The Loop
- // case maintains this stack mapping labels to the PostOrderId for the loop entry.
- ExprKind::Continue(hir::Destination { label, .. }, ..) => self
- .label_stack
- .iter()
- .rev()
- .find(|(loop_label, _)| label.is_none() || *loop_label == label)
- .map_or((), |(_, target)| {
- self.drop_ranges.add_control_edge(self.expr_index, *target)
- }),
-
- ExprKind::Break(destination, ..) => {
- // destination either points to an expression or to a block. We use
- // find_target_expression_from_destination to use the last expression of the block
- // if destination points to a block.
- //
- // We add an edge to the hir_id of the expression/block we are breaking out of, and
- // then in process_deferred_edges we will map this hir_id to its PostOrderId, which
- // will refer to the end of the block due to the post order traversal.
- self.find_target_expression_from_destination(destination).map_or((), |target| {
- self.drop_ranges.add_control_edge_hir_id(self.expr_index, target)
- })
- }
-
- ExprKind::Call(f, args) => {
- self.visit_expr(f);
- for arg in args {
- self.visit_expr(arg);
- }
-
- self.handle_uninhabited_return(expr);
- }
- ExprKind::MethodCall(_, receiver, exprs, _) => {
- self.visit_expr(receiver);
- for expr in exprs {
- self.visit_expr(expr);
- }
-
- self.handle_uninhabited_return(expr);
- }
-
- ExprKind::AddrOf(..)
- | ExprKind::Array(..)
- | ExprKind::AssignOp(..)
- | ExprKind::Binary(..)
- | ExprKind::Block(..)
- | ExprKind::Box(..)
- | ExprKind::Cast(..)
- | ExprKind::Closure { .. }
- | ExprKind::ConstBlock(..)
- | ExprKind::DropTemps(..)
- | ExprKind::Err
- | ExprKind::Field(..)
- | ExprKind::Index(..)
- | ExprKind::InlineAsm(..)
- | ExprKind::Let(..)
- | ExprKind::Lit(..)
- | ExprKind::Path(..)
- | ExprKind::Repeat(..)
- | ExprKind::Ret(..)
- | ExprKind::Struct(..)
- | ExprKind::Tup(..)
- | ExprKind::Type(..)
- | ExprKind::Unary(..)
- | ExprKind::Yield(..) => intravisit::walk_expr(self, expr),
- }
-
- self.expr_index = self.expr_index + 1;
- self.drop_ranges.add_node_mapping(expr.hir_id, self.expr_index);
- self.consume_expr(expr);
- if let Some(expr) = reinit {
- self.reinit_expr(expr);
- }
- }
-
- fn visit_pat(&mut self, pat: &'tcx hir::Pat<'tcx>) {
- intravisit::walk_pat(self, pat);
-
- // Increment expr_count here to match what InteriorVisitor expects.
- self.expr_index = self.expr_index + 1;
- }
-}
-
-impl DropRangesBuilder {
- fn new(
- tracked_values: impl Iterator<Item = TrackedValue>,
- hir: Map<'_>,
- num_exprs: usize,
- ) -> Self {
- let mut tracked_value_map = FxHashMap::<_, TrackedValueIndex>::default();
- let mut next = <_>::from(0u32);
- for value in tracked_values {
- for_each_consumable(hir, value, |value| {
- if !tracked_value_map.contains_key(&value) {
- tracked_value_map.insert(value, next);
- next = next + 1;
- }
- });
- }
- debug!("hir_id_map: {:?}", tracked_value_map);
- let num_values = tracked_value_map.len();
- Self {
- tracked_value_map,
- nodes: IndexVec::from_fn_n(|_| NodeInfo::new(num_values), num_exprs + 1),
- deferred_edges: <_>::default(),
- post_order_map: <_>::default(),
- }
- }
-
- fn tracked_value_index(&self, tracked_value: TrackedValue) -> TrackedValueIndex {
- *self.tracked_value_map.get(&tracked_value).unwrap()
- }
-
- /// Adds an entry in the mapping from HirIds to PostOrderIds
- ///
- /// Needed so that `add_control_edge_hir_id` can work.
- fn add_node_mapping(&mut self, node_hir_id: HirId, post_order_id: PostOrderId) {
- self.post_order_map.insert(node_hir_id, post_order_id);
- }
-
- /// Like add_control_edge, but uses a hir_id as the target.
- ///
- /// This can be used for branches where we do not know the PostOrderId of the target yet,
- /// such as when handling `break` or `continue`.
- fn add_control_edge_hir_id(&mut self, from: PostOrderId, to: HirId) {
- self.deferred_edges.push((from, to));
- }
-
- fn drop_at(&mut self, value: TrackedValue, location: PostOrderId) {
- let value = self.tracked_value_index(value);
- self.node_mut(location).drops.push(value);
- }
-
- fn reinit_at(&mut self, value: TrackedValue, location: PostOrderId) {
- let value = match self.tracked_value_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).reinits.push(value);
- }
-
- /// Looks up PostOrderId for any control edges added by HirId and adds a proper edge for them.
- ///
- /// Should be called after visiting the HIR but before solving the control flow, otherwise some
- /// edges will be missed.
- fn process_deferred_edges(&mut self) {
- trace!("processing deferred edges. post_order_map={:#?}", self.post_order_map);
- let mut edges = vec![];
- swap(&mut edges, &mut self.deferred_edges);
- edges.into_iter().for_each(|(from, to)| {
- trace!("Adding deferred edge from {:?} to {:?}", from, to);
- let to = *self.post_order_map.get(&to).expect("Expression ID not found");
- trace!("target edge PostOrderId={:?}", to);
- self.add_control_edge(from, to)
- });
- }
-}