1use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
2use rustc_middle::ty::RegionVid;
3use rustc_mir_dataflow::points::PointIndex;
45use super::{LiveLoans, LocalizedOutlivesConstraintSet};
6use crate::BorrowSet;
7use crate::constraints::OutlivesConstraint;
8use crate::region_infer::values::LivenessValues;
9use crate::type_check::Locations;
1011/// Compute loan reachability to approximately trace loan liveness throughout the CFG, by
12/// traversing the full graph of constraints that combines:
13/// - the localized constraints (the physical edges),
14/// - with the constraints that hold at all points (the logical edges).
15pub(super) fn compute_loan_liveness<'tcx>(
16 liveness: &LivenessValues,
17 outlives_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
18 borrow_set: &BorrowSet<'tcx>,
19 localized_outlives_constraints: &LocalizedOutlivesConstraintSet,
20) -> LiveLoans {
21let mut live_loans = LiveLoans::new(borrow_set.len());
2223// Create the full graph with the physical edges we've localized earlier, and the logical edges
24 // of constraints that hold at all points.
25let logical_constraints =
26outlives_constraints.filter(|c| #[allow(non_exhaustive_omitted_patterns)] match c.locations {
Locations::All(_) => true,
_ => false,
}matches!(c.locations, Locations::All(_)));
27let graph = LocalizedConstraintGraph::new(&localized_outlives_constraints, logical_constraints);
28let mut visited = FxHashSet::default();
29let mut stack = Vec::new();
3031// Compute reachability per loan by traversing each loan's subgraph starting from where it is
32 // introduced.
33for (loan_idx, loan) in borrow_set.iter_enumerated() {
34 visited.clear();
35 stack.clear();
3637let start_node = LocalizedNode {
38 region: loan.region,
39 point: liveness.point_from_location(loan.reserve_location),
40 };
41 stack.push(start_node);
4243while let Some(node) = stack.pop() {
44if !visited.insert(node) {
45continue;
46 }
4748// Record the loan as being live on entry to this point if it reaches a live region
49 // there.
50 //
51 // This is an approximation of liveness (which is the thing we want), in that we're
52 // using a single notion of reachability to represent what used to be _two_ different
53 // transitive closures. It didn't seem impactful when coming up with the single-graph
54 // and reachability through space (regions) + time (CFG) concepts, but in practice the
55 // combination of time-traveling with kills is more impactful than initially
56 // anticipated.
57 //
58 // Kills should prevent a loan from reaching its successor points in the CFG, but not
59 // while time-traveling: we're not actually at that CFG point, but looking for
60 // predecessor regions that contain the loan. One of the two TCs we had pushed the
61 // transitive subset edges to each point instead of having backward edges, and the
62 // problem didn't exist before. In the abstract, naive reachability is not enough to
63 // model this, we'd need a slightly different solution. For example, maybe with a
64 // two-step traversal:
65 // - at each point we first traverse the subgraph (and possibly time-travel) looking for
66 // exit nodes while ignoring kills,
67 // - and then when we're back at the current point, we continue normally.
68 //
69 // Another (less annoying) subtlety is that kills and the loan use-map are
70 // flow-insensitive. Kills can actually appear in places before a loan is introduced, or
71 // at a location that is actually unreachable in the CFG from the introduction point,
72 // and these can also be encountered during time-traveling.
73 //
74 // The simplest change that made sense to "fix" the issues above is taking into
75 // account kills that are:
76 // - reachable from the introduction point
77 // - encountered during forward traversal. Note that this is not transitive like the
78 // two-step traversal described above: only kills encountered on exit via a backward
79 // edge are ignored.
80 //
81 // This version of the analysis, however, is enough in practice to pass the tests that
82 // we care about and NLLs reject, without regressions on crater, and is an actionable
83 // subset of the full analysis. It also naturally points to areas of improvement that we
84 // wish to explore later, namely handling kills appropriately during traversal, instead
85 // of continuing traversal to all the reachable nodes.
86 //
87 // FIXME: analyze potential unsoundness, possibly in concert with a borrowck
88 // implementation in a-mir-formality, fuzzing, or manually crafting counter-examples.
8990if liveness.is_live_at(node.region, liveness.location_from_point(node.point)) {
91 live_loans.insert(node.point, loan_idx);
92 }
9394for succ in graph.outgoing_edges(node) {
95 stack.push(succ);
96 }
97 }
98 }
99100live_loans101}
102103/// The localized constraint graph indexes the physical and logical edges to compute a given node's
104/// successors during traversal.
105struct LocalizedConstraintGraph {
106/// The actual, physical, edges we have recorded for a given node.
107edges: FxHashMap<LocalizedNode, FxIndexSet<LocalizedNode>>,
108109/// The logical edges representing the outlives constraints that hold at all points in the CFG,
110 /// which we don't localize to avoid creating a lot of unnecessary edges in the graph. Some CFGs
111 /// can be big, and we don't need to create such a physical edge for every point in the CFG.
112logical_edges: FxHashMap<RegionVid, FxIndexSet<RegionVid>>,
113}
114115/// A node in the graph to be traversed, one of the two vertices of a localized outlives constraint.
116#[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, #[automatically_derived]
impl ::core::fmt::Debug for LocalizedNode {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "LocalizedNode",
"region", &self.region, "point", &&self.point)
}
}Debug)]
117struct LocalizedNode {
118 region: RegionVid,
119 point: PointIndex,
120}
121122impl LocalizedConstraintGraph {
123/// Traverses the constraints and returns the indexed graph of edges per node.
124fn new<'tcx>(
125 constraints: &LocalizedOutlivesConstraintSet,
126 logical_constraints: impl Iterator<Item = OutlivesConstraint<'tcx>>,
127 ) -> Self {
128let mut edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default();
129for constraint in &constraints.outlives {
130let source = LocalizedNode { region: constraint.source, point: constraint.from };
131let target = LocalizedNode { region: constraint.target, point: constraint.to };
132 edges.entry(source).or_default().insert(target);
133 }
134135let mut logical_edges: FxHashMap<_, FxIndexSet<_>> = FxHashMap::default();
136for constraint in logical_constraints {
137 logical_edges.entry(constraint.sup).or_default().insert(constraint.sub);
138 }
139140LocalizedConstraintGraph { edges, logical_edges }
141 }
142143/// Returns the outgoing edges of a given node, not its transitive closure.
144fn outgoing_edges(&self, node: LocalizedNode) -> impl Iterator<Item = LocalizedNode> {
145// The outgoing edges are:
146 // - the physical edges present at this node,
147 // - the materialized logical edges that exist virtually at all points for this node's
148 // region, localized at this point.
149let physical_edges =
150self.edges.get(&node).into_iter().flat_map(|targets| targets.iter().copied());
151let materialized_edges =
152self.logical_edges.get(&node.region).into_iter().flat_map(move |targets| {
153targets154 .iter()
155 .copied()
156 .map(move |target| LocalizedNode { point: node.point, region: target })
157 });
158physical_edges.chain(materialized_edges)
159 }
160}