Skip to content

Commit 08067ee

Browse files
committed
Handle universe leaks by rewriting the constraint graph
This version is a squash-rebased version of a series of exiermental commits, since large parts of them were broken out into PR #125069. It explicitly handles universe violations in higher-kinded outlives constraints by adding extra outlives static constraints.
1 parent 99f77a2 commit 08067ee

File tree

4 files changed

+111
-68
lines changed

4 files changed

+111
-68
lines changed

compiler/rustc_borrowck/src/constraints/mod.rs

+84
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1+
use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker};
12
use crate::type_check::Locations;
3+
use crate::universal_regions::UniversalRegions;
24
use rustc_index::{IndexSlice, IndexVec};
35
use rustc_middle::mir::ConstraintCategory;
46
use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo};
@@ -48,6 +50,88 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
4850
) -> &IndexSlice<OutlivesConstraintIndex, OutlivesConstraint<'tcx>> {
4951
&self.outlives
5052
}
53+
54+
/// Computes cycles (SCCs) in the graph of regions. In particular,
55+
/// find all regions R1, R2 such that R1: R2 and R2: R1 and group
56+
/// them into an SCC, and find the relationships between SCCs.
57+
pub(crate) fn compute_sccs(
58+
&self,
59+
static_region: RegionVid,
60+
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
61+
) -> ConstraintSccs {
62+
let constraint_graph = self.graph(definitions.len());
63+
let region_graph = &constraint_graph.region_graph(self, static_region);
64+
ConstraintSccs::new_with_annotation(&region_graph, |r| {
65+
RegionTracker::new(r, &definitions[r])
66+
})
67+
}
68+
69+
/// This method handles universe errors by rewriting the constraint
70+
/// graph. For each strongly connected component in the constraint
71+
/// graph such that there is a series of constraints
72+
/// A: B: C: ... : X where
73+
/// A's universe is smaller than X's and A is a placeholder,
74+
/// add A: 'static.
75+
///
76+
/// For a more precise definition, see the documentation for
77+
/// [`RegionTracker::has_incompatible_universes()`].
78+
///
79+
/// Every constraint added by this method is an
80+
/// `IllegalUniverse` constraint.
81+
#[instrument(skip(self, universal_regions, definitions))]
82+
pub(crate) fn add_outlives_static(
83+
&mut self,
84+
universal_regions: &UniversalRegions<'tcx>,
85+
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
86+
) -> ConstraintSccs {
87+
let fr_static = universal_regions.fr_static;
88+
let sccs = self.compute_sccs(fr_static, definitions);
89+
90+
// Changed to `true` if we added any constraints to `self` and need to
91+
// recompute SCCs.
92+
let mut added_constraints = false;
93+
94+
for scc in sccs.all_sccs() {
95+
// No point in adding 'static: 'static!
96+
// This micro-optimisation makes somewhat sense
97+
// because static outlives *everything*.
98+
if scc == sccs.scc(fr_static) {
99+
continue;
100+
}
101+
102+
let annotation = sccs.annotation(scc);
103+
104+
// If this SCC participates in a universe violation,
105+
// e.g. if it reaches a region with a universe smaller than
106+
// the largest region reached, add a requirement that it must
107+
// outlive `'static`.
108+
if annotation.has_incompatible_universes() {
109+
// Optimisation opportunity: this will add more constraints than
110+
// needed for correctness, since an SCC upstream of another with
111+
// a universe violation will "infect" its downstream SCCs to also
112+
// outlive static.
113+
added_constraints = true;
114+
let scc_representative_outlives_static = OutlivesConstraint {
115+
sup: annotation.representative,
116+
sub: fr_static,
117+
category: ConstraintCategory::IllegalUniverse,
118+
locations: Locations::All(rustc_span::DUMMY_SP),
119+
span: rustc_span::DUMMY_SP,
120+
variance_info: VarianceDiagInfo::None,
121+
from_closure: false,
122+
};
123+
self.push(scc_representative_outlives_static);
124+
}
125+
}
126+
127+
if added_constraints {
128+
// We changed the constraint set and so must recompute SCCs.
129+
self.compute_sccs(fr_static, definitions)
130+
} else {
131+
// If we didn't add any back-edges; no more work needs doing
132+
sccs
133+
}
134+
}
51135
}
52136

53137
impl<'tcx> Index<OutlivesConstraintIndex> for OutlivesConstraintSet<'tcx> {

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ impl<'tcx> ConstraintDescription for ConstraintCategory<'tcx> {
6666
ConstraintCategory::Predicate(_)
6767
| ConstraintCategory::Boring
6868
| ConstraintCategory::BoringNoLocation
69-
| ConstraintCategory::Internal => "",
69+
| ConstraintCategory::Internal
70+
| ConstraintCategory::IllegalUniverse => "",
7071
}
7172
}
7273
}

compiler/rustc_borrowck/src/region_infer/mod.rs

+22-67
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ pub struct RegionTracker {
6262
/// The representative Region Variable Id for this SCC. We prefer
6363
/// placeholders over existentially quantified variables, otherwise
6464
/// it's the one with the smallest Region Variable ID.
65-
representative: RegionVid,
65+
pub(crate) representative: RegionVid,
6666

6767
/// Is the current representative a placeholder?
6868
representative_is_placeholder: bool,
@@ -97,7 +97,7 @@ impl scc::Annotation for RegionTracker {
9797
}
9898

9999
impl RegionTracker {
100-
fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
100+
pub(crate) fn new(rvid: RegionVid, definition: &RegionDefinition<'_>) -> Self {
101101
let (representative_is_placeholder, representative_is_existential) = match definition.origin
102102
{
103103
rustc_infer::infer::NllRegionVariableOrigin::FreeRegion => (false, false),
@@ -132,7 +132,7 @@ impl RegionTracker {
132132

133133
/// Returns `true` if during the annotated SCC reaches a placeholder
134134
/// with a universe larger than the smallest reachable one, `false` otherwise.
135-
pub fn has_incompatible_universes(&self) -> bool {
135+
pub(crate) fn has_incompatible_universes(&self) -> bool {
136136
self.universe().cannot_name(self.max_placeholder_universe_reached)
137137
}
138138
}
@@ -163,7 +163,7 @@ pub struct RegionInferenceContext<'tcx> {
163163
/// The SCC computed from `constraints` and the constraint
164164
/// graph. We have an edge from SCC A to SCC B if `A: B`. Used to
165165
/// compute the values of each region.
166-
constraint_sccs: Rc<ConstraintSccs>,
166+
constraint_sccs: ConstraintSccs,
167167

168168
/// Reverse of the SCC constraint graph -- i.e., an edge `A -> B` exists if
169169
/// `B: A`. This is used to compute the universal regions that are required
@@ -401,7 +401,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
401401
universal_regions: Rc<UniversalRegions<'tcx>>,
402402
placeholder_indices: Rc<PlaceholderIndices>,
403403
universal_region_relations: Frozen<UniversalRegionRelations<'tcx>>,
404-
outlives_constraints: OutlivesConstraintSet<'tcx>,
404+
mut outlives_constraints: OutlivesConstraintSet<'tcx>,
405405
member_constraints_in: MemberConstraintSet<'tcx, RegionVid>,
406406
universe_causes: FxIndexMap<ty::UniverseIndex, UniverseInfo<'tcx>>,
407407
type_tests: Vec<TypeTest<'tcx>>,
@@ -419,17 +419,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
419419
.map(|info| RegionDefinition::new(info.universe, info.origin))
420420
.collect();
421421

422-
let fr_static = universal_regions.fr_static;
422+
let constraint_sccs =
423+
outlives_constraints.add_outlives_static(&universal_regions, &definitions);
423424
let constraints = Frozen::freeze(outlives_constraints);
424425
let constraint_graph = Frozen::freeze(constraints.graph(definitions.len()));
425-
let constraint_sccs = {
426-
let constraint_graph = constraints.graph(definitions.len());
427-
let region_graph = &constraint_graph.region_graph(&constraints, fr_static);
428-
let sccs = ConstraintSccs::new_with_annotation(&region_graph, |r| {
429-
RegionTracker::new(r, &definitions[r])
430-
});
431-
Rc::new(sccs)
432-
};
433426

434427
if cfg!(debug_assertions) {
435428
sccs_info(infcx, &constraint_sccs);
@@ -548,21 +541,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
548541
}
549542

550543
NllRegionVariableOrigin::Placeholder(placeholder) => {
551-
// Each placeholder region is only visible from
552-
// its universe `ui` and its extensions. So we
553-
// can't just add it into `scc` unless the
554-
// universe of the scc can name this region.
555-
let scc_universe = self.scc_universe(scc);
556-
if scc_universe.can_name(placeholder.universe) {
557-
self.scc_values.add_element(scc, placeholder);
558-
} else {
559-
debug!(
560-
"init_free_and_bound_regions: placeholder {:?} is \
561-
not compatible with universe {:?} of its SCC {:?}",
562-
placeholder, scc_universe, scc,
563-
);
564-
self.add_incompatible_universe(scc);
565-
}
544+
self.scc_values.add_element(scc, placeholder);
566545
}
567546

568547
NllRegionVariableOrigin::Existential { .. } => {
@@ -744,23 +723,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
744723
/// (which is assured by iterating over SCCs in dependency order).
745724
#[instrument(skip(self), level = "debug")]
746725
fn compute_value_for_scc(&mut self, scc_a: ConstraintSccIndex) {
747-
let constraint_sccs = self.constraint_sccs.clone();
748-
749726
// Walk each SCC `B` such that `A: B`...
750-
for &scc_b in constraint_sccs.successors(scc_a) {
727+
for &scc_b in self.constraint_sccs.successors(scc_a) {
751728
debug!(?scc_b);
752-
753-
// ...and add elements from `B` into `A`. One complication
754-
// arises because of universes: If `B` contains something
755-
// that `A` cannot name, then `A` can only contain `B` if
756-
// it outlives static.
757-
if self.universe_compatible(scc_b, scc_a) {
758-
// `A` can name everything that is in `B`, so just
759-
// merge the bits.
760-
self.scc_values.add_region(scc_a, scc_b);
761-
} else {
762-
self.add_incompatible_universe(scc_a);
763-
}
729+
self.scc_values.add_region(scc_a, scc_b);
764730
}
765731

766732
// Now take member constraints into account.
@@ -886,35 +852,20 @@ impl<'tcx> RegionInferenceContext<'tcx> {
886852
/// in `scc_a`. Used during constraint propagation, and only once
887853
/// the value of `scc_b` has been computed.
888854
fn universe_compatible(&self, scc_b: ConstraintSccIndex, scc_a: ConstraintSccIndex) -> bool {
889-
let universe_a = self.constraint_sccs().annotation(scc_a).universe();
890-
let universe_b = self.constraint_sccs().annotation(scc_b).universe();
855+
let a_annotation = self.constraint_sccs().annotation(scc_a);
856+
let b_annotation = self.constraint_sccs().annotation(scc_b);
857+
let a_universe = a_annotation.universe();
891858

892-
// Quick check: if scc_b's declared universe is a subset of
859+
// If scc_b's declared universe is a subset of
893860
// scc_a's declared universe (typically, both are ROOT), then
894861
// it cannot contain any problematic universe elements.
895-
if universe_a.can_name(universe_b) {
862+
if a_universe.can_name(b_annotation.universe()) {
896863
return true;
897864
}
898865

899-
// Otherwise, we have to iterate over the universe elements in
900-
// B's value, and check whether all of them are nameable
901-
// from universe_a
902-
self.scc_values.placeholders_contained_in(scc_b).all(|p| universe_a.can_name(p.universe))
903-
}
904-
905-
/// Extend `scc` so that it can outlive some placeholder region
906-
/// from a universe it can't name; at present, the only way for
907-
/// this to be true is if `scc` outlives `'static`. This is
908-
/// actually stricter than necessary: ideally, we'd support bounds
909-
/// like `for<'a: 'b>` that might then allow us to approximate
910-
/// `'a` with `'b` and not `'static`. But it will have to do for
911-
/// now.
912-
fn add_incompatible_universe(&mut self, scc: ConstraintSccIndex) {
913-
debug!("add_incompatible_universe(scc={:?})", scc);
914-
915-
let fr_static = self.universal_regions.fr_static;
916-
self.scc_values.add_all_points(scc);
917-
self.scc_values.add_element(scc, fr_static);
866+
// Otherwise, there can be no placeholder in `b` with a too high
867+
// universe index to name from `a`.
868+
a_universe.can_name(b_annotation.max_placeholder_universe_reached)
918869
}
919870

920871
/// Once regions have been propagated, this method is used to see
@@ -1896,6 +1847,10 @@ impl<'tcx> RegionInferenceContext<'tcx> {
18961847

18971848
// This loop can be hot.
18981849
for constraint in outgoing_edges_from_graph {
1850+
if matches!(constraint.category, ConstraintCategory::IllegalUniverse) {
1851+
debug!("Ignoring illegal universe constraint: {constraint:?}");
1852+
continue;
1853+
}
18991854
handle_constraint(constraint);
19001855
}
19011856

compiler/rustc_middle/src/mir/query.rs

+3
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,9 @@ pub enum ConstraintCategory<'tcx> {
274274

275275
/// A constraint that doesn't correspond to anything the user sees.
276276
Internal,
277+
278+
/// An internal constraint derived from an illegal universe relation.
279+
IllegalUniverse,
277280
}
278281

279282
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]

0 commit comments

Comments
 (0)