Skip to main content

rustc_borrowck/polonius/
constraints.rs

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