use std::collections::BTreeMap; use rustc_index::bit_set::SparseBitMatrix; use rustc_middle::mir::{Body, Location}; use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation}; use rustc_middle::ty::{self, RegionVid, Ty, TyCtxt, TypeVisitable}; use rustc_mir_dataflow::points::PointIndex; use super::{ ConstraintDirection, LocalizedOutlivesConstraint, LocalizedOutlivesConstraintSet, PoloniusLivenessContext, }; use crate::region_infer::values::LivenessValues; use crate::universal_regions::UniversalRegions; impl PoloniusLivenessContext { /// Record the variance of each region contained within the given value. pub(crate) fn record_live_region_variance<'tcx>( &mut self, tcx: TyCtxt<'tcx>, universal_regions: &UniversalRegions<'tcx>, value: impl TypeVisitable> + Relate>, ) { let mut extractor = VarianceExtractor { tcx, ambient_variance: ty::Variance::Covariant, directions: &mut self.live_region_variances, universal_regions, }; extractor.relate(value, value).expect("Can't have a type error relating to itself"); } } /// Propagate loans throughout the CFG: for each statement in the MIR, create localized outlives /// constraints for loans that are propagated to the next statements. pub(super) fn create_liveness_constraints<'tcx>( body: &Body<'tcx>, liveness: &LivenessValues, live_regions: &SparseBitMatrix, live_region_variances: &BTreeMap, universal_regions: &UniversalRegions<'tcx>, localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, ) { for (block, bb) in body.basic_blocks.iter_enumerated() { let statement_count = bb.statements.len(); for statement_index in 0..=statement_count { let current_location = Location { block, statement_index }; let current_point = liveness.point_from_location(current_location); if statement_index < statement_count { // Intra-block edges, straight line constraints from each point to its successor // within the same block. let next_location = Location { block, statement_index: statement_index + 1 }; let next_point = liveness.point_from_location(next_location); propagate_loans_between_points( current_point, next_point, live_regions, live_region_variances, universal_regions, localized_outlives_constraints, ); } else { // Inter-block edges, from the block's terminator to each successor block's entry // point. for successor_block in bb.terminator().successors() { let next_location = Location { block: successor_block, statement_index: 0 }; let next_point = liveness.point_from_location(next_location); propagate_loans_between_points( current_point, next_point, live_regions, live_region_variances, universal_regions, localized_outlives_constraints, ); } } } } } /// Propagate loans within a region between two points in the CFG, if that region is live at both /// the source and target points. fn propagate_loans_between_points( current_point: PointIndex, next_point: PointIndex, live_regions: &SparseBitMatrix, live_region_variances: &BTreeMap, universal_regions: &UniversalRegions<'_>, localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, ) { // Universal regions are semantically live at all points. // Note: we always have universal regions but they're not always (or often) involved in the // subset graph. For now, we emit all their edges unconditionally, but some of these subgraphs // will be disconnected from the rest of the graph and thus, unnecessary. // // FIXME: only emit the edges of universal regions that existential regions can reach. for region in universal_regions.universal_regions_iter() { localized_outlives_constraints.push(LocalizedOutlivesConstraint { source: region, from: current_point, target: region, to: next_point, }); } let Some(next_live_regions) = live_regions.row(next_point) else { // There are no constraints to add: there are no live regions at the next point. return; }; for region in next_live_regions.iter() { // `region` could be live at the current point, and is live at the next point: add a // constraint between them, according to variance. if let Some(&direction) = live_region_variances.get(®ion) { add_liveness_constraint( region, current_point, next_point, direction, localized_outlives_constraints, ); } else { // Note: there currently are cases related to promoted and const generics, where we // don't yet have variance information (possibly about temporary regions created when // typeck sanitizes the promoteds). Until that is done, we conservatively fallback to // maximizing reachability by adding a bidirectional edge here. This will not limit // traversal whatsoever, and thus propagate liveness when needed. // // FIXME: add the missing variance information and remove this fallback bidirectional // edge. let fallback = ConstraintDirection::Bidirectional; add_liveness_constraint( region, current_point, next_point, fallback, localized_outlives_constraints, ); } } } /// Adds `LocalizedOutlivesConstraint`s between two connected points, according to the given edge /// direction. fn add_liveness_constraint( region: RegionVid, current_point: PointIndex, next_point: PointIndex, direction: ConstraintDirection, localized_outlives_constraints: &mut LocalizedOutlivesConstraintSet, ) { match direction { ConstraintDirection::Forward => { // Covariant cases: loans flow in the regular direction, from the current point to the // next point. localized_outlives_constraints.push(LocalizedOutlivesConstraint { source: region, from: current_point, target: region, to: next_point, }); } ConstraintDirection::Backward => { // Contravariant cases: loans flow in the inverse direction, from the next point to the // current point. localized_outlives_constraints.push(LocalizedOutlivesConstraint { source: region, from: next_point, target: region, to: current_point, }); } ConstraintDirection::Bidirectional => { // For invariant cases, loans can flow in both directions: we add both edges. localized_outlives_constraints.push(LocalizedOutlivesConstraint { source: region, from: current_point, target: region, to: next_point, }); localized_outlives_constraints.push(LocalizedOutlivesConstraint { source: region, from: next_point, target: region, to: current_point, }); } } } /// Extracts variances for regions contained within types. Follows the same structure as /// `rustc_infer`'s `Generalizer`: we try to relate a type with itself to track and extract the /// variances of regions. struct VarianceExtractor<'a, 'tcx> { tcx: TyCtxt<'tcx>, ambient_variance: ty::Variance, directions: &'a mut BTreeMap, universal_regions: &'a UniversalRegions<'tcx>, } impl<'tcx> VarianceExtractor<'_, 'tcx> { fn record_variance(&mut self, region: ty::Region<'tcx>, variance: ty::Variance) { // We're only interested in the variance of vars and free regions. // // Note: even if we currently bail for two cases of unexpected region kinds here, missing // variance data is not a soundness problem: the regions with missing variance will still be // present in the constraint graph as they are live, and liveness edges construction has a // fallback for this case. // // FIXME: that being said, we need to investigate these cases better to not ignore regions // in general. if region.is_bound() { // We ignore these because they cannot be turned into the vids we need. return; } if region.is_erased() { // These cannot be turned into a vid either, and we also ignore them: the fact that they // show up here looks like either an issue upstream or a combination with unexpectedly // continuing compilation too far when we're in a tainted by errors situation. // // FIXME: investigate the `generic_const_exprs` test that triggers this issue, // `ui/const-generics/generic_const_exprs/issue-97047-ice-2.rs` return; } let direction = match variance { ty::Covariant => ConstraintDirection::Forward, ty::Contravariant => ConstraintDirection::Backward, ty::Invariant => ConstraintDirection::Bidirectional, ty::Bivariant => { // We don't add edges for bivariant cases. return; } }; let region = self.universal_regions.to_region_vid(region); self.directions .entry(region) .and_modify(|entry| { // If there's already a recorded direction for this region, we combine the two: // - combining the same direction is idempotent // - combining different directions is trivially bidirectional if entry != &direction { *entry = ConstraintDirection::Bidirectional; } }) .or_insert(direction); } } impl<'tcx> TypeRelation> for VarianceExtractor<'_, 'tcx> { fn cx(&self) -> TyCtxt<'tcx> { self.tcx } fn relate_with_variance>>( &mut self, variance: ty::Variance, _info: ty::VarianceDiagInfo>, a: T, b: T, ) -> RelateResult<'tcx, T> { let old_ambient_variance = self.ambient_variance; self.ambient_variance = self.ambient_variance.xform(variance); let r = self.relate(a, b)?; self.ambient_variance = old_ambient_variance; Ok(r) } fn tys(&mut self, a: Ty<'tcx>, b: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be == relate::structurally_relate_tys(self, a, b) } fn regions( &mut self, a: ty::Region<'tcx>, b: ty::Region<'tcx>, ) -> RelateResult<'tcx, ty::Region<'tcx>> { assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be == self.record_variance(a, self.ambient_variance); Ok(a) } fn consts( &mut self, a: ty::Const<'tcx>, b: ty::Const<'tcx>, ) -> RelateResult<'tcx, ty::Const<'tcx>> { assert_eq!(a, b); // we are misusing TypeRelation here; both LHS and RHS ought to be == relate::structurally_relate_consts(self, a, b) } fn binders( &mut self, a: ty::Binder<'tcx, T>, _: ty::Binder<'tcx, T>, ) -> RelateResult<'tcx, ty::Binder<'tcx, T>> where T: Relate>, { self.relate(a.skip_binder(), a.skip_binder())?; Ok(a) } }