Skip to content

Commit dee6d0f

Browse files
committed
Auto merge of #44269 - nikomatsakis:issue-43613-limit-cache-obligations, r=arielb1
limit and clear cache obligations opportunistically Keeping **all** the obligations for every projection is wasteful of memory and compilation time. We only really care about those subobligations that may inform the result of the projection (i.e., may help to resolve any inference variables that appear within). Therefore, we can clear the subobligations from the cache that don't potentially affect the result of the projection. On every cache hit, we also take the opportunity to check if the type variables have been resolved *yet* and, if so, clear out the pending obligations. Fixes #43613. r? @arielb1 NB -- not sure how to test for this. Probably we should add the #43613 test case to perf.
2 parents dead08c + c1dddce commit dee6d0f

File tree

4 files changed

+156
-8
lines changed

4 files changed

+156
-8
lines changed

src/librustc/infer/mod.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1160,6 +1160,18 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
11601160
value.fold_with(&mut r)
11611161
}
11621162

1163+
/// Returns true if `T` contains unresolved type variables. In the
1164+
/// process of visiting `T`, this will resolve (where possible)
1165+
/// type variables in `T`, but it never constructs the final,
1166+
/// resolved type, so it's more efficient than
1167+
/// `resolve_type_vars_if_possible()`.
1168+
pub fn any_unresolved_type_vars<T>(&self, value: &T) -> bool
1169+
where T: TypeFoldable<'tcx>
1170+
{
1171+
let mut r = resolve::UnresolvedTypeFinder::new(self);
1172+
value.visit_with(&mut r)
1173+
}
1174+
11631175
pub fn resolve_type_and_region_vars_if_possible<T>(&self, value: &T) -> T
11641176
where T: TypeFoldable<'tcx>
11651177
{

src/librustc/infer/resolve.rs

+38-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
use super::{InferCtxt, FixupError, FixupResult};
1212
use ty::{self, Ty, TyCtxt, TypeFoldable};
13-
use ty::fold::TypeFolder;
13+
use ty::fold::{TypeFolder, TypeVisitor};
1414

1515
///////////////////////////////////////////////////////////////////////////
1616
// OPPORTUNISTIC TYPE RESOLVER
@@ -80,6 +80,43 @@ impl<'a, 'gcx, 'tcx> TypeFolder<'gcx, 'tcx> for OpportunisticTypeAndRegionResolv
8080
}
8181
}
8282

83+
///////////////////////////////////////////////////////////////////////////
84+
// UNRESOLVED TYPE FINDER
85+
86+
/// The unresolved type **finder** walks your type and searches for
87+
/// type variables that don't yet have a value. They get pushed into a
88+
/// vector. It does not construct the fully resolved type (which might
89+
/// involve some hashing and so forth).
90+
pub struct UnresolvedTypeFinder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
91+
infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
92+
}
93+
94+
impl<'a, 'gcx, 'tcx> UnresolvedTypeFinder<'a, 'gcx, 'tcx> {
95+
pub fn new(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>) -> Self {
96+
UnresolvedTypeFinder { infcx }
97+
}
98+
}
99+
100+
impl<'a, 'gcx, 'tcx> TypeVisitor<'tcx> for UnresolvedTypeFinder<'a, 'gcx, 'tcx> {
101+
fn visit_ty(&mut self, t: Ty<'tcx>) -> bool {
102+
let t = self.infcx.shallow_resolve(t);
103+
if t.has_infer_types() {
104+
if let ty::TyInfer(_) = t.sty {
105+
// Since we called `shallow_resolve` above, this must
106+
// be an (as yet...) unresolved inference variable.
107+
true
108+
} else {
109+
// Otherwise, visit its contents.
110+
t.super_visit_with(self)
111+
}
112+
} else {
113+
// Micro-optimize: no inference types at all Can't have unresolved type
114+
// variables, no need to visit the contents.
115+
false
116+
}
117+
}
118+
}
119+
83120
///////////////////////////////////////////////////////////////////////////
84121
// FULL TYPE RESOLUTION
85122

src/librustc/traits/project.rs

+102-7
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ use super::VtableImplData;
2525
use super::util;
2626

2727
use hir::def_id::DefId;
28-
use infer::InferOk;
28+
use infer::{InferCtxt, InferOk};
2929
use infer::type_variable::TypeVariableOrigin;
3030
use rustc_data_structures::snapshot_map::{Snapshot, SnapshotMap};
3131
use syntax::ast;
@@ -416,7 +416,8 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
416416
// bounds. It might be the case that we want two distinct caches,
417417
// or else another kind of cache entry.
418418

419-
match infcx.projection_cache.borrow_mut().try_start(cache_key) {
419+
let cache_result = infcx.projection_cache.borrow_mut().try_start(cache_key);
420+
match cache_result {
420421
Ok(()) => { }
421422
Err(ProjectionCacheEntry::Ambiguous) => {
422423
// If we found ambiguity the last time, that generally
@@ -466,7 +467,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
466467
projection_ty);
467468
selcx.infcx().report_overflow_error(&obligation, false);
468469
}
469-
Err(ProjectionCacheEntry::NormalizedTy(ty)) => {
470+
Err(ProjectionCacheEntry::NormalizedTy(mut ty)) => {
470471
// If we find the value in the cache, then return it along
471472
// with the obligations that went along with it. Note
472473
// that, when using a fulfillment context, these
@@ -479,6 +480,21 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
479480
debug!("opt_normalize_projection_type: \
480481
found normalized ty `{:?}`",
481482
ty);
483+
484+
// Once we have inferred everything we need to know, we
485+
// can ignore the `obligations` from that point on.
486+
if !infcx.any_unresolved_type_vars(&ty.value) {
487+
infcx.projection_cache.borrow_mut().complete(cache_key);
488+
ty.obligations = vec![];
489+
}
490+
491+
push_paranoid_cache_value_obligation(infcx,
492+
param_env,
493+
projection_ty,
494+
cause,
495+
depth,
496+
&mut ty);
497+
482498
return Some(ty);
483499
}
484500
Err(ProjectionCacheEntry::Error) => {
@@ -527,7 +543,10 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
527543
obligations,
528544
}
529545
};
530-
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
546+
547+
let cache_value = prune_cache_value_obligations(infcx, &result);
548+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, cache_value);
549+
531550
Some(result)
532551
}
533552
Ok(ProjectedTy::NoProgress(projected_ty)) => {
@@ -538,7 +557,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
538557
value: projected_ty,
539558
obligations: vec![]
540559
};
541-
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
560+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, result.clone());
542561
Some(result)
543562
}
544563
Err(ProjectionTyError::TooManyCandidates) => {
@@ -562,6 +581,82 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
562581
}
563582
}
564583

584+
/// If there are unresolved type variables, then we need to include
585+
/// any subobligations that bind them, at least until those type
586+
/// variables are fully resolved.
587+
fn prune_cache_value_obligations<'a, 'gcx, 'tcx>(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
588+
result: &NormalizedTy<'tcx>)
589+
-> NormalizedTy<'tcx> {
590+
if !infcx.any_unresolved_type_vars(&result.value) {
591+
return NormalizedTy { value: result.value, obligations: vec![] };
592+
}
593+
594+
let mut obligations: Vec<_> =
595+
result.obligations
596+
.iter()
597+
.filter(|obligation| match obligation.predicate {
598+
// We found a `T: Foo<X = U>` predicate, let's check
599+
// if `U` references any unresolved type
600+
// variables. In principle, we only care if this
601+
// projection can help resolve any of the type
602+
// variables found in `result.value` -- but we just
603+
// check for any type variables here, for fear of
604+
// indirect obligations (e.g., we project to `?0`,
605+
// but we have `T: Foo<X = ?1>` and `?1: Bar<X =
606+
// ?0>`).
607+
ty::Predicate::Projection(ref data) =>
608+
!infcx.any_unresolved_type_vars(&data.ty()),
609+
610+
// We are only interested in `T: Foo<X = U>` predicates, whre
611+
// `U` references one of `unresolved_type_vars`. =)
612+
_ => false,
613+
})
614+
.cloned()
615+
.collect();
616+
617+
obligations.shrink_to_fit();
618+
619+
NormalizedTy { value: result.value, obligations }
620+
}
621+
622+
/// Whenever we give back a cache result for a projection like `<T as
623+
/// Trait>::Item ==> X`, we *always* include the obligation to prove
624+
/// that `T: Trait` (we may also include some other obligations). This
625+
/// may or may not be necessary -- in principle, all the obligations
626+
/// that must be proven to show that `T: Trait` were also returned
627+
/// when the cache was first populated. But there are some vague concerns,
628+
/// and so we take the precatuionary measure of including `T: Trait` in
629+
/// the result:
630+
///
631+
/// Concern #1. The current setup is fragile. Perhaps someone could
632+
/// have failed to prove the concerns from when the cache was
633+
/// populated, but also not have used a snapshot, in which case the
634+
/// cache could remain populated even though `T: Trait` has not been
635+
/// shown. In this case, the "other code" is at fault -- when you
636+
/// project something, you are supposed to either have a snapshot or
637+
/// else prove all the resulting obligations -- but it's still easy to
638+
/// get wrong.
639+
///
640+
/// Concern #2. Even within the snapshot, if those original
641+
/// obligations are not yet proven, then we are able to do projections
642+
/// that may yet turn out to be wrong. This *may* lead to some sort
643+
/// of trouble, though we don't have a concrete example of how that
644+
/// can occur yet. But it seems risky at best.
645+
fn push_paranoid_cache_value_obligation<'a, 'gcx, 'tcx>(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
646+
param_env: ty::ParamEnv<'tcx>,
647+
projection_ty: ty::ProjectionTy<'tcx>,
648+
cause: ObligationCause<'tcx>,
649+
depth: usize,
650+
result: &mut NormalizedTy<'tcx>)
651+
{
652+
let trait_ref = projection_ty.trait_ref(infcx.tcx).to_poly_trait_ref();
653+
let trait_obligation = Obligation { cause,
654+
recursion_depth: depth,
655+
param_env,
656+
predicate: trait_ref.to_predicate() };
657+
result.obligations.push(trait_obligation);
658+
}
659+
565660
/// If we are projecting `<T as Trait>::Item`, but `T: Trait` does not
566661
/// hold. In various error cases, we cannot generate a valid
567662
/// normalized projection. Therefore, we create an inference variable
@@ -1493,10 +1588,10 @@ impl<'tcx> ProjectionCache<'tcx> {
14931588
}
14941589

14951590
/// Indicates that `key` was normalized to `value`.
1496-
fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: &NormalizedTy<'tcx>) {
1591+
fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: NormalizedTy<'tcx>) {
14971592
debug!("ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}",
14981593
key, value);
1499-
let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value.clone()));
1594+
let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value));
15001595
assert!(!fresh_key, "never started projecting `{:?}`", key);
15011596
}
15021597

src/librustc/ty/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,10 @@ impl<'tcx> PolyProjectionPredicate<'tcx> {
10171017
// levels.
10181018
ty::Binder(self.0.projection_ty.trait_ref(tcx))
10191019
}
1020+
1021+
pub fn ty(&self) -> Binder<Ty<'tcx>> {
1022+
Binder(self.skip_binder().ty) // preserves binding levels
1023+
}
10201024
}
10211025

10221026
pub trait ToPolyTraitRef<'tcx> {

0 commit comments

Comments
 (0)