Skip to content

Commit 7534f73

Browse files
committed
clear out projection subobligations after they are processed
After a projection was processed, its derived subobligations no longer need any processing when encountered, and can be removed. This improves the status of #43787. This is actually complementary to #43938 - that PR fixes selection caching (and @remram44's example, which "accidentally" worked because of the buggy projection caching) while this PR fixes projection caching
1 parent 78e95bb commit 7534f73

File tree

3 files changed

+112
-19
lines changed

3 files changed

+112
-19
lines changed

src/librustc/traits/fulfill.rs

+3
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,9 @@ impl<'a, 'gcx, 'tcx> FulfillmentContext<'tcx> {
251251
});
252252
debug!("select: outcome={:?}", outcome);
253253

254+
// FIXME: if we kept the original cache key, we could mark projection
255+
// obligations as complete for the projection cache here.
256+
254257
errors.extend(
255258
outcome.errors.into_iter()
256259
.map(|e| to_fulfillment_error(e)));

src/librustc/traits/project.rs

+100-16
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,13 @@ struct ProjectionTyCandidateSet<'tcx> {
121121
///
122122
/// for<...> <T as Trait>::U == V
123123
///
124-
/// If successful, this may result in additional obligations.
124+
/// If successful, this may result in additional obligations. Also returns
125+
/// the projection cache key used to track these additional obligations.
125126
pub fn poly_project_and_unify_type<'cx, 'gcx, 'tcx>(
126127
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
127128
obligation: &PolyProjectionObligation<'tcx>)
128-
-> Result<Option<Vec<PredicateObligation<'tcx>>>, MismatchedProjectionTypes<'tcx>>
129+
-> Result<Option<Vec<PredicateObligation<'tcx>>>,
130+
MismatchedProjectionTypes<'tcx>>
129131
{
130132
debug!("poly_project_and_unify_type(obligation={:?})",
131133
obligation);
@@ -161,7 +163,8 @@ pub fn poly_project_and_unify_type<'cx, 'gcx, 'tcx>(
161163
fn project_and_unify_type<'cx, 'gcx, 'tcx>(
162164
selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
163165
obligation: &ProjectionObligation<'tcx>)
164-
-> Result<Option<Vec<PredicateObligation<'tcx>>>, MismatchedProjectionTypes<'tcx>>
166+
-> Result<Option<Vec<PredicateObligation<'tcx>>>,
167+
MismatchedProjectionTypes<'tcx>>
165168
{
166169
debug!("project_and_unify_type(obligation={:?})",
167170
obligation);
@@ -396,6 +399,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
396399
let infcx = selcx.infcx();
397400

398401
let projection_ty = infcx.resolve_type_vars_if_possible(&projection_ty);
402+
let cache_key = ProjectionCacheKey { ty: projection_ty };
399403

400404
debug!("opt_normalize_projection_type(\
401405
projection_ty={:?}, \
@@ -411,7 +415,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
411415
// bounds. It might be the case that we want two distinct caches,
412416
// or else another kind of cache entry.
413417

414-
match infcx.projection_cache.borrow_mut().try_start(projection_ty) {
418+
match infcx.projection_cache.borrow_mut().try_start(cache_key) {
415419
Ok(()) => { }
416420
Err(ProjectionCacheEntry::Ambiguous) => {
417421
// If we found ambiguity the last time, that generally
@@ -522,7 +526,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
522526
obligations,
523527
}
524528
};
525-
infcx.projection_cache.borrow_mut().complete(projection_ty, &result);
529+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
526530
Some(result)
527531
}
528532
Ok(ProjectedTy::NoProgress(projected_ty)) => {
@@ -533,14 +537,14 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
533537
value: projected_ty,
534538
obligations: vec![]
535539
};
536-
infcx.projection_cache.borrow_mut().complete(projection_ty, &result);
540+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
537541
Some(result)
538542
}
539543
Err(ProjectionTyError::TooManyCandidates) => {
540544
debug!("opt_normalize_projection_type: \
541545
too many candidates");
542546
infcx.projection_cache.borrow_mut()
543-
.ambiguous(projection_ty);
547+
.ambiguous(cache_key);
544548
None
545549
}
546550
Err(ProjectionTyError::TraitSelectionError(_)) => {
@@ -551,7 +555,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
551555
// reported later
552556

553557
infcx.projection_cache.borrow_mut()
554-
.error(projection_ty);
558+
.error(cache_key);
555559
Some(normalize_to_error(selcx, param_env, projection_ty, cause, depth))
556560
}
557561
}
@@ -1323,8 +1327,62 @@ fn assoc_ty_def<'cx, 'gcx, 'tcx>(
13231327

13241328
// # Cache
13251329

1330+
/// The projection cache. Unlike the standard caches, this can
1331+
/// include infcx-dependent type variables - therefore, we have to roll
1332+
/// the cache back each time we roll a snapshot back, to avoid assumptions
1333+
/// on yet-unresolved inference variables. Types with skolemized regions
1334+
/// also have to be removed when the respective snapshot ends.
1335+
///
1336+
/// Because of that, projection cache entries can be "stranded" and left
1337+
/// inaccessible when type variables inside the key are resolved. We make no
1338+
/// attempt to recover or remove "stranded" entries, but rather let them be
1339+
/// (for the lifetime of the infcx).
1340+
///
1341+
/// Entries in the projection cache might contain inference variables
1342+
/// that will be resolved by obligations on the projection cache entry - e.g.
1343+
/// when a type parameter in the associated type is constrained through
1344+
/// an "RFC 447" projection on the impl.
1345+
///
1346+
/// When working with a fulfillment context, the derived obligations of each
1347+
/// projection cache entry will be registered on the fulfillcx, so any users
1348+
/// that can wait for a fulfillcx fixed point need not care about this. However,
1349+
/// users that don't wait for a fixed point (e.g. trait evaluation) have to
1350+
/// resolve the obligations themselves to make sure the projected result is
1351+
/// ok and avoid issues like #43132.
1352+
///
1353+
/// If that is done, after evaluation the obligations, it is a good idea to
1354+
/// call `ProjectionCache::complete` to make sure the obligations won't be
1355+
/// re-evaluated and avoid an exponential worst-case.
1356+
///
1357+
/// FIXME: we probably also want some sort of cross-infcx cache here to
1358+
/// reduce the amount of duplication. Let's see what we get with the Chalk
1359+
/// reforms.
13261360
pub struct ProjectionCache<'tcx> {
1327-
map: SnapshotMap<ty::ProjectionTy<'tcx>, ProjectionCacheEntry<'tcx>>,
1361+
map: SnapshotMap<ProjectionCacheKey<'tcx>, ProjectionCacheEntry<'tcx>>,
1362+
}
1363+
1364+
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
1365+
pub struct ProjectionCacheKey<'tcx> {
1366+
ty: ty::ProjectionTy<'tcx>
1367+
}
1368+
1369+
impl<'cx, 'gcx, 'tcx> ProjectionCacheKey<'tcx> {
1370+
pub fn from_poly_projection_predicate(selcx: &mut SelectionContext<'cx, 'gcx, 'tcx>,
1371+
predicate: &ty::PolyProjectionPredicate<'tcx>)
1372+
-> Option<Self>
1373+
{
1374+
let infcx = selcx.infcx();
1375+
// We don't do cross-snapshot caching of obligations with escaping regions,
1376+
// so there's no cache key to use
1377+
infcx.tcx.no_late_bound_regions(&predicate)
1378+
.map(|predicate| ProjectionCacheKey {
1379+
// We don't attempt to match up with a specific type-variable state
1380+
// from a specific call to `opt_normalize_projection_type` - if
1381+
// there's no precise match, the original cache entry is "stranded"
1382+
// anyway.
1383+
ty: infcx.resolve_type_vars_if_possible(&predicate.projection_ty)
1384+
})
1385+
}
13281386
}
13291387

13301388
#[derive(Clone, Debug)]
@@ -1337,7 +1395,7 @@ enum ProjectionCacheEntry<'tcx> {
13371395

13381396
// NB: intentionally not Clone
13391397
pub struct ProjectionCacheSnapshot {
1340-
snapshot: Snapshot
1398+
snapshot: Snapshot,
13411399
}
13421400

13431401
impl<'tcx> ProjectionCache<'tcx> {
@@ -1356,7 +1414,7 @@ impl<'tcx> ProjectionCache<'tcx> {
13561414
}
13571415

13581416
pub fn rollback_skolemized(&mut self, snapshot: &ProjectionCacheSnapshot) {
1359-
self.map.partial_rollback(&snapshot.snapshot, &|k| k.has_re_skol());
1417+
self.map.partial_rollback(&snapshot.snapshot, &|k| k.ty.has_re_skol());
13601418
}
13611419

13621420
pub fn commit(&mut self, snapshot: ProjectionCacheSnapshot) {
@@ -1366,7 +1424,7 @@ impl<'tcx> ProjectionCache<'tcx> {
13661424
/// Try to start normalize `key`; returns an error if
13671425
/// normalization already occurred (this error corresponds to a
13681426
/// cache hit, so it's actually a good thing).
1369-
fn try_start(&mut self, key: ty::ProjectionTy<'tcx>)
1427+
fn try_start(&mut self, key: ProjectionCacheKey<'tcx>)
13701428
-> Result<(), ProjectionCacheEntry<'tcx>> {
13711429
if let Some(entry) = self.map.get(&key) {
13721430
return Err(entry.clone());
@@ -1377,25 +1435,51 @@ impl<'tcx> ProjectionCache<'tcx> {
13771435
}
13781436

13791437
/// Indicates that `key` was normalized to `value`.
1380-
fn complete(&mut self, key: ty::ProjectionTy<'tcx>, value: &NormalizedTy<'tcx>) {
1381-
debug!("ProjectionCacheEntry::complete: adding cache entry: key={:?}, value={:?}",
1438+
fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: &NormalizedTy<'tcx>) {
1439+
debug!("ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}",
13821440
key, value);
13831441
let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value.clone()));
13841442
assert!(!fresh_key, "never started projecting `{:?}`", key);
13851443
}
13861444

1445+
/// Mark the relevant projection cache key as having its derived obligations
1446+
/// complete, so they won't have to be re-computed (this is OK to do in a
1447+
/// snapshot - if the snapshot is rolled back, the obligations will be
1448+
/// marked as incomplete again).
1449+
pub fn complete(&mut self, key: ProjectionCacheKey<'tcx>) {
1450+
let ty = match self.map.get(&key) {
1451+
Some(&ProjectionCacheEntry::NormalizedTy(ref ty)) => {
1452+
debug!("ProjectionCacheEntry::complete({:?}) - completing {:?}",
1453+
key, ty);
1454+
ty.value
1455+
}
1456+
ref value => {
1457+
// Type inference could "strand behind" old cache entries. Leave
1458+
// them alone for now.
1459+
debug!("ProjectionCacheEntry::complete({:?}) - ignoring {:?}",
1460+
key, value);
1461+
return
1462+
}
1463+
};
1464+
1465+
self.map.insert(key, ProjectionCacheEntry::NormalizedTy(Normalized {
1466+
value: ty,
1467+
obligations: vec![]
1468+
}));
1469+
}
1470+
13871471
/// Indicates that trying to normalize `key` resulted in
13881472
/// ambiguity. No point in trying it again then until we gain more
13891473
/// type information (in which case, the "fully resolved" key will
13901474
/// be different).
1391-
fn ambiguous(&mut self, key: ty::ProjectionTy<'tcx>) {
1475+
fn ambiguous(&mut self, key: ProjectionCacheKey<'tcx>) {
13921476
let fresh = self.map.insert(key, ProjectionCacheEntry::Ambiguous);
13931477
assert!(!fresh, "never started projecting `{:?}`", key);
13941478
}
13951479

13961480
/// Indicates that trying to normalize `key` resulted in
13971481
/// error.
1398-
fn error(&mut self, key: ty::ProjectionTy<'tcx>) {
1482+
fn error(&mut self, key: ProjectionCacheKey<'tcx>) {
13991483
let fresh = self.map.insert(key, ProjectionCacheEntry::Error);
14001484
assert!(!fresh, "never started projecting `{:?}`", key);
14011485
}

src/librustc/traits/select.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use self::EvaluationResult::*;
1616
use super::coherence;
1717
use super::DerivedObligationCause;
1818
use super::project;
19-
use super::project::{normalize_with_depth, Normalized};
19+
use super::project::{normalize_with_depth, Normalized, ProjectionCacheKey};
2020
use super::{PredicateObligation, TraitObligation, ObligationCause};
2121
use super::{ObligationCauseCode, BuiltinDerivedObligation, ImplDerivedObligation};
2222
use super::{SelectionError, Unimplemented, OutputTypeParameterMismatch};
@@ -655,8 +655,14 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
655655
let project_obligation = obligation.with(data.clone());
656656
match project::poly_project_and_unify_type(self, &project_obligation) {
657657
Ok(Some(subobligations)) => {
658-
self.evaluate_predicates_recursively(previous_stack,
659-
subobligations.iter())
658+
let result = self.evaluate_predicates_recursively(previous_stack,
659+
subobligations.iter());
660+
if let Some(key) =
661+
ProjectionCacheKey::from_poly_projection_predicate(self, data)
662+
{
663+
self.infcx.projection_cache.borrow_mut().complete(key);
664+
}
665+
result
660666
}
661667
Ok(None) => {
662668
EvaluatedToAmbig

0 commit comments

Comments
 (0)