rustc_borrowck/polonius/
liveness_constraints.rs

1use std::collections::BTreeMap;
2
3use rustc_index::bit_set::SparseBitMatrix;
4use rustc_middle::mir::{Body, Location};
5use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation};
6use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeVisitable};
7use rustc_mir_dataflow::points::PointIndex;
8
9use super::{
10    ConstraintDirection, LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet,
11    PoloniusLivenessContext,
12};
13use crate::region_infer::values::LivenessValues;
14use crate::universal_regions::UniversalRegions;
15
16impl PoloniusLivenessContext {
17    /// Record the variance of each region contained within the given value.
18    pub(crate) fn record_live_region_variance<'tcx>(
19        &mut self,
20        tcx: TyCtxt<'tcx>,
21        universal_regions: &UniversalRegions<'tcx>,
22        value: impl TypeVisitable<TyCtxt<'tcx>> + Relate<TyCtxt<'tcx>>,
23    ) {
24        let mut extractor = VarianceExtractor {
25            tcx,
26            ambient_variance: ty::Variance::Covariant,
27            directions: &mut self.live_region_variances,
28            universal_regions,
29        };
30        extractor.relate(value, value).expect("Can't have a type error relating to itself");
31    }
32}
33
34/// Propagate loans throughout the CFG: for each statement in the MIR, create localized outlives
35/// constraints for loans that are propagated to the next statements.
36pub(super) fn create_liveness_constraints<'tcx>(
37    body: &Body<'tcx>,
38    liveness: &LivenessValues,
39    live_regions: &SparseBitMatrix<PointIndex, RegionVid>,
40    live_region_variances: &BTreeMap<RegionVid, ConstraintDirection>,
41    universal_regions: &UniversalRegions<'tcx>,
42    localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
43) {
44    for (block, bb) in body.basic_blocks.iter_enumerated() {
45        let statement_count = bb.statements.len();
46        for statement_index in 0..=statement_count {
47            let current_location = Location { block, statement_index };
48            let current_point = liveness.point_from_location(current_location);
49
50            if statement_index < statement_count {
51                // Intra-block edges, straight line constraints from each point to its successor
52                // within the same block.
53                let next_location = Location { block, statement_index: statement_index + 1 };
54                let next_point = liveness.point_from_location(next_location);
55                propagate_loans_between_points(
56                    current_point,
57                    next_point,
58                    live_regions,
59                    live_region_variances,
60                    universal_regions,
61                    localized_outlives_constraints,
62                );
63            } else {
64                // Inter-block edges, from the block's terminator to each successor block's entry
65                // point.
66                for successor_block in bb.terminator().successors() {
67                    let next_location = Location { block: successor_block, statement_index: 0 };
68                    let next_point = liveness.point_from_location(next_location);
69                    propagate_loans_between_points(
70                        current_point,
71                        next_point,
72                        live_regions,
73                        live_region_variances,
74                        universal_regions,
75                        localized_outlives_constraints,
76                    );
77                }
78            }
79        }
80    }
81}
82
83/// Propagate loans within a region between two points in the CFG, if that region is live at both
84/// the source and target points.
85fn propagate_loans_between_points(
86    current_point: PointIndex,
87    next_point: PointIndex,
88    live_regions: &SparseBitMatrix<PointIndex, RegionVid>,
89    live_region_variances: &BTreeMap<RegionVid, ConstraintDirection>,
90    universal_regions: &UniversalRegions<'_>,
91    localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
92) {
93    // Universal regions are semantically live at all points.
94    // Note: we always have universal regions but they're not always (or often) involved in the
95    // subset graph. For now, we emit all their edges unconditionally, but some of these subgraphs
96    // will be disconnected from the rest of the graph and thus, unnecessary.
97    //
98    // FIXME: only emit the edges of universal regions that existential regions can reach.
99    for region in universal_regions.universal_regions_iter() {
100        localized_outlives_constraints.push(LocalizedOutlivesConstraint {
101            source: region,
102            from: current_point,
103            target: region,
104            to: next_point,
105        });
106    }
107
108    let Some(next_live_regions) = live_regions.row(next_point) else {
109        // There are no constraints to add: there are no live regions at the next point.
110        return;
111    };
112
113    for region in next_live_regions.iter() {
114        // `region` could be live at the current point, and is live at the next point: add a
115        // constraint between them, according to variance.
116        if let Some(&direction) = live_region_variances.get(&region) {
117            add_liveness_constraint(
118                region,
119                current_point,
120                next_point,
121                direction,
122                localized_outlives_constraints,
123            );
124        } else {
125            // Note: there currently are cases related to promoted and const generics, where we
126            // don't yet have variance information (possibly about temporary regions created when
127            // typeck sanitizes the promoteds). Until that is done, we conservatively fallback to
128            // maximizing reachability by adding a bidirectional edge here. This will not limit
129            // traversal whatsoever, and thus propagate liveness when needed.
130            //
131            // FIXME: add the missing variance information and remove this fallback bidirectional
132            // edge.
133            let fallback = ConstraintDirection::Bidirectional;
134            add_liveness_constraint(
135                region,
136                current_point,
137                next_point,
138                fallback,
139                localized_outlives_constraints,
140            );
141        }
142    }
143}
144
145/// Adds `LocalizedOutlivesConstraint`s between two connected points, according to the given edge
146/// direction.
147fn add_liveness_constraint(
148    region: RegionVid,
149    current_point: PointIndex,
150    next_point: PointIndex,
151    direction: ConstraintDirection,
152    localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
153) {
154    match direction {
155        ConstraintDirection::Forward => {
156            // Covariant cases: loans flow in the regular direction, from the current point to the
157            // next point.
158            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
159                source: region,
160                from: current_point,
161                target: region,
162                to: next_point,
163            });
164        }
165        ConstraintDirection::Backward => {
166            // Contravariant cases: loans flow in the inverse direction, from the next point to the
167            // current point.
168            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
169                source: region,
170                from: next_point,
171                target: region,
172                to: current_point,
173            });
174        }
175        ConstraintDirection::Bidirectional => {
176            // For invariant cases, loans can flow in both directions: we add both edges.
177            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
178                source: region,
179                from: current_point,
180                target: region,
181                to: next_point,
182            });
183            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
184                source: region,
185                from: next_point,
186                target: region,
187                to: current_point,
188            });
189        }
190    }
191}
192
193/// Extracts variances for regions contained within types. Follows the same structure as
194/// `rustc_infer`'s `Generalizer`: we try to relate a type with itself to track and extract the
195/// variances of regions.
196struct VarianceExtractor<'a, 'tcx> {
197    tcx: TyCtxt<'tcx>,
198    ambient_variance: ty::Variance,
199    directions: &'a mut BTreeMap<RegionVid, ConstraintDirection>,
200    universal_regions: &'a UniversalRegions<'tcx>,
201}
202
203impl<'tcx> VarianceExtractor<'_, 'tcx> {
204    fn record_variance(&mut self, region: ty::Region<'tcx>, variance: ty::Variance) {
205        // We're only interested in the variance of vars and free regions.
206        //
207        // Note: even if we currently bail for two cases of unexpected region kinds here, missing
208        // variance data is not a soundness problem: the regions with missing variance will still be
209        // present in the constraint graph as they are live, and liveness edges construction has a
210        // fallback for this case.
211        //
212        // FIXME: that being said, we need to investigate these cases better to not ignore regions
213        // in general.
214        if region.is_bound() {
215            // We ignore these because they cannot be turned into the vids we need.
216            return;
217        }
218
219        if region.is_erased() {
220            // These cannot be turned into a vid either, and we also ignore them: the fact that they
221            // show up here looks like either an issue upstream or a combination with unexpectedly
222            // continuing compilation too far when we're in a tainted by errors situation.
223            //
224            // FIXME: investigate the `generic_const_exprs` test that triggers this issue,
225            // `ui/const-generics/generic_const_exprs/issue-97047-ice-2.rs`
226            return;
227        }
228
229        let direction = match variance {
230            ty::Covariant => ConstraintDirection::Forward,
231            ty::Contravariant => ConstraintDirection::Backward,
232            ty::Invariant => ConstraintDirection::Bidirectional,
233            ty::Bivariant => {
234                // We don't add edges for bivariant cases.
235                return;
236            }
237        };
238
239        let region = self.universal_regions.to_region_vid(region);
240        self.directions
241            .entry(region)
242            .and_modify(|entry| {
243                // If there's already a recorded direction for this region, we combine the two:
244                // - combining the same direction is idempotent
245                // - combining different directions is trivially bidirectional
246                if entry != &direction {
247                    *entry = ConstraintDirection::Bidirectional;
248                }
249            })
250            .or_insert(direction);
251    }
252}
253
254impl<'tcx> TypeRelation<TyCtxt<'tcx>> for VarianceExtractor<'_, 'tcx> {
255    fn cx(&self) -> TyCtxt<'tcx> {
256        self.tcx
257    }
258
259    fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
260        &mut self,
261        variance: ty::Variance,
262        _info: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
263        a: T,
264        b: T,
265    ) -> RelateResult<'tcx, T> {
266        let old_ambient_variance = self.ambient_variance;
267        self.ambient_variance = self.ambient_variance.xform(variance);
268        let r = self.relate(a, b)?;
269        self.ambient_variance = old_ambient_variance;
270        Ok(r)
271    }
272
273    fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
274        assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
275        relate::structurally_relate_tys(self, a, b)
276    }
277
278    fn regions(
279        &mut self,
280        a: ty::Region<'tcx>,
281        b: ty::Region<'tcx>,
282    ) -> RelateResult<'tcx, ty::Region<'tcx>> {
283        assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
284        self.record_variance(a, self.ambient_variance);
285        Ok(a)
286    }
287
288    fn consts(
289        &mut self,
290        a: ty::Const<'tcx>,
291        b: ty::Const<'tcx>,
292    ) -> RelateResult<'tcx, ty::Const<'tcx>> {
293        assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
294        relate::structurally_relate_consts(self, a, b)
295    }
296
297    fn binders<T>(
298        &mut self,
299        a: ty::Binder<'tcx, T>,
300        _: ty::Binder<'tcx, T>,
301    ) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
302    where
303        T: Relate<TyCtxt<'tcx>>,
304    {
305        self.relate(a.skip_binder(), a.skip_binder())?;
306        Ok(a)
307    }
308}