Skip to main content

rustc_borrowck/polonius/
constraints.rs

1use std::rc::Rc;
2
3use rustc_data_structures::fx::{FxHashMap, FxHashSet};
4use rustc_index::IndexVec;
5use rustc_middle::mir::{Body, Location};
6use rustc_middle::ty::RegionVid;
7use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex};
8use smallvec::SmallVec;
9
10use crate::BorrowSet;
11use crate::constraints::OutlivesConstraint;
12use crate::dataflow::BorrowIndex;
13use crate::polonius::ConstraintDirection;
14use crate::polonius::liveness::LivenessSource;
15use crate::type_check::Locations;
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]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for LocalizedNode { }
#[automatically_derived]
impl ::core::clone::Clone for LocalizedNode {
    #[inline]
    fn clone(&self) -> Self {
        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: &Self) -> 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)]
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    location_map: Rc<DenseLocationMap>,
45
46    /// The actual, physical, edges we have recorded for a given node. We localize them on-demand
47    /// when traversing from the node to the successor region.
48    edges: FxHashMap<LocalizedNode, SmallVec<[RegionVid; 4]>>,
49
50    /// The logical edges representing the outlives constraints that hold at all points in the CFG,
51    /// which we don't localize to avoid creating a lot of unnecessary edges in the graph. Some CFGs
52    /// can be big, and we don't need to create such a physical edge for every point in the CFG.
53    logical_edges: IndexVec<RegionVid, SmallVec<[RegionVid; 4]>>,
54}
55
56/// The visitor interface when traversing a `LocalizedConstraintGraph`.
57pub(super) trait LocalizedConstraintGraphVisitor {
58    /// Callback called when traversing a given `loan` encounters a localized `node` it hasn't
59    /// visited before, and whether its region is live at that point.
60    fn on_node_traversed(&mut self, _loan: BorrowIndex, _node: LocalizedNode, _is_live: bool) {}
61
62    /// Callback called when discovering a new `successor` node for the `current_node`.
63    fn on_successor_discovered(&mut self, _current_node: LocalizedNode, _successor: LocalizedNode) {
64    }
65}
66
67impl LocalizedConstraintGraph {
68    /// Traverses the constraints and returns the indexed graph of edges per node.
69    pub(super) fn new<'tcx>(
70        location_map: Rc<DenseLocationMap>,
71        outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
72    ) -> Self {
73        let mut edges: FxHashMap<_, SmallVec<[RegionVid; 4]>> = FxHashMap::default();
74        let mut logical_edges: IndexVec<_, SmallVec<[RegionVid; 4]>> = IndexVec::new();
75
76        for outlives_constraint in outlives_constraints {
77            match outlives_constraint.locations {
78                Locations::All(_) => {
79                    let succs =
80                        logical_edges.ensure_contains_elem(outlives_constraint.sup, SmallVec::new);
81                    if !succs.contains(&outlives_constraint.sub) {
82                        succs.push(outlives_constraint.sub);
83                    }
84                }
85
86                Locations::Single(location) => {
87                    let node = LocalizedNode {
88                        region: outlives_constraint.sup,
89                        point: location_map.point_from_location(location),
90                    };
91                    let succs = edges.entry(node).or_default();
92                    if !succs.contains(&outlives_constraint.sub) {
93                        succs.push(outlives_constraint.sub);
94                    }
95                }
96            }
97        }
98
99        LocalizedConstraintGraph { location_map, edges, logical_edges }
100    }
101
102    /// Traverses the localized constraint graph per-loan, and notifies the `visitor` of discovered
103    /// nodes and successors.
104    pub(super) fn traverse<'tcx>(
105        &self,
106        body: &Body<'tcx>,
107        borrow_set: &BorrowSet<'tcx>,
108        liveness_source: &mut impl LivenessSource,
109        visitor: &mut impl LocalizedConstraintGraphVisitor,
110    ) {
111        let mut visited = FxHashSet::default();
112        let mut stack = Vec::new();
113
114        // Compute reachability per loan by traversing each loan's subgraph starting from where it
115        // is introduced.
116        for (loan_idx, loan) in borrow_set.iter_enumerated() {
117            visited.clear();
118            stack.clear();
119
120            let start_node = LocalizedNode {
121                region: loan.region,
122                point: self.location_map.point_from_location(loan.reserve_location),
123            };
124            visited.insert(start_node);
125            stack.push(start_node);
126
127            while let Some(node) = stack.pop() {
128                let liveness = liveness_source.liveness_for_region(node.region);
129                // We've reached a node we haven't visited before.
130                let location = self.location_map.to_location(node.point);
131                visitor.on_node_traversed(loan_idx, node, liveness.is_live_at(node.point));
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.insert(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                // The physical edges present at this node are:
150                //
151                // 1. the typeck edges that flow from region to region *at this point*.
152                for &succ in self.edges.get(&node).into_flat_iter() {
153                    let succ = LocalizedNode { region: succ, point: node.point };
154                    successor_found(succ);
155                }
156
157                // 2a. the liveness edges that flow *forward*, from this node's point to its
158                // successors in the CFG.
159                //
160                // - for covariant cases: loans flow in the regular direction, from the current point
161                // to the next point.
162                // - for invariant cases, loans can flow in both directions, but here we're only
163                // interested in the forward path of the bidirectional edge.
164                //
165                // We still need to check liveness for each next point though.
166                if #[allow(non_exhaustive_omitted_patterns)] match liveness.direction {
    ConstraintDirection::Forward | ConstraintDirection::Bidirectional => true,
    _ => false,
}matches!(
167                    liveness.direction,
168                    ConstraintDirection::Forward | ConstraintDirection::Bidirectional
169                ) {
170                    if body[location.block].statements.get(location.statement_index).is_some() {
171                        // Intra-block edges, straight line constraints from each point to its successor
172                        // within the same block.
173                        let next_point = node.point + 1;
174                        if liveness.is_live_at(next_point) {
175                            successor_found(LocalizedNode {
176                                region: node.region,
177                                point: next_point,
178                            });
179                        }
180                    } else {
181                        // Inter-block edges, from the block's terminator to each successor block's
182                        // entry point.
183                        for successor_block in body[location.block].terminator().successors() {
184                            let next_location =
185                                Location { block: successor_block, statement_index: 0 };
186                            let next_point = self.location_map.point_from_location(next_location);
187                            if liveness.is_live_at(next_point) {
188                                successor_found(LocalizedNode {
189                                    region: node.region,
190                                    point: next_point,
191                                });
192                            }
193                        }
194                    }
195                }
196
197                // 2b. the liveness edges that flow *backward*, from this node's point to its
198                // predecessors in the CFG.
199                //
200                // - for contravariant cases: loans flow in the inverse direction, from the current
201                // point to the previous point.
202                // - for invariant cases, loans can flow in both directions, but here we only
203                // want the backward path of the bidirectional edge.
204                //
205                // Liveness flows into the regions live at the next point. So, in a backwards view, we'll link
206                // the region from the current point, if it's live there, to the previous point.
207                if #[allow(non_exhaustive_omitted_patterns)] match liveness.direction {
    ConstraintDirection::Backward | ConstraintDirection::Bidirectional =>
        true,
    _ => false,
}matches!(
208                    liveness.direction,
209                    ConstraintDirection::Backward | ConstraintDirection::Bidirectional
210                ) && liveness.is_live_at(node.point)
211                {
212                    if location.statement_index > 0 {
213                        // Backward edges to the predecessor point in the same block.
214                        let previous_point = PointIndex::from(node.point.as_usize() - 1);
215                        successor_found(LocalizedNode {
216                            region: node.region,
217                            point: previous_point,
218                        });
219                    } else {
220                        // Backward edges from the block entry point to the terminator of the
221                        // predecessor blocks.
222                        let predecessors = body.basic_blocks.predecessors();
223                        for &pred_block in &predecessors[location.block] {
224                            let previous_location = Location {
225                                block: pred_block,
226                                statement_index: body[pred_block].statements.len(),
227                            };
228                            let previous_point =
229                                self.location_map.point_from_location(previous_location);
230                            successor_found(LocalizedNode {
231                                region: node.region,
232                                point: previous_point,
233                            });
234                        }
235                    }
236                }
237
238                // And finally, we have the logical edges, materialized at this point.
239                let logical_succs = self.logical_edges.get(node.region);
240                for &logical_succ in logical_succs.into_flat_iter() {
241                    let succ = LocalizedNode { region: logical_succ, point: node.point };
242                    successor_found(succ);
243                }
244            }
245        }
246    }
247}