Skip to main content

rustc_borrowck/polonius/
constraints.rs

1use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2use rustc_index::interval::SparseIntervalMatrix;
3use rustc_middle::mir::{Body, Location};
4use rustc_middle::ty::RegionVid;
5use rustc_mir_dataflow::points::PointIndex;
6
7use crate::BorrowSet;
8use crate::constraints::OutlivesConstraint;
9use crate::dataflow::BorrowIndex;
10use crate::polonius::{ConstraintDirection, LiveRegionVariances};
11use crate::region_infer::values::LivenessValues;
12use crate::type_check::Locations;
13use crate::universal_regions::UniversalRegions;
14
15/// A localized outlives constraint reifies the CFG location where the outlives constraint holds,
16/// within the origins themselves as if they were different from point to point: from `a: b`
17/// outlives constraints to `a@p: b@p`, where `p` is the point in the CFG.
18///
19/// This models two sources of constraints:
20/// - constraints that traverse the subsets between regions at a given point, `a@p: b@p`. These
21///   depend on typeck constraints generated via assignments, calls, etc.
22/// - constraints that traverse the CFG via the same region, `a@p: a@q`, where `p` is a predecessor
23///   of `q`. These depend on the liveness of the regions at these points, as well as their
24///   variance.
25///
26/// This dual of NLL's [crate::constraints::OutlivesConstraint] therefore encodes the
27/// position-dependent outlives constraints used by Polonius, to model the flow-sensitive loan
28/// propagation via reachability within a graph of localized constraints.
29///
30/// That `LocalizedConstraintGraph` can create these edges on-demand during traversal, and we
31/// therefore model them as a pair of `LocalizedNode` vertices.
32///
33#[derive(#[automatically_derived]
impl ::core::marker::Copy for LocalizedNode { }Copy, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LocalizedNode { }
#[automatically_derived]
impl ::core::clone::Clone for LocalizedNode {
    #[inline]
    fn clone(&self) -> LocalizedNode {
        let _: ::core::clone::AssertParamIsClone<RegionVid>;
        let _: ::core::clone::AssertParamIsClone<PointIndex>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for LocalizedNode { }
#[automatically_derived]
impl ::core::cmp::PartialEq for LocalizedNode {
    #[inline]
    fn eq(&self, other: &LocalizedNode) -> bool {
        self.region == other.region && self.point == other.point
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for LocalizedNode {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<RegionVid>;
        let _: ::core::cmp::AssertParamIsEq<PointIndex>;
    }
}Eq, #[automatically_derived]
impl ::core::hash::Hash for LocalizedNode {
    #[inline]
    fn hash<__H: ::core::hash::Hasher>(&self, state: &mut __H) {
        ::core::hash::Hash::hash(&self.region, state);
        ::core::hash::Hash::hash(&self.point, state)
    }
}Hash)]
34pub(super) struct LocalizedNode {
35    pub region: RegionVid,
36    pub point: PointIndex,
37}
38
39/// The localized constraint graph indexes the physical and logical edges to lazily compute a given
40/// node's successors during traversal.
41pub(super) struct LocalizedConstraintGraph {
42    /// The actual, physical, edges we have recorded for a given node. We localize them on-demand
43    /// when traversing from the node to the successor region.
44    edges: FxHashMap<LocalizedNode, FxIndexSet<RegionVid>>,
45
46    /// The logical edges representing the outlives constraints that hold at all points in the CFG,
47    /// which we don't localize to avoid creating a lot of unnecessary edges in the graph. Some CFGs
48    /// can be big, and we don't need to create such a physical edge for every point in the CFG.
49    logical_edges: FxHashMap<RegionVid, FxIndexSet<RegionVid>>,
50}
51
52/// The visitor interface when traversing a `LocalizedConstraintGraph`.
53pub(super) trait LocalizedConstraintGraphVisitor {
54    /// Callback called when traversing a given `loan` encounters a localized `node` it hasn't
55    /// visited before.
56    fn on_node_traversed(&mut self, _loan: BorrowIndex, _node: LocalizedNode) {}
57
58    /// Callback called when discovering a new `successor` node for the `current_node`.
59    fn on_successor_discovered(&mut self, _current_node: LocalizedNode, _successor: LocalizedNode) {
60    }
61}
62
63impl LocalizedConstraintGraph {
64    /// Traverses the constraints and returns the indexed graph of edges per node.
65    pub(super) fn new<'tcx>(
66        liveness: &LivenessValues,
67        outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
68    ) -> Self {
69        let mut edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default();
70        let mut logical_edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default();
71
72        for outlives_constraint in outlives_constraints {
73            match outlives_constraint.locations {
74                Locations::All(_) => {
75                    logical_edges
76                        .entry(outlives_constraint.sup)
77                        .or_default()
78                        .insert(outlives_constraint.sub);
79                }
80
81                Locations::Single(location) => {
82                    let node = LocalizedNode {
83                        region: outlives_constraint.sup,
84                        point: liveness.point_from_location(location),
85                    };
86                    edges.entry(node).or_default().insert(outlives_constraint.sub);
87                }
88            }
89        }
90
91        LocalizedConstraintGraph { edges, logical_edges }
92    }
93
94    /// Traverses the localized constraint graph per-loan, and notifies the `visitor` of discovered
95    /// nodes and successors.
96    pub(super) fn traverse<'tcx>(
97        &self,
98        body: &Body<'tcx>,
99        liveness: &LivenessValues,
100        live_region_variances: &LiveRegionVariances,
101        universal_regions: &UniversalRegions<'tcx>,
102        borrow_set: &BorrowSet<'tcx>,
103        visitor: &mut impl LocalizedConstraintGraphVisitor,
104    ) {
105        let live_regions = liveness.points();
106
107        let mut visited = FxHashSet::default();
108        let mut stack = Vec::new();
109
110        // Compute reachability per loan by traversing each loan's subgraph starting from where it
111        // is introduced.
112        for (loan_idx, loan) in borrow_set.iter_enumerated() {
113            visited.clear();
114            stack.clear();
115
116            let start_node = LocalizedNode {
117                region: loan.region,
118                point: liveness.point_from_location(loan.reserve_location),
119            };
120            stack.push(start_node);
121
122            while let Some(node) = stack.pop() {
123                if !visited.insert(node) {
124                    continue;
125                }
126
127                // We've reached a node we haven't visited before.
128                let location = liveness.location_from_point(node.point);
129                visitor.on_node_traversed(loan_idx, node);
130
131                // When we find a _new_ successor, we'd like to
132                // - visit it eventually,
133                // - and let the generic visitor know about it.
134                let mut successor_found = |succ| {
135                    if !visited.contains(&succ) {
136                        stack.push(succ);
137                        visitor.on_successor_discovered(node, succ);
138                    }
139                };
140
141                // Then, we propagate the loan along the localized constraint graph. The outgoing
142                // edges are computed lazily, from:
143                // - the various physical edges present at this node,
144                // - the materialized logical edges that exist virtually at all points for this
145                //   node's region, localized at this point.
146
147                // Universal regions propagate loans along the CFG, i.e. forwards only.
148                let is_universal_region = universal_regions.is_universal_region(node.region);
149
150                // The physical edges present at this node are:
151                //
152                // 1. the typeck edges that flow from region to region *at this point*.
153                for &succ in self.edges.get(&node).into_flat_iter() {
154                    let succ = LocalizedNode { region: succ, point: node.point };
155                    successor_found(succ);
156                }
157
158                // 2a. the liveness edges that flow *forward*, from this node's point to its
159                // successors in the CFG.
160                if body[location.block].statements.get(location.statement_index).is_some() {
161                    // Intra-block edges, straight line constraints from each point to its successor
162                    // within the same block.
163                    let next_point = node.point + 1;
164                    if let Some(succ) = compute_forward_successor(
165                        node.region,
166                        next_point,
167                        live_regions,
168                        live_region_variances,
169                        is_universal_region,
170                    ) {
171                        successor_found(succ);
172                    }
173                } else {
174                    // Inter-block edges, from the block's terminator to each successor block's
175                    // entry point.
176                    for successor_block in body[location.block].terminator().successors() {
177                        let next_location = Location { block: successor_block, statement_index: 0 };
178                        let next_point = liveness.point_from_location(next_location);
179                        if let Some(succ) = compute_forward_successor(
180                            node.region,
181                            next_point,
182                            live_regions,
183                            live_region_variances,
184                            is_universal_region,
185                        ) {
186                            successor_found(succ);
187                        }
188                    }
189                }
190
191                // 2b. the liveness edges that flow *backward*, from this node's point to its
192                // predecessors in the CFG.
193                if !is_universal_region {
194                    if location.statement_index > 0 {
195                        // Backward edges to the predecessor point in the same block.
196                        let previous_point = PointIndex::from(node.point.as_usize() - 1);
197                        if let Some(succ) = compute_backward_successor(
198                            node.region,
199                            node.point,
200                            previous_point,
201                            live_regions,
202                            live_region_variances,
203                        ) {
204                            successor_found(succ);
205                        }
206                    } else {
207                        // Backward edges from the block entry point to the terminator of the
208                        // predecessor blocks.
209                        let predecessors = body.basic_blocks.predecessors();
210                        for &pred_block in &predecessors[location.block] {
211                            let previous_location = Location {
212                                block: pred_block,
213                                statement_index: body[pred_block].statements.len(),
214                            };
215                            let previous_point = liveness.point_from_location(previous_location);
216                            if let Some(succ) = compute_backward_successor(
217                                node.region,
218                                node.point,
219                                previous_point,
220                                live_regions,
221                                live_region_variances,
222                            ) {
223                                successor_found(succ);
224                            }
225                        }
226                    }
227                }
228
229                // And finally, we have the logical edges, materialized at this point.
230                for &logical_succ in self.logical_edges.get(&node.region).into_flat_iter() {
231                    let succ = LocalizedNode { region: logical_succ, point: node.point };
232                    successor_found(succ);
233                }
234            }
235        }
236    }
237}
238
239/// Returns the successor for the current region/point node when propagating a loan through forward
240/// edges, if applicable, according to liveness and variance.
241fn compute_forward_successor(
242    region: RegionVid,
243    next_point: PointIndex,
244    live_regions: &SparseIntervalMatrix<RegionVid, PointIndex>,
245    live_region_variances: &LiveRegionVariances,
246    is_universal_region: bool,
247) -> Option<LocalizedNode> {
248    // 1. Universal regions are semantically live at all points.
249    if is_universal_region {
250        let succ = LocalizedNode { region, point: next_point };
251        return Some(succ);
252    }
253
254    // 2. Otherwise, gather the edges due to explicit region liveness, when applicable.
255    if !live_regions.contains(region, next_point) {
256        return None;
257    }
258
259    // Here, `region` could be live at the current point, and is live at the next point: add a
260    // constraint between them, according to variance.
261
262    // Note: there currently are cases related to promoted and const generics, where we don't yet
263    // have variance information (possibly about temporary regions created when typeck sanitizes the
264    // promoteds). Until that is done, we conservatively fallback to maximizing reachability by
265    // adding a bidirectional edge here. This will not limit traversal whatsoever, and thus
266    // propagate liveness when needed.
267    //
268    // FIXME: add the missing variance information and remove this fallback bidirectional edge.
269    let direction = live_region_variances
270        .get(region)
271        .copied()
272        .flatten()
273        .unwrap_or(ConstraintDirection::Bidirectional);
274
275    match direction {
276        ConstraintDirection::Backward => {
277            // Contravariant cases: loans flow in the inverse direction, but we're only interested
278            // in forward successors and there are none here.
279            None
280        }
281        ConstraintDirection::Forward | ConstraintDirection::Bidirectional => {
282            // 1. For covariant cases: loans flow in the regular direction, from the current point
283            // to the next point.
284            // 2. For invariant cases, loans can flow in both directions, but here as well, we only
285            // want the forward path of the bidirectional edge.
286            Some(LocalizedNode { region, point: next_point })
287        }
288    }
289}
290
291/// Returns the successor for the current region/point node when propagating a loan through backward
292/// edges, if applicable, according to liveness and variance.
293fn compute_backward_successor(
294    region: RegionVid,
295    current_point: PointIndex,
296    previous_point: PointIndex,
297    live_regions: &SparseIntervalMatrix<RegionVid, PointIndex>,
298    live_region_variances: &LiveRegionVariances,
299) -> Option<LocalizedNode> {
300    // Liveness flows into the regions live at the next point. So, in a backwards view, we'll link
301    // the region from the current point, if it's live there, to the previous point.
302    if !live_regions.contains(region, current_point) {
303        return None;
304    }
305
306    // FIXME: add the missing variance information and remove this fallback bidirectional edge. See
307    // the same comment in `compute_forward_successor`.
308    let direction = live_region_variances
309        .get(region)
310        .copied()
311        .flatten()
312        .unwrap_or(ConstraintDirection::Bidirectional);
313
314    match direction {
315        ConstraintDirection::Forward => {
316            // Covariant cases: loans flow in the regular direction, but we're only interested in
317            // backward successors and there are none here.
318            None
319        }
320        ConstraintDirection::Backward | ConstraintDirection::Bidirectional => {
321            // 1. For contravariant cases: loans flow in the inverse direction, from the current
322            // point to the previous point.
323            // 2. For invariant cases, loans can flow in both directions, but here as well, we only
324            // want the backward path of the bidirectional edge.
325            Some(LocalizedNode { region, point: previous_point })
326        }
327    }
328}