1use rustc_data_structures::fx::{FxHashMap, FxHashSet};
2use rustc_index::IndexVec;
3use rustc_middle::mir::{Body, Location};
4use rustc_middle::ty::RegionVid;
5use rustc_mir_dataflow::points::{DenseLocationMap, PointIndex};
6use smallvec::SmallVec;
78use crate::BorrowSet;
9use crate::constraints::OutlivesConstraint;
10use crate::dataflow::BorrowIndex;
11use crate::polonius::ConstraintDirection;
12use crate::polonius::liveness::LivenessSource;
13use crate::type_check::Locations;
1415/// 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) -> 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)]
34pub(super) struct LocalizedNode {
35pub region: RegionVid,
36pub point: PointIndex,
37}
3839/// 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.
44edges: FxHashMap<LocalizedNode, SmallVec<[RegionVid; 4]>>,
4546/// 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.
49logical_edges: IndexVec<RegionVid, SmallVec<[RegionVid; 4]>>,
50}
5152/// 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, and whether its region is live at that point.
56fn on_node_traversed(&mut self, _loan: BorrowIndex, _node: LocalizedNode, _is_live: bool) {}
5758/// Callback called when discovering a new `successor` node for the `current_node`.
59fn on_successor_discovered(&mut self, _current_node: LocalizedNode, _successor: LocalizedNode) {
60 }
61}
6263impl LocalizedConstraintGraph {
64/// Traverses the constraints and returns the indexed graph of edges per node.
65pub(super) fn new<'tcx>(
66 location_map: &DenseLocationMap,
67 outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
68 ) -> Self {
69let mut edges: FxHashMap<_, SmallVec<[RegionVid; 4]>> = FxHashMap::default();
70let mut logical_edges: IndexVec<_, SmallVec<[RegionVid; 4]>> = IndexVec::new();
7172for outlives_constraint in outlives_constraints {
73match outlives_constraint.locations {
74 Locations::All(_) => {
75let succs =
76 logical_edges.ensure_contains_elem(outlives_constraint.sup, SmallVec::new);
77if !succs.contains(&outlives_constraint.sub) {
78 succs.push(outlives_constraint.sub);
79 }
80 }
8182 Locations::Single(location) => {
83let node = LocalizedNode {
84 region: outlives_constraint.sup,
85 point: location_map.point_from_location(location),
86 };
87let succs = edges.entry(node).or_default();
88if !succs.contains(&outlives_constraint.sub) {
89 succs.push(outlives_constraint.sub);
90 }
91 }
92 }
93 }
9495LocalizedConstraintGraph { edges, logical_edges }
96 }
9798/// Traverses the localized constraint graph per-loan, and notifies the `visitor` of discovered
99 /// nodes and successors.
100pub(super) fn traverse<'tcx>(
101&self,
102 body: &Body<'tcx>,
103 borrow_set: &BorrowSet<'tcx>,
104 location_map: &DenseLocationMap,
105 liveness_source: &mut impl LivenessSource,
106 visitor: &mut impl LocalizedConstraintGraphVisitor,
107 ) {
108let mut visited = FxHashSet::default();
109let mut stack = Vec::new();
110111// Compute reachability per loan by traversing each loan's subgraph starting from where it
112 // is introduced.
113for (loan_idx, loan) in borrow_set.iter_enumerated() {
114 visited.clear();
115 stack.clear();
116117let start_node = LocalizedNode {
118 region: loan.region,
119 point: location_map.point_from_location(loan.reserve_location),
120 };
121 visited.insert(start_node);
122 stack.push(start_node);
123124while let Some(node) = stack.pop() {
125let liveness = liveness_source.liveness_for_region(node.region);
126// We've reached a node we haven't visited before.
127let location = location_map.to_location(node.point);
128 visitor.on_node_traversed(loan_idx, node, liveness.is_live_at(node.point));
129130// When we find a _new_ successor, we'd like to
131 // - visit it eventually,
132 // - and let the generic visitor know about it.
133let mut successor_found = |succ| {
134if visited.insert(succ) {
135 stack.push(succ);
136 visitor.on_successor_discovered(node, succ);
137 }
138 };
139140// Then, we propagate the loan along the localized constraint graph. The outgoing
141 // edges are computed lazily, from:
142 // - the various physical edges present at this node,
143 // - the materialized logical edges that exist virtually at all points for this
144 // node's region, localized at this point.
145146 // The physical edges present at this node are:
147 //
148 // 1. the typeck edges that flow from region to region *at this point*.
149for &succ in self.edges.get(&node).into_flat_iter() {
150let succ = LocalizedNode { region: succ, point: node.point };
151 successor_found(succ);
152 }
153154// 2a. the liveness edges that flow *forward*, from this node's point to its
155 // successors in the CFG.
156 //
157 // - for covariant cases: loans flow in the regular direction, from the current point
158 // to the next point.
159 // - for invariant cases, loans can flow in both directions, but here we're only
160 // interested in the forward path of the bidirectional edge.
161 //
162 // We still need to check liveness for each next point though.
163if #[allow(non_exhaustive_omitted_patterns)] match liveness.direction {
ConstraintDirection::Forward | ConstraintDirection::Bidirectional => true,
_ => false,
}matches!(
164 liveness.direction,
165 ConstraintDirection::Forward | ConstraintDirection::Bidirectional
166 ) {
167if body[location.block].statements.get(location.statement_index).is_some() {
168// Intra-block edges, straight line constraints from each point to its successor
169 // within the same block.
170let next_point = node.point + 1;
171if liveness.is_live_at(next_point) {
172 successor_found(LocalizedNode {
173 region: node.region,
174 point: next_point,
175 });
176 }
177 } else {
178// Inter-block edges, from the block's terminator to each successor block's
179 // entry point.
180for successor_block in body[location.block].terminator().successors() {
181let next_location = successor_block.start_location();
182let next_point = location_map.point_from_location(next_location);
183if liveness.is_live_at(next_point) {
184 successor_found(LocalizedNode {
185 region: node.region,
186 point: next_point,
187 });
188 }
189 }
190 }
191 }
192193// 2b. the liveness edges that flow *backward*, from this node's point to its
194 // predecessors in the CFG.
195 //
196 // - for contravariant cases: loans flow in the inverse direction, from the current
197 // point to the previous point.
198 // - for invariant cases, loans can flow in both directions, but here we only
199 // want the backward path of the bidirectional edge.
200 //
201 // Liveness flows into the regions live at the next point. So, in a backwards view, we'll link
202 // the region from the current point, if it's live there, to the previous point.
203if #[allow(non_exhaustive_omitted_patterns)] match liveness.direction {
ConstraintDirection::Backward | ConstraintDirection::Bidirectional =>
true,
_ => false,
}matches!(
204 liveness.direction,
205 ConstraintDirection::Backward | ConstraintDirection::Bidirectional
206 ) && liveness.is_live_at(node.point)
207 {
208if location.statement_index > 0 {
209// Backward edges to the predecessor point in the same block.
210let previous_point = PointIndex::from(node.point.as_usize() - 1);
211 successor_found(LocalizedNode {
212 region: node.region,
213 point: previous_point,
214 });
215 } else {
216// Backward edges from the block entry point to the terminator of the
217 // predecessor blocks.
218let predecessors = body.basic_blocks.predecessors();
219for &pred_block in &predecessors[location.block] {
220let previous_location = Location {
221 block: pred_block,
222 statement_index: body[pred_block].statements.len(),
223 };
224let previous_point =
225 location_map.point_from_location(previous_location);
226 successor_found(LocalizedNode {
227 region: node.region,
228 point: previous_point,
229 });
230 }
231 }
232 }
233234// And finally, we have the logical edges, materialized at this point.
235let logical_succs = self.logical_edges.get(node.region);
236for &logical_succ in logical_succs.into_flat_iter() {
237let succ = LocalizedNode { region: logical_succ, point: node.point };
238 successor_found(succ);
239 }
240 }
241 }
242 }
243}