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(current_live_regions) = live_regions.row(current_point) else {
109        // There are no constraints to add: there are no live regions at the current point.
110        return;
111    };
112    let Some(next_live_regions) = live_regions.row(next_point) else {
113        // There are no constraints to add: there are no live regions at the next point.
114        return;
115    };
116
117    for region in next_live_regions.iter() {
118        if !current_live_regions.contains(region) {
119            continue;
120        }
121
122        // `region` is indeed live at both points, add a constraint between them, according to
123        // variance.
124        if let Some(&direction) = live_region_variances.get(&region) {
125            add_liveness_constraint(
126                region,
127                current_point,
128                next_point,
129                direction,
130                localized_outlives_constraints,
131            );
132        } else {
133            // Note: there currently are cases related to promoted and const generics, where we
134            // don't yet have variance information (possibly about temporary regions created when
135            // typeck sanitizes the promoteds). Until that is done, we conservatively fallback to
136            // maximizing reachability by adding a bidirectional edge here. This will not limit
137            // traversal whatsoever, and thus propagate liveness when needed.
138            //
139            // FIXME: add the missing variance information and remove this fallback bidirectional
140            // edge.
141            let fallback = ConstraintDirection::Bidirectional;
142            add_liveness_constraint(
143                region,
144                current_point,
145                next_point,
146                fallback,
147                localized_outlives_constraints,
148            );
149        }
150    }
151}
152
153/// Adds `LocalizedOutlivesConstraint`s between two connected points, according to the given edge
154/// direction.
155fn add_liveness_constraint(
156    region: RegionVid,
157    current_point: PointIndex,
158    next_point: PointIndex,
159    direction: ConstraintDirection,
160    localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet,
161) {
162    match direction {
163        ConstraintDirection::Forward => {
164            // Covariant cases: loans flow in the regular direction, from the current point to the
165            // next point.
166            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
167                source: region,
168                from: current_point,
169                target: region,
170                to: next_point,
171            });
172        }
173        ConstraintDirection::Backward => {
174            // Contravariant cases: loans flow in the inverse direction, from the next point to the
175            // current point.
176            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
177                source: region,
178                from: next_point,
179                target: region,
180                to: current_point,
181            });
182        }
183        ConstraintDirection::Bidirectional => {
184            // For invariant cases, loans can flow in both directions: we add both edges.
185            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
186                source: region,
187                from: current_point,
188                target: region,
189                to: next_point,
190            });
191            localized_outlives_constraints.push(LocalizedOutlivesConstraint {
192                source: region,
193                from: next_point,
194                target: region,
195                to: current_point,
196            });
197        }
198    }
199}
200
201/// Extracts variances for regions contained within types. Follows the same structure as
202/// `rustc_infer`'s `Generalizer`: we try to relate a type with itself to track and extract the
203/// variances of regions.
204struct VarianceExtractor<'a, 'tcx> {
205    tcx: TyCtxt<'tcx>,
206    ambient_variance: ty::Variance,
207    directions: &'a mut BTreeMap<RegionVid, ConstraintDirection>,
208    universal_regions: &'a UniversalRegions<'tcx>,
209}
210
211impl<'tcx> VarianceExtractor<'_, 'tcx> {
212    fn record_variance(&mut self, region: ty::Region<'tcx>, variance: ty::Variance) {
213        // We're only interested in the variance of vars and free regions.
214        //
215        // Note: even if we currently bail for two cases of unexpected region kinds here, missing
216        // variance data is not a soundness problem: the regions with missing variance will still be
217        // present in the constraint graph as they are live, and liveness edges construction has a
218        // fallback for this case.
219        //
220        // FIXME: that being said, we need to investigate these cases better to not ignore regions
221        // in general.
222        if region.is_bound() {
223            // We ignore these because they cannot be turned into the vids we need.
224            return;
225        }
226
227        if region.is_erased() {
228            // These cannot be turned into a vid either, and we also ignore them: the fact that they
229            // show up here looks like either an issue upstream or a combination with unexpectedly
230            // continuing compilation too far when we're in a tainted by errors situation.
231            //
232            // FIXME: investigate the `generic_const_exprs` test that triggers this issue,
233            // `ui/const-generics/generic_const_exprs/issue-97047-ice-2.rs`
234            return;
235        }
236
237        let direction = match variance {
238            ty::Covariant => ConstraintDirection::Forward,
239            ty::Contravariant => ConstraintDirection::Backward,
240            ty::Invariant => ConstraintDirection::Bidirectional,
241            ty::Bivariant => {
242                // We don't add edges for bivariant cases.
243                return;
244            }
245        };
246
247        let region = self.universal_regions.to_region_vid(region);
248        self.directions
249            .entry(region)
250            .and_modify(|entry| {
251                // If there's already a recorded direction for this region, we combine the two:
252                // - combining the same direction is idempotent
253                // - combining different directions is trivially bidirectional
254                if entry != &direction {
255                    *entry = ConstraintDirection::Bidirectional;
256                }
257            })
258            .or_insert(direction);
259    }
260}
261
262impl<'tcx> TypeRelation<TyCtxt<'tcx>> for VarianceExtractor<'_, 'tcx> {
263    fn cx(&self) -> TyCtxt<'tcx> {
264        self.tcx
265    }
266
267    fn relate_with_variance<T: Relate<TyCtxt<'tcx>>>(
268        &mut self,
269        variance: ty::Variance,
270        _info: ty::VarianceDiagInfo<TyCtxt<'tcx>>,
271        a: T,
272        b: T,
273    ) -> RelateResult<'tcx, T> {
274        let old_ambient_variance = self.ambient_variance;
275        self.ambient_variance = self.ambient_variance.xform(variance);
276        let r = self.relate(a, b)?;
277        self.ambient_variance = old_ambient_variance;
278        Ok(r)
279    }
280
281    fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
282        assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
283        relate::structurally_relate_tys(self, a, b)
284    }
285
286    fn regions(
287        &mut self,
288        a: ty::Region<'tcx>,
289        b: ty::Region<'tcx>,
290    ) -> RelateResult<'tcx, ty::Region<'tcx>> {
291        assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
292        self.record_variance(a, self.ambient_variance);
293        Ok(a)
294    }
295
296    fn consts(
297        &mut self,
298        a: ty::Const<'tcx>,
299        b: ty::Const<'tcx>,
300    ) -> RelateResult<'tcx, ty::Const<'tcx>> {
301        assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be ==
302        relate::structurally_relate_consts(self, a, b)
303    }
304
305    fn binders<T>(
306        &mut self,
307        a: ty::Binder<'tcx, T>,
308        _: ty::Binder<'tcx, T>,
309    ) -> RelateResult<'tcx, ty::Binder<'tcx, T>>
310    where
311        T: Relate<TyCtxt<'tcx>>,
312    {
313        self.relate(a.skip_binder(), a.skip_binder())?;
314        Ok(a)
315    }
316}