Skip to content

Commit 372912b

Browse files
committed
Produce slightly fewer placeholder constraints
This seems to hit the sweet spot between enabling debugging and being efficient. Somewhat cleaner representation that separates concerns Avoid handling placeholders for MIR bodies without them Determine once and for all if there are higher-kinded concerns and nope out This also changes how annotations for SCCs work. This version finally does away with all kinds of universe handling It does so in a preprocessing step that now also removes higher-ranked concerns from type tests. Fix handling of opaque regions
1 parent 54bcafb commit 372912b

File tree

13 files changed

+681
-495
lines changed

13 files changed

+681
-495
lines changed

compiler/rustc_borrowck/src/constraints/mod.rs

Lines changed: 67 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
use std::fmt;
22
use std::ops::Index;
33

4+
use rustc_data_structures::fx::FxHashSet;
5+
use rustc_data_structures::graph::scc;
46
use rustc_index::{IndexSlice, IndexVec};
57
use rustc_infer::infer::NllRegionVariableOrigin;
68
use rustc_middle::mir::ConstraintCategory;
79
use rustc_middle::ty::{RegionVid, TyCtxt, VarianceDiagInfo};
810
use rustc_span::Span;
911
use tracing::{debug, instrument};
1012

11-
use crate::region_infer::{ConstraintSccs, RegionDefinition, RegionTracker};
13+
use crate::region_infer::{RegionDefinition, RegionTracker, SccAnnotations};
1214
use crate::type_check::Locations;
1315
use crate::universal_regions::UniversalRegions;
1416

@@ -58,16 +60,18 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
5860
/// Computes cycles (SCCs) in the graph of regions. In particular,
5961
/// find all regions R1, R2 such that R1: R2 and R2: R1 and group
6062
/// them into an SCC, and find the relationships between SCCs.
61-
pub(crate) fn compute_sccs(
63+
pub(crate) fn compute_sccs<
64+
A: scc::Annotation,
65+
AA: scc::Annotations<RegionVid, ConstraintSccIndex, A>,
66+
>(
6267
&self,
6368
static_region: RegionVid,
64-
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
65-
) -> ConstraintSccs {
66-
let constraint_graph = self.graph(definitions.len());
69+
num_region_vars: usize,
70+
annotations: &mut AA,
71+
) -> scc::Sccs<RegionVid, ConstraintSccIndex> {
72+
let constraint_graph = self.graph(num_region_vars);
6773
let region_graph = &constraint_graph.region_graph(self, static_region);
68-
ConstraintSccs::new_with_annotation(&region_graph, |r| {
69-
RegionTracker::new(r, &definitions[r])
70-
})
74+
scc::Sccs::new_with_annotation(&region_graph, annotations)
7175
}
7276

7377
/// There is a placeholder violation; add a requirement
@@ -126,88 +130,91 @@ impl<'tcx> OutlivesConstraintSet<'tcx> {
126130
/// Every constraint added by this method is an
127131
/// internal `IllegalUniverse` constraint.
128132
#[instrument(skip(self, universal_regions, definitions))]
129-
pub(crate) fn add_outlives_static(
133+
pub(crate) fn add_outlives_static<'d>(
130134
&mut self,
131135
universal_regions: &UniversalRegions<'tcx>,
132-
definitions: &IndexVec<RegionVid, RegionDefinition<'tcx>>,
133-
) -> ConstraintSccs {
136+
definitions: &'d IndexVec<RegionVid, RegionDefinition<'tcx>>,
137+
) -> (scc::Sccs<RegionVid, ConstraintSccIndex>, IndexVec<ConstraintSccIndex, RegionTracker>)
138+
{
134139
let fr_static = universal_regions.fr_static;
135-
let sccs = self.compute_sccs(fr_static, definitions);
140+
let mut annotations = SccAnnotations::init(definitions);
141+
let sccs = self.compute_sccs(fr_static, definitions.len(), &mut annotations);
136142

137-
// Changed to `true` if we added any constraints to `self` and need to
138-
// recompute SCCs.
139-
let mut added_constraints = false;
143+
// Is this SCC already outliving static directly or transitively?
144+
let mut outlives_static = FxHashSet::default();
140145

141146
for scc in sccs.all_sccs() {
142-
let annotation = sccs.annotation(scc);
147+
let annotation: RegionTracker = annotations.scc_to_annotation[scc];
148+
if scc == sccs.scc(fr_static) {
149+
// No use adding 'static: 'static.
150+
continue;
151+
}
143152

144153
// If this SCC participates in a universe violation
145154
// e.g. if it reaches a region with a universe smaller than
146155
// the largest region reached, add a requirement that it must
147156
// outlive `'static`. Here we get to know which reachable region
148157
// caused the violation.
149158
if let Some(to) = annotation.universe_violation() {
150-
// Optimisation opportunity: this will potentially add more constraints
151-
// than needed for correctness, since an SCC upstream of another with
152-
// a universe violation will "infect" its downstream SCCs to also
153-
// outlive static. However, some of those may be useful for error
154-
// reporting.
155-
added_constraints = true;
159+
outlives_static.insert(scc);
156160
self.add_placeholder_violation_constraint(
157-
annotation.representative,
158-
annotation.representative,
161+
annotation.representative_rvid(),
162+
annotation.representative_rvid(),
159163
to,
160164
fr_static,
161165
);
162166
}
163167
}
164168

165-
// The second kind of violation: a placeholder reaching another placeholder.
166-
// OPTIMIZATION: This one is even more optimisable since it adds constraints for every
167-
// placeholder in an SCC.
168-
for rvid in definitions.iter_enumerated().filter_map(|(rvid, definition)| {
169-
if matches!(definition.origin, NllRegionVariableOrigin::Placeholder { .. }) {
170-
Some(rvid)
171-
} else {
172-
None
173-
}
174-
}) {
175-
let scc = sccs.scc(rvid);
176-
let annotation = sccs.annotation(scc);
169+
// Note: it's possible to sort this iterator by SCC and get dependency order,
170+
// which makes it easy to only add only one constraint per future cycle.
171+
// However, this worsens diagnostics and requires iterating over
172+
// all successors to determine if we outlive static transitively,
173+
// a cost you pay even if you have no placeholders.
174+
let placeholders_and_sccs =
175+
definitions.iter_enumerated().filter_map(|(rvid, definition)| {
176+
if matches!(definition.origin, NllRegionVariableOrigin::Placeholder { .. }) {
177+
Some((sccs.scc(rvid), rvid))
178+
} else {
179+
None
180+
}
181+
});
177182

178-
// Unwrap safety: since this is our SCC it must contain us, which is
179-
// at worst min AND max, but it has at least one or there is a bug.
180-
let min = annotation.min_reachable_placeholder.unwrap();
181-
let max = annotation.max_reachable_placeholder.unwrap();
183+
// The second kind of violation: a placeholder reaching another placeholder.
184+
for (scc, rvid) in placeholders_and_sccs {
185+
let annotation = annotations.scc_to_annotation[scc];
182186

183-
// Good path: Nothing to see here, at least no other placeholders!
184-
if min == max {
187+
if sccs.scc(fr_static) == scc || outlives_static.contains(&scc) {
188+
debug!("{:?} already outlives (or is) static", annotation.representative_rvid());
185189
continue;
186190
}
187191

188-
// Bad path: figure out who we illegally reached.
189-
// Note that this will prefer the representative if it is a
190-
// placeholder, since the representative has the smallest index!
191-
let other_placeholder = if min != rvid { min } else { max };
192-
193-
debug!(
194-
"Placeholder {rvid:?} of SCC {scc:?} reaches other placeholder {other_placeholder:?}"
195-
);
196-
added_constraints = true;
197-
self.add_placeholder_violation_constraint(
198-
annotation.representative,
199-
rvid,
200-
other_placeholder,
201-
fr_static,
202-
);
192+
if let Some(other_placeholder) = annotation.reaches_other_placeholder(rvid) {
193+
debug!(
194+
"Placeholder {rvid:?} of SCC {scc:?} reaches other placeholder {other_placeholder:?}"
195+
);
196+
outlives_static.insert(scc);
197+
self.add_placeholder_violation_constraint(
198+
annotation.representative_rvid(),
199+
rvid,
200+
other_placeholder,
201+
fr_static,
202+
);
203+
};
203204
}
204205

205-
if added_constraints {
206+
if !outlives_static.is_empty() {
207+
debug!("The following SCCs had :'static constraints added: {:?}", outlives_static);
208+
let mut annotations = SccAnnotations::init(definitions);
209+
206210
// We changed the constraint set and so must recompute SCCs.
207-
self.compute_sccs(fr_static, definitions)
211+
(
212+
self.compute_sccs(fr_static, definitions.len(), &mut annotations),
213+
annotations.scc_to_annotation,
214+
)
208215
} else {
209216
// If we didn't add any back-edges; no more work needs doing
210-
sccs
217+
(sccs, annotations.scc_to_annotation)
211218
}
212219
}
213220
}

compiler/rustc_borrowck/src/diagnostics/opaque_suggestions.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,9 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for FindOpaqueRegion<'_, 'tcx> {
156156
let opaque_region_vid = self.regioncx.to_region_vid(opaque_region);
157157

158158
// Find a path between the borrow region and our opaque capture.
159-
if let Some((path, _)) =
160-
self.regioncx.find_constraint_paths_between_regions(self.borrow_region, |r| {
161-
r == opaque_region_vid
162-
})
159+
if let Some((path, _)) = self
160+
.regioncx
161+
.constraint_path_between_regions(self.borrow_region, opaque_region_vid)
163162
{
164163
for constraint in path {
165164
// If we find a call in this path, then check if it defines the opaque.

compiler/rustc_borrowck/src/diagnostics/region_errors.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
212212

213213
// find higher-ranked trait bounds bounded to the generic associated types
214214
let scc = self.regioncx.constraint_sccs().scc(lower_bound);
215+
215216
let placeholder: ty::PlaceholderRegion = self.regioncx.placeholder_representative(scc)?;
217+
216218
let placeholder_id = placeholder.bound.kind.get_id()?.as_local()?;
217219
let gat_hir_id = self.infcx.tcx.local_def_id_to_hir_id(placeholder_id);
218220
let generics_impl =
@@ -221,7 +223,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
221223
let mut hrtb_bounds = vec![];
222224

223225
for pred in generics_impl.predicates {
224-
let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }) = pred
226+
let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }): &rustc_hir::WherePredicate<'tcx> = pred
225227
else {
226228
continue;
227229
};
@@ -329,6 +331,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
329331
span: type_test_span,
330332
});
331333

334+
debug!("This bound caused outlives static: {:?}", type_test.derived_from);
335+
332336
// Add notes and suggestions for the case of 'static lifetime
333337
// implied but not specified when a generic associated types
334338
// are from higher-ranked trait bounds

compiler/rustc_borrowck/src/nll.rs

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ use std::{env, io};
77

88
use polonius_engine::{Algorithm, Output};
99
use rustc_data_structures::fx::FxIndexMap;
10+
use rustc_data_structures::graph::scc::Sccs;
1011
use rustc_hir::def_id::LocalDefId;
11-
use rustc_index::IndexSlice;
12+
use rustc_index::{IndexSlice, IndexVec};
13+
use rustc_infer::infer::NllRegionVariableOrigin;
14+
use rustc_infer::infer::region_constraints::RegionVariableInfo;
1215
use rustc_middle::mir::pretty::{PrettyPrintMirOptions, dump_mir_with_options};
1316
use rustc_middle::mir::{
1417
Body, ClosureOutlivesSubject, ClosureRegionRequirements, PassWhere, Promoted, create_dump_file,
1518
dump_enabled, dump_mir,
1619
};
1720
use rustc_middle::ty::print::with_no_trimmed_paths;
18-
use rustc_middle::ty::{self, OpaqueHiddenType, TyCtxt};
21+
use rustc_middle::ty::{self, OpaqueHiddenType, RegionVid, TyCtxt};
1922
use rustc_mir_dataflow::ResultsCursor;
2023
use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
2124
use rustc_mir_dataflow::move_paths::MoveData;
@@ -25,11 +28,14 @@ use rustc_span::symbol::sym;
2528
use tracing::{debug, instrument};
2629

2730
use crate::borrow_set::BorrowSet;
31+
use crate::constraints::{ConstraintSccIndex, OutlivesConstraintSet};
2832
use crate::consumers::ConsumerOptions;
2933
use crate::diagnostics::RegionErrors;
3034
use crate::facts::{AllFacts, AllFactsExt, RustcFacts};
3135
use crate::location::LocationTable;
32-
use crate::region_infer::RegionInferenceContext;
36+
use crate::region_infer::{
37+
RegionDefinition, RegionInferenceContext, Representative, SccAnnotations, TypeTest,
38+
};
3339
use crate::type_check::{self, MirTypeckRegionConstraints, MirTypeckResults};
3440
use crate::universal_regions::UniversalRegions;
3541
use crate::{BorrowckInferCtxt, polonius, renumber};
@@ -152,6 +158,15 @@ pub(crate) fn compute_regions<'a, 'tcx>(
152158
infcx.set_tainted_by_errors(guar);
153159
}
154160

161+
let (type_tests, sccs, region_definitions, scc_representatives) =
162+
rewrite_higher_kinded_outlives_as_constraints(
163+
&mut outlives_constraints,
164+
&var_origins,
165+
&universal_regions,
166+
type_tests,
167+
infcx.tcx,
168+
);
169+
155170
let mut regioncx = RegionInferenceContext::new(
156171
infcx,
157172
var_origins,
@@ -163,6 +178,9 @@ pub(crate) fn compute_regions<'a, 'tcx>(
163178
type_tests,
164179
liveness_constraints,
165180
elements,
181+
sccs,
182+
region_definitions,
183+
scc_representatives,
166184
);
167185

168186
// If requested: dump NLL facts, and run legacy polonius analysis.
@@ -208,6 +226,78 @@ pub(crate) fn compute_regions<'a, 'tcx>(
208226
}
209227
}
210228

229+
fn rewrite_higher_kinded_outlives_as_constraints<'tcx>(
230+
outlives_constraints: &mut OutlivesConstraintSet<'tcx>,
231+
var_origins: &IndexVec<RegionVid, RegionVariableInfo>,
232+
universal_regions: &UniversalRegions<'tcx>,
233+
type_tests: Vec<TypeTest<'tcx>>,
234+
tcx: TyCtxt<'tcx>,
235+
) -> (
236+
Vec<TypeTest<'tcx>>,
237+
Sccs<ty::RegionVid, ConstraintSccIndex>,
238+
IndexVec<ty::RegionVid, RegionDefinition<'tcx>>,
239+
IndexVec<ConstraintSccIndex, Representative>,
240+
) {
241+
// Create a RegionDefinition for each inference variable. This happens here because
242+
// it allows us to sneak in a cheap check for placeholders. Otherwise, its proper home
243+
// is in `RegionInferenceContext::new()`, probably.
244+
let (definitions, has_placeholders) = {
245+
let mut definitions = IndexVec::with_capacity(var_origins.len());
246+
let mut has_placeholders = false;
247+
248+
for info in var_origins.iter() {
249+
let definition = RegionDefinition::new(info);
250+
has_placeholders |=
251+
matches!(definition.origin, NllRegionVariableOrigin::Placeholder(_));
252+
definitions.push(definition);
253+
}
254+
255+
// Add external names from universal regions in fun function definitions.
256+
for (external_name, variable) in universal_regions.named_universal_regions() {
257+
debug!("region {:?} has external name {:?}", variable, external_name);
258+
definitions[variable].external_name = Some(external_name);
259+
}
260+
(definitions, has_placeholders)
261+
};
262+
263+
if !has_placeholders {
264+
debug!("No placeholder regions found; skipping rewriting logic!");
265+
let mut annotations = SccAnnotations::init(&definitions);
266+
let sccs = outlives_constraints.compute_sccs(
267+
universal_regions.fr_static,
268+
definitions.len(),
269+
&mut annotations,
270+
);
271+
let annotations = annotations.scc_to_annotation;
272+
return (type_tests, sccs, definitions, annotations);
273+
}
274+
275+
debug!("Placeholders present; activating placeholder handling logic!");
276+
let (sccs, scc_annotations) =
277+
outlives_constraints.add_outlives_static(&universal_regions, &definitions);
278+
279+
// Rewrite universe-violating type tests into outlives 'static while we remember
280+
// which universes go where.
281+
let type_tests = type_tests
282+
.into_iter()
283+
.map(|type_test| {
284+
type_test.rewrite_higher_kinded_constraints(
285+
&sccs,
286+
&scc_annotations,
287+
universal_regions,
288+
tcx,
289+
)
290+
})
291+
.collect();
292+
293+
let representatives = scc_annotations
294+
.into_iter()
295+
.map(|rich_annotation| rich_annotation.into_representative())
296+
.collect();
297+
298+
(type_tests, sccs, definitions, representatives)
299+
}
300+
211301
/// `-Zdump-mir=nll` dumps MIR annotated with NLL specific information:
212302
/// - free regions
213303
/// - inferred region values

compiler/rustc_borrowck/src/region_infer/dump_mir.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ impl<'tcx> RegionInferenceContext<'tcx> {
4242
for region in self.regions() {
4343
writeln!(
4444
out,
45-
"| {r:rw$?} | {ui:4?} | {v}",
45+
"| {r:rw$?} | {v}",
4646
r = region,
4747
rw = REGION_WIDTH,
48-
ui = self.region_universe(region),
4948
v = self.region_value_str(region),
5049
)?;
5150
}

0 commit comments

Comments
 (0)