Use an enum for SCC representatives, plus other code review

Co-authored-by: lcnr <rust@lcnr.de>
This commit is contained in:
Amanda Stjerna 2025-05-02 10:50:03 +02:00
parent aca36fd12a
commit f455c4fc39
7 changed files with 131 additions and 136 deletions

View file

@ -1,14 +1,13 @@
//! Logic for lowering higher-kinded outlives constraints
//! (with placeholders and universes) and turn them into regular
//! outlives constraints.
//!
//! This logic is provisional and should be removed once the trait
//! solver can handle this kind of constraint.
use rustc_data_structures::frozen::Frozen;
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
use rustc_data_structures::fx::FxIndexMap;
use rustc_data_structures::graph::scc;
use rustc_data_structures::graph::scc::Sccs;
use rustc_index::IndexVec;
use rustc_infer::infer::RegionVariableOrigin;
use rustc_middle::mir::ConstraintCategory;
use rustc_middle::ty::{RegionVid, UniverseIndex};
use tracing::debug;
@ -18,7 +17,7 @@ use crate::consumers::OutlivesConstraint;
use crate::diagnostics::UniverseInfo;
use crate::member_constraints::MemberConstraintSet;
use crate::region_infer::values::{LivenessValues, PlaceholderIndices};
use crate::region_infer::{ConstraintSccs, RegionDefinition, TypeTest};
use crate::region_infer::{ConstraintSccs, RegionDefinition, Representative, TypeTest};
use crate::ty::VarianceDiagInfo;
use crate::type_check::free_region_relations::UniversalRegionRelations;
use crate::type_check::{Locations, MirTypeckRegionConstraints};
@ -32,7 +31,7 @@ pub(crate) struct LoweredConstraints<'tcx> {
pub(crate) definitions: Frozen<IndexVec<RegionVid, RegionDefinition<'tcx>>>,
pub(crate) scc_annotations: IndexVec<ConstraintSccIndex, RegionTracker>,
pub(crate) member_constraints: MemberConstraintSet<'tcx, RegionVid>,
pub(crate) outlives_constraints: OutlivesConstraintSet<'tcx>,
pub(crate) outlives_constraints: Frozen<OutlivesConstraintSet<'tcx>>,
pub(crate) type_tests: Vec<TypeTest<'tcx>>,
pub(crate) liveness_constraints: LivenessValues,
pub(crate) universe_causes: FxIndexMap<UniverseIndex, UniverseInfo<'tcx>>,
@ -73,45 +72,35 @@ pub(crate) struct RegionTracker {
/// This includes placeholders within this SCC.
max_placeholder_universe_reached: UniverseIndex,
/// The smallest universe index reachable form the nodes of this SCC.
min_reachable_universe: UniverseIndex,
/// The largest universe nameable from this SCC.
/// It is the smallest nameable universes of all
/// existential regions reachable from it.
max_nameable_universe: UniverseIndex,
/// The representative Region Variable Id for this SCC. We prefer
/// placeholders over existentially quantified variables, otherwise
/// it's the one with the smallest Region Variable ID.
pub(crate) representative: RegionVid,
/// Is the current representative a placeholder?
representative_is_placeholder: bool,
/// Is the current representative existentially quantified?
representative_is_existential: bool,
/// The representative Region Variable Id for this SCC.
pub(crate) representative: Representative,
}
impl RegionTracker {
pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
let (representative_is_placeholder, representative_is_existential) = match definition.origin
{
NllRegionVariableOrigin::FreeRegion => (false, false),
NllRegionVariableOrigin::Placeholder(_) => (true, false),
NllRegionVariableOrigin::Existential { .. } => (false, true),
};
let placeholder_universe =
if representative_is_placeholder { definition.universe } else { UniverseIndex::ROOT };
if matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_)) {
definition.universe
} else {
UniverseIndex::ROOT
};
Self {
max_placeholder_universe_reached: placeholder_universe,
min_reachable_universe: definition.universe,
representative: rvid,
representative_is_placeholder,
representative_is_existential,
max_nameable_universe: definition.universe,
representative: Representative::new(rvid, definition),
}
}
/// The smallest-indexed universe reachable from and/or in this SCC.
pub(crate) fn min_universe(self) -> UniverseIndex {
self.min_reachable_universe
/// The largest universe this SCC can name. It's the smallest
/// largest nameable uninverse of any reachable region.
pub(crate) fn max_nameable_universe(self) -> UniverseIndex {
self.max_nameable_universe
}
fn merge_min_max_seen(&mut self, other: &Self) {
@ -120,40 +109,29 @@ impl RegionTracker {
other.max_placeholder_universe_reached,
);
self.min_reachable_universe =
std::cmp::min(self.min_reachable_universe, other.min_reachable_universe);
self.max_nameable_universe =
std::cmp::min(self.max_nameable_universe, other.max_nameable_universe);
}
/// Returns `true` if during the annotated SCC reaches a placeholder
/// with a universe larger than the smallest reachable one, `false` otherwise.
/// with a universe larger than the smallest nameable universe of any
/// reachable existential region.
pub(crate) fn has_incompatible_universes(&self) -> bool {
self.min_universe().cannot_name(self.max_placeholder_universe_reached)
self.max_nameable_universe().cannot_name(self.max_placeholder_universe_reached)
}
/// Determine if the tracked universes of the two SCCs
/// are compatible.
/// Determine if the tracked universes of the two SCCs are compatible.
pub(crate) fn universe_compatible_with(&self, other: Self) -> bool {
self.min_universe().can_name(other.min_universe())
|| self.min_universe().can_name(other.max_placeholder_universe_reached)
self.max_nameable_universe().can_name(other.max_nameable_universe())
|| self.max_nameable_universe().can_name(other.max_placeholder_universe_reached)
}
}
impl scc::Annotation for RegionTracker {
fn merge_scc(mut self, mut other: Self) -> Self {
// Prefer any placeholder over any existential
if other.representative_is_placeholder && self.representative_is_existential {
other.merge_min_max_seen(&self);
return other;
}
if self.representative_is_placeholder && other.representative_is_existential
|| (self.representative <= other.representative)
{
self.merge_min_max_seen(&other);
return self;
}
other.merge_min_max_seen(&self);
other
fn merge_scc(mut self, other: Self) -> Self {
self.representative = self.representative.merge_scc(other.representative);
self.merge_min_max_seen(&other);
self
}
fn merge_reached(mut self, other: Self) -> Self {
@ -164,7 +142,7 @@ impl scc::Annotation for RegionTracker {
}
/// Determines if the region variable definitions contain
/// placeholers, and compute them for later use.
/// placeholders, and compute them for later use.
fn region_definitions<'tcx>(
universal_regions: &UniversalRegions<'tcx>,
infcx: &BorrowckInferCtxt<'tcx>,
@ -177,12 +155,19 @@ fn region_definitions<'tcx>(
let mut has_placeholders = false;
for info in var_infos.iter() {
let definition = RegionDefinition::new(info);
has_placeholders |= matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_));
let origin = match info.origin {
RegionVariableOrigin::Nll(origin) => origin,
_ => NllRegionVariableOrigin::Existential { from_forall: false },
};
let definition = RegionDefinition { origin, universe: info.universe, external_name: None };
has_placeholders |= matches!(origin, NllRegionVariableOrigin::Placeholder(_));
definitions.push(definition);
}
// Add external names from universal regions in fun function definitions.
// FIXME: this two-step method is annoying, but I don't know how to avoid it.
for (external_name, variable) in universal_regions.named_universal_regions_iter() {
debug!("region {:?} has external name {:?}", variable, external_name);
definitions[variable].external_name = Some(external_name);
@ -190,29 +175,19 @@ fn region_definitions<'tcx>(
(Frozen::freeze(definitions), has_placeholders)
}
/// This method handles Universe errors by rewriting the constraint
/// This method handles placeholders by rewriting the constraint
/// graph. For each strongly connected component in the constraint
/// graph such that there is a series of constraints
/// A: B: C: ... : X where
/// A's universe is smaller than X's and A is a placeholder,
/// A contains a placeholder whose universe cannot be named by X,
/// add a constraint that A: 'static. This is a safe upper bound
/// in the face of borrow checker/trait solver limitations that will
/// eventually go away.
///
/// For a more precise definition, see the documentation for
/// [`RegionTracker`] and its methods!.
///
/// Since universes can also be involved in errors (if one placeholder
/// transitively outlives another), this function also flags those.
///
/// Additionally, it similarly rewrites type-tests.
///
/// This edge case used to be handled during constraint propagation
/// by iterating over the strongly connected components in the constraint
/// graph while maintaining a set of bookkeeping mappings similar
/// to what is stored in `RegionTracker` and manually adding 'sttaic as
/// needed.
/// [`RegionTracker`] and its methods!
///
/// This edge case used to be handled during constraint propagation.
/// It was rewritten as part of the Polonius project with the goal of moving
/// higher-kindedness concerns out of the path of the borrow checker,
/// for two reasons:
@ -228,7 +203,7 @@ fn region_definitions<'tcx>(
/// This code is a stop-gap measure in preparation for the future trait solver.
///
/// Every constraint added by this method is an internal `IllegalUniverse` constraint.
pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
pub(crate) fn compute_sccs_applying_placeholder_outlives_constraints<'tcx>(
constraints: MirTypeckRegionConstraints<'tcx>,
universal_region_relations: &Frozen<UniversalRegionRelations<'tcx>>,
infcx: &BorrowckInferCtxt<'tcx>,
@ -267,13 +242,14 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
)
};
let mut scc_annotations = SccAnnotations::init(&definitions);
let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations);
// This code structure is a bit convoluted because it allows for a planned
// future change where the early return here has a different type of annotation
// that does much less work.
if !has_placeholders {
debug!("No placeholder regions found; skipping rewriting logic!");
let mut scc_annotations = SccAnnotations::init(&definitions);
let constraint_sccs = compute_sccs(&outlives_constraints, &mut scc_annotations);
return LoweredConstraints {
type_tests,
@ -281,7 +257,7 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
constraint_sccs,
scc_annotations: scc_annotations.scc_to_annotation,
definitions,
outlives_constraints,
outlives_constraints: Frozen::freeze(outlives_constraints),
liveness_constraints,
universe_causes,
placeholder_indices,
@ -289,14 +265,14 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
}
debug!("Placeholders present; activating placeholder handling logic!");
let mut annotations = SccAnnotations::init(&definitions);
let sccs = compute_sccs(&outlives_constraints, &mut annotations);
let added_constraints = rewrite_placeholder_outlives(
&constraint_sccs,
&scc_annotations,
fr_static,
&mut outlives_constraints,
);
let outlives_static =
rewrite_outlives(&sccs, &annotations, fr_static, &mut outlives_constraints);
let (sccs, scc_annotations) = if !outlives_static.is_empty() {
debug!("The following SCCs had :'static constraints added: {:?}", outlives_static);
let (constraint_sccs, scc_annotations) = if added_constraints {
let mut annotations = SccAnnotations::init(&definitions);
// We changed the constraint set and so must recompute SCCs.
@ -307,15 +283,15 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
} else {
// If we didn't add any back-edges; no more work needs doing
debug!("No constraints rewritten!");
(sccs, annotations.scc_to_annotation)
(constraint_sccs, scc_annotations.scc_to_annotation)
};
LoweredConstraints {
constraint_sccs: sccs,
constraint_sccs,
definitions,
scc_annotations,
member_constraints,
outlives_constraints,
outlives_constraints: Frozen::freeze(outlives_constraints),
type_tests,
liveness_constraints,
universe_causes,
@ -323,15 +299,15 @@ pub(crate) fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
}
}
fn rewrite_outlives<'tcx>(
fn rewrite_placeholder_outlives<'tcx>(
sccs: &Sccs<RegionVid, ConstraintSccIndex>,
annotations: &SccAnnotations<'_, '_, RegionTracker>,
fr_static: RegionVid,
outlives_constraints: &mut OutlivesConstraintSet<'tcx>,
) -> FxHashSet<ConstraintSccIndex> {
// Changed to `true` if we added any constraints to `self` and need to
) -> bool {
// Changed to `true` if we added any constraints and need to
// recompute SCCs.
let mut outlives_static = FxHashSet::default();
let mut added_constraints = false;
let annotations = &annotations.scc_to_annotation;
@ -354,9 +330,8 @@ fn rewrite_outlives<'tcx>(
// needed for correctness, since an SCC upstream of another with
// a universe violation will "infect" its downstream SCCs to also
// outlive static.
outlives_static.insert(scc);
let scc_representative_outlives_static = OutlivesConstraint {
sup: annotation.representative,
sup: annotation.representative.rvid(),
sub: fr_static,
category: ConstraintCategory::IllegalUniverse,
locations: Locations::All(rustc_span::DUMMY_SP),
@ -365,7 +340,9 @@ fn rewrite_outlives<'tcx>(
from_closure: false,
};
outlives_constraints.push(scc_representative_outlives_static);
added_constraints = true;
debug!("Added {:?}: 'static!", annotation.representative.rvid());
}
}
outlives_static
added_constraints
}

View file

@ -75,7 +75,7 @@ mod constraints;
mod dataflow;
mod def_use;
mod diagnostics;
mod eliminate_placeholders;
mod handle_placeholders;
mod member_constraints;
mod nll;
mod path_utils;

View file

@ -22,7 +22,7 @@ use tracing::{debug, instrument};
use crate::borrow_set::BorrowSet;
use crate::consumers::ConsumerOptions;
use crate::diagnostics::RegionErrors;
use crate::eliminate_placeholders::rewrite_higher_kinded_outlives_as_constraints;
use crate::handle_placeholders::compute_sccs_applying_placeholder_outlives_constraints;
use crate::polonius::PoloniusDiagnosticsContext;
use crate::polonius::legacy::{
PoloniusFacts, PoloniusFactsExt, PoloniusLocationTable, PoloniusOutput,
@ -118,7 +118,7 @@ pub(crate) fn compute_regions<'a, 'tcx>(
Rc::clone(&location_map),
);
let lowered_constraints = rewrite_higher_kinded_outlives_as_constraints(
let lowered_constraints = compute_sccs_applying_placeholder_outlives_constraints(
constraints,
&universal_region_relations,
infcx,

View file

@ -13,7 +13,7 @@ use tracing::debug;
use crate::borrow_set::BorrowSet;
use crate::constraints::OutlivesConstraint;
use crate::eliminate_placeholders::LoweredConstraints;
use crate::handle_placeholders::LoweredConstraints;
use crate::type_check::free_region_relations::UniversalRegionRelations;
use crate::universal_regions::UniversalRegions;

View file

@ -46,7 +46,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
"| {r:rw$?} | {ui:4?} | {v}",
r = region,
rw = REGION_WIDTH,
ui = self.region_universe(region),
ui = self.max_nameable_universe(self.constraint_sccs.scc(region)),
v = self.region_value_str(region),
)?;
}

View file

@ -4,15 +4,13 @@ use std::rc::Rc;
use rustc_data_structures::binary_search_util;
use rustc_data_structures::frozen::Frozen;
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
use rustc_data_structures::graph::scc::Sccs;
use rustc_data_structures::graph::scc::{self, Sccs};
use rustc_errors::Diag;
use rustc_hir::def_id::CRATE_DEF_ID;
use rustc_index::IndexVec;
use rustc_infer::infer::outlives::test_type_match;
use rustc_infer::infer::region_constraints::{
GenericKind, RegionVariableInfo, VerifyBound, VerifyIfEq,
};
use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin};
use rustc_infer::infer::region_constraints::{GenericKind, VerifyBound, VerifyIfEq};
use rustc_infer::infer::{InferCtxt, NllRegionVariableOrigin};
use rustc_middle::bug;
use rustc_middle::mir::{
AnnotationSource, BasicBlock, Body, ConstraintCategory, Local, Location, ReturnConstraint,
@ -29,7 +27,7 @@ use crate::constraints::graph::{self, NormalConstraintGraph, RegionGraph};
use crate::constraints::{ConstraintSccIndex, OutlivesConstraint, OutlivesConstraintSet};
use crate::dataflow::BorrowIndex;
use crate::diagnostics::{RegionErrorKind, RegionErrors, UniverseInfo};
use crate::eliminate_placeholders::{LoweredConstraints, RegionTracker};
use crate::handle_placeholders::{LoweredConstraints, RegionTracker};
use crate::member_constraints::{MemberConstraintSet, NllMemberConstraintIndex};
use crate::polonius::LiveLoans;
use crate::polonius::legacy::PoloniusOutput;
@ -50,6 +48,47 @@ mod reverse_sccs;
pub(crate) mod values;
/// The representative region variable for an SCC, tagged by its origin.
/// We prefer placeholders over existentially quantified variables, otherwise
/// it's the one with the smallest Region Variable ID. In other words,
/// the order of this enumeration really matters!
#[derive(Copy, Debug, Clone, PartialEq, PartialOrd, Eq, Ord)]
pub(crate) enum Representative {
FreeRegion(RegionVid),
Placeholder(RegionVid),
Existential(RegionVid),
}
impl Representative {
pub(crate) fn rvid(self) -> RegionVid {
match self {
Representative::FreeRegion(region_vid)
| Representative::Placeholder(region_vid)
| Representative::Existential(region_vid) => region_vid,
}
}
pub(crate) fn new(r: RegionVid, definition: &RegionDefinition<'_>) -> Self {
match definition.origin {
NllRegionVariableOrigin::FreeRegion => Representative::FreeRegion(r),
NllRegionVariableOrigin::Placeholder(_) => Representative::Placeholder(r),
NllRegionVariableOrigin::Existential { .. } => Representative::Existential(r),
}
}
}
impl scc::Annotation for Representative {
fn merge_scc(self, other: Self) -> Self {
// Just pick the smallest one. Note that we order by tag first!
std::cmp::min(self, other)
}
// For reachability, we do nothing since the representative doesn't change.
fn merge_reached(self, _other: Self) -> Self {
self
}
}
pub(crate) type ConstraintSccs = Sccs<RegionVid, ConstraintSccIndex>;
pub struct RegionInferenceContext<'tcx> {
@ -351,7 +390,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let mut result = Self {
definitions,
liveness_constraints,
constraints: Frozen::freeze(outlives_constraints),
constraints: outlives_constraints,
constraint_graph,
constraint_sccs,
scc_annotations,
@ -510,11 +549,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
self.scc_values.placeholders_contained_in(scc)
}
/// Returns access to the value of `r` for debugging purposes.
pub(crate) fn region_universe(&self, r: RegionVid) -> ty::UniverseIndex {
self.scc_universe(self.constraint_sccs.scc(r))
}
/// Once region solving has completed, this function will return the member constraints that
/// were applied to the value of a given SCC `scc`. See `AppliedMemberConstraint`.
pub(crate) fn applied_member_constraints(
@ -681,7 +715,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
// If the member region lives in a higher universe, we currently choose
// the most conservative option by leaving it unchanged.
if !self.scc_universe(scc).is_root() {
if !self.max_nameable_universe(scc).is_root() {
return;
}
@ -861,7 +895,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
"lower_bound = {:?} r_scc={:?} universe={:?}",
lower_bound,
r_scc,
self.scc_universe(r_scc)
self.max_nameable_universe(r_scc)
);
// If the type test requires that `T: 'a` where `'a` is a
// placeholder from another universe, that effectively requires
@ -1339,10 +1373,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
}
}
/// The minimum universe of any variable reachable from this
/// SCC, inside or outside of it.
fn scc_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex {
self.scc_annotations[scc].min_universe()
/// The largest universe of any region nameable from this SCC.
fn max_nameable_universe(&self, scc: ConstraintSccIndex) -> UniverseIndex {
self.scc_annotations[scc].max_nameable_universe()
}
/// Checks the final value for the free region `fr` to see if it
@ -1364,7 +1397,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
// Because this free region must be in the ROOT universe, we
// know it cannot contain any bound universes.
assert!(self.scc_universe(longer_fr_scc).is_root());
assert!(self.max_nameable_universe(longer_fr_scc).is_root());
// Only check all of the relations for the main representative of each
// SCC, otherwise just check that we outlive said representative. This
@ -1755,7 +1788,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
#[instrument(skip(self), level = "trace", ret)]
pub(crate) fn find_sub_region_live_at(&self, fr1: RegionVid, location: Location) -> RegionVid {
trace!(scc = ?self.constraint_sccs.scc(fr1));
trace!(universe = ?self.region_universe(fr1));
trace!(universe = ?self.max_nameable_universe(self.constraint_sccs.scc(fr1)));
self.find_constraint_paths_between_regions(fr1, |r| {
// First look for some `r` such that `fr1: r` and `r` is live at `location`
trace!(?r, liveness_constraints=?self.liveness_constraints.pretty_print_live_points(r));
@ -2086,7 +2119,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
/// they *must* be equal (though not having the same repr does not
/// mean they are unequal).
fn scc_representative(&self, scc: ConstraintSccIndex) -> RegionVid {
self.scc_annotations[scc].representative
self.scc_annotations[scc].representative.rvid()
}
pub(crate) fn liveness_constraints(&self) -> &LivenessValues {
@ -2108,21 +2141,6 @@ impl<'tcx> RegionInferenceContext<'tcx> {
}
}
impl<'tcx> RegionDefinition<'tcx> {
pub(crate) fn new(rv_info: &RegionVariableInfo) -> Self {
// Create a new region definition. Note that, for free
// regions, the `external_name` field gets updated later in
// [[crate::eliminate_placeholders]].
let origin = match rv_info.origin {
RegionVariableOrigin::Nll(origin) => origin,
_ => NllRegionVariableOrigin::Existential { from_forall: false },
};
Self { origin, universe: rv_info.universe, external_name: None }
}
}
#[derive(Clone, Debug)]
pub(crate) struct BlameConstraint<'tcx> {
pub category: ConstraintCategory<'tcx>,

View file

@ -191,7 +191,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
let scc = self.constraint_sccs.scc(vid);
// Special handling of higher-ranked regions.
if !self.scc_universe(scc).is_root() {
if !self.max_nameable_universe(scc).is_root() {
match self.scc_values.placeholders_contained_in(scc).enumerate().last() {
// If the region contains a single placeholder then they're equal.
Some((0, placeholder)) => {