rustc_borrowck/polonius/
liveness_constraints.rs

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