summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_hir_typeck/src/generator_interior/drop_ranges/mod.rs
blob: 2abcadcc9ce7de4f6335daf6c9febc0ca8d3a6c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
//! Drop range analysis finds the portions of the tree where a value is guaranteed to be dropped
//! (i.e. moved, uninitialized, etc.). This is used to exclude the types of those values from the
//! generator type. See `InteriorVisitor::record` for where the results of this analysis are used.
//!
//! There are three phases to this analysis:
//! 1. Use `ExprUseVisitor` to identify the interesting values that are consumed and borrowed.
//! 2. Use `DropRangeVisitor` to find where the interesting values are dropped or reinitialized,
//!    and also build a control flow graph.
//! 3. Use `DropRanges::propagate_to_fixpoint` to flow the dropped/reinitialized information through
//!    the CFG and find the exact points where we know a value is definitely dropped.
//!
//! The end result is a data structure that maps the post-order index of each node in the HIR tree
//! to a set of values that are known to be dropped at that location.

use self::cfg_build::build_control_flow_graph;
use self::record_consumed_borrow::find_consumed_and_borrowed;
use crate::FnCtxt;
use hir::def_id::DefId;
use hir::{Body, HirId, HirIdMap, Node};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_hir as hir;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::IndexVec;
use rustc_middle::hir::map::Map;
use rustc_middle::hir::place::{PlaceBase, PlaceWithHirId};
use rustc_middle::ty;
use std::collections::BTreeMap;
use std::fmt::Debug;

mod cfg_build;
mod cfg_propagate;
mod cfg_visualize;
mod record_consumed_borrow;

pub fn compute_drop_ranges<'a, 'tcx>(
    fcx: &'a FnCtxt<'a, 'tcx>,
    def_id: DefId,
    body: &'tcx Body<'tcx>,
) -> DropRanges {
    if fcx.sess().opts.unstable_opts.drop_tracking {
        let consumed_borrowed_places = find_consumed_and_borrowed(fcx, def_id, body);

        let typeck_results = &fcx.typeck_results.borrow();
        let num_exprs = fcx.tcx.region_scope_tree(def_id).body_expr_count(body.id()).unwrap_or(0);
        let (mut drop_ranges, borrowed_temporaries) = build_control_flow_graph(
            &fcx,
            typeck_results,
            fcx.param_env,
            consumed_borrowed_places,
            body,
            num_exprs,
        );

        drop_ranges.propagate_to_fixpoint();

        debug!("borrowed_temporaries = {borrowed_temporaries:?}");
        DropRanges {
            tracked_value_map: drop_ranges.tracked_value_map,
            nodes: drop_ranges.nodes,
            borrowed_temporaries: Some(borrowed_temporaries),
        }
    } else {
        // If drop range tracking is not enabled, skip all the analysis and produce an
        // empty set of DropRanges.
        DropRanges {
            tracked_value_map: FxHashMap::default(),
            nodes: IndexVec::new(),
            borrowed_temporaries: None,
        }
    }
}

/// Applies `f` to consumable node in the HIR subtree pointed to by `place`.
///
/// This includes the place itself, and if the place is a reference to a local
/// variable then `f` is also called on the HIR node for that variable as well.
///
/// For example, if `place` points to `foo()`, then `f` is called once for the
/// result of `foo`. On the other hand, if `place` points to `x` then `f` will
/// be called both on the `ExprKind::Path` node that represents the expression
/// as well as the HirId of the local `x` itself.
fn for_each_consumable<'tcx>(hir: Map<'tcx>, place: TrackedValue, mut f: impl FnMut(TrackedValue)) {
    f(place);
    let node = hir.find(place.hir_id());
    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(TrackedValue::Variable(*hir_id));
            }
            _ => (),
        }
    }
}

rustc_index::newtype_index! {
    pub struct PostOrderId {
        DEBUG_FORMAT = "id({})",
    }
}

rustc_index::newtype_index! {
    pub struct TrackedValueIndex {
        DEBUG_FORMAT = "hidx({})",
    }
}

/// Identifies a value whose drop state we need to track.
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
enum TrackedValue {
    /// Represents a named variable, such as a let binding, parameter, or upvar.
    ///
    /// The HirId points to the variable's definition site.
    Variable(HirId),
    /// A value produced as a result of an expression.
    ///
    /// The HirId points to the expression that returns this value.
    Temporary(HirId),
}

impl Debug for TrackedValue {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        ty::tls::with_opt(|opt_tcx| {
            if let Some(tcx) = opt_tcx {
                write!(f, "{}", tcx.hir().node_to_string(self.hir_id()))
            } else {
                match self {
                    Self::Variable(hir_id) => write!(f, "Variable({:?})", hir_id),
                    Self::Temporary(hir_id) => write!(f, "Temporary({:?})", hir_id),
                }
            }
        })
    }
}

impl TrackedValue {
    fn hir_id(&self) -> HirId {
        match self {
            TrackedValue::Variable(hir_id) | TrackedValue::Temporary(hir_id) => *hir_id,
        }
    }

    fn from_place_with_projections_allowed(place_with_id: &PlaceWithHirId<'_>) -> Self {
        match place_with_id.place.base {
            PlaceBase::Rvalue | PlaceBase::StaticItem => {
                TrackedValue::Temporary(place_with_id.hir_id)
            }
            PlaceBase::Local(hir_id)
            | PlaceBase::Upvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id }, .. }) => {
                TrackedValue::Variable(hir_id)
            }
        }
    }
}

/// Represents a reason why we might not be able to convert a HirId or Place
/// into a tracked value.
#[derive(Debug)]
enum TrackedValueConversionError {
    /// Place projects are not currently supported.
    ///
    /// The reasoning around these is kind of subtle, so we choose to be more
    /// conservative around these for now. There is no reason in theory we
    /// cannot support these, we just have not implemented it yet.
    PlaceProjectionsNotSupported,
}

impl TryFrom<&PlaceWithHirId<'_>> for TrackedValue {
    type Error = TrackedValueConversionError;

    fn try_from(place_with_id: &PlaceWithHirId<'_>) -> Result<Self, Self::Error> {
        if !place_with_id.place.projections.is_empty() {
            debug!(
                "TrackedValue from PlaceWithHirId: {:?} has projections, which are not supported.",
                place_with_id
            );
            return Err(TrackedValueConversionError::PlaceProjectionsNotSupported);
        }

        Ok(TrackedValue::from_place_with_projections_allowed(place_with_id))
    }
}

pub struct DropRanges {
    tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>,
    nodes: IndexVec<PostOrderId, NodeInfo>,
    borrowed_temporaries: Option<FxHashSet<HirId>>,
}

impl DropRanges {
    pub fn is_dropped_at(&self, hir_id: HirId, location: usize) -> bool {
        self.tracked_value_map
            .get(&TrackedValue::Temporary(hir_id))
            .or(self.tracked_value_map.get(&TrackedValue::Variable(hir_id)))
            .cloned()
            .map_or(false, |tracked_value_id| {
                self.expect_node(location.into()).drop_state.contains(tracked_value_id)
            })
    }

    pub fn is_borrowed_temporary(&self, expr: &hir::Expr<'_>) -> bool {
        if let Some(b) = &self.borrowed_temporaries { b.contains(&expr.hir_id) } else { true }
    }

    /// Returns a reference to the NodeInfo for a node, panicking if it does not exist
    fn expect_node(&self, id: PostOrderId) -> &NodeInfo {
        &self.nodes[id]
    }
}

/// Tracks information needed to compute drop ranges.
struct DropRangesBuilder {
    /// The core of DropRangesBuilder is a set of nodes, which each represent
    /// one expression. We primarily refer to them by their index in a
    /// post-order traversal of the HIR tree,  since this is what
    /// generator_interior uses to talk about yield positions.
    ///
    /// This IndexVec keeps the relevant details for each node. See the
    /// NodeInfo struct for more details, but this information includes things
    /// such as the set of control-flow successors, which variables are dropped
    /// or reinitialized, and whether each variable has been inferred to be
    /// known-dropped or potentially reinitialized at each point.
    nodes: IndexVec<PostOrderId, NodeInfo>,
    /// We refer to values whose drop state we are tracking by the HirId of
    /// where they are defined. Within a NodeInfo, however, we store the
    /// drop-state in a bit vector indexed by a HirIdIndex
    /// (see NodeInfo::drop_state). The hir_id_map field stores the mapping
    /// from HirIds to the HirIdIndex that is used to represent that value in
    /// bitvector.
    tracked_value_map: FxHashMap<TrackedValue, TrackedValueIndex>,

    /// When building the control flow graph, we don't always know the
    /// post-order index of the target node at the point we encounter it.
    /// For example, this happens with break and continue. In those cases,
    /// we store a pair of the PostOrderId of the source and the HirId
    /// of the target. Once we have gathered all of these edges, we make a
    /// pass over the set of deferred edges (see process_deferred_edges in
    /// cfg_build.rs), look up the PostOrderId for the target (since now the
    /// post-order index for all nodes is known), and add missing control flow
    /// edges.
    deferred_edges: Vec<(PostOrderId, HirId)>,
    /// This maps HirIds of expressions to their post-order index. It is
    /// used in process_deferred_edges to correctly add back-edges.
    post_order_map: HirIdMap<PostOrderId>,
}

impl Debug for DropRangesBuilder {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("DropRanges")
            .field("hir_id_map", &self.tracked_value_map)
            .field("post_order_maps", &self.post_order_map)
            .field("nodes", &self.nodes.iter_enumerated().collect::<BTreeMap<_, _>>())
            .finish()
    }
}

/// 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 DropRangesBuilder {
    /// Returns the number of values (hir_ids) that are tracked
    fn num_values(&self) -> usize {
        self.tracked_value_map.len()
    }

    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]
    }

    fn add_control_edge(&mut self, from: PostOrderId, to: PostOrderId) {
        trace!("adding control edge from {:?} to {:?}", from, to);
        self.node_mut(from).successors.push(to);
    }
}

#[derive(Debug)]
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<TrackedValueIndex>,

    /// List of hir_ids that are reinitialized by this node.
    reinits: Vec<TrackedValueIndex>,

    /// Set of values that are definitely dropped at this point.
    drop_state: BitSet<TrackedValueIndex>,
}

impl NodeInfo {
    fn new(num_values: usize) -> Self {
        Self {
            successors: vec![],
            drops: vec![],
            reinits: vec![],
            drop_state: BitSet::new_filled(num_values),
        }
    }
}