Skip to content

Commit 596c9a7

Browse files
committed
limit and clear cache obligations opportunistically
Keep **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 rust-lang#43613
1 parent 423251a commit 596c9a7

File tree

4 files changed

+111
-8
lines changed

4 files changed

+111
-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

+57-7
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use super::VtableImplData;
2424
use super::util;
2525

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

418-
match infcx.projection_cache.borrow_mut().try_start(cache_key) {
418+
let cache_result = infcx.projection_cache.borrow_mut().try_start(cache_key);
419+
match cache_result {
419420
Ok(()) => { }
420421
Err(ProjectionCacheEntry::Ambiguous) => {
421422
// If we found ambiguity the last time, that generally
@@ -465,7 +466,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
465466
projection_ty);
466467
selcx.infcx().report_overflow_error(&obligation, false);
467468
}
468-
Err(ProjectionCacheEntry::NormalizedTy(ty)) => {
469+
Err(ProjectionCacheEntry::NormalizedTy(mut ty)) => {
469470
// If we find the value in the cache, then return it along
470471
// with the obligations that went along with it. Note
471472
// that, when using a fulfillment context, these
@@ -478,6 +479,14 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
478479
debug!("opt_normalize_projection_type: \
479480
found normalized ty `{:?}`",
480481
ty);
482+
483+
// Once we have inferred everything we need to know, we
484+
// can ignore the `obligations` from that point on.
485+
if !infcx.any_unresolved_type_vars(&ty.value) {
486+
infcx.projection_cache.borrow_mut().complete(cache_key);
487+
ty.obligations = vec![];
488+
}
489+
481490
return Some(ty);
482491
}
483492
Err(ProjectionCacheEntry::Error) => {
@@ -526,7 +535,10 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
526535
obligations,
527536
}
528537
};
529-
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
538+
539+
let cache_value = prune_cache_value_obligations(infcx, &result);
540+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, cache_value);
541+
530542
Some(result)
531543
}
532544
Ok(ProjectedTy::NoProgress(projected_ty)) => {
@@ -537,7 +549,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
537549
value: projected_ty,
538550
obligations: vec![]
539551
};
540-
infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
552+
infcx.projection_cache.borrow_mut().insert_ty(cache_key, result.clone());
541553
Some(result)
542554
}
543555
Err(ProjectionTyError::TooManyCandidates) => {
@@ -561,6 +573,44 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
561573
}
562574
}
563575

576+
/// If there are unresolved type variables, then we need to include
577+
/// any subobligations that bind them, at least until those type
578+
/// variables are fully resolved.
579+
fn prune_cache_value_obligations<'a, 'gcx, 'tcx>(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
580+
result: &NormalizedTy<'tcx>)
581+
-> NormalizedTy<'tcx> {
582+
if !infcx.any_unresolved_type_vars(&result.value) {
583+
return NormalizedTy { value: result.value, obligations: vec![] };
584+
}
585+
586+
let mut obligations: Vec<_> =
587+
result.obligations
588+
.iter()
589+
.filter(|obligation| match obligation.predicate {
590+
// We found a `T: Foo<X = U>` predicate, let's check
591+
// if `U` references any unresolved type
592+
// variables. In principle, we only care if this
593+
// projection can help resolve any of the type
594+
// variables found in `result.value` -- but we just
595+
// check for any type variables here, for fear of
596+
// indirect obligations (e.g., we project to `?0`,
597+
// but we have `T: Foo<X = ?1>` and `?1: Bar<X =
598+
// ?0>`).
599+
ty::Predicate::Projection(ref data) =>
600+
!infcx.any_unresolved_type_vars(&data.ty()),
601+
602+
// We are only interested in `T: Foo<X = U>` predicates, whre
603+
// `U` references one of `unresolved_type_vars`. =)
604+
_ => false,
605+
})
606+
.cloned()
607+
.collect();
608+
609+
obligations.shrink_to_fit();
610+
611+
NormalizedTy { value: result.value, obligations }
612+
}
613+
564614
/// If we are projecting `<T as Trait>::Item`, but `T: Trait` does not
565615
/// hold. In various error cases, we cannot generate a valid
566616
/// normalized projection. Therefore, we create an inference variable
@@ -1435,10 +1485,10 @@ impl<'tcx> ProjectionCache<'tcx> {
14351485
}
14361486

14371487
/// Indicates that `key` was normalized to `value`.
1438-
fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: &NormalizedTy<'tcx>) {
1488+
fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: NormalizedTy<'tcx>) {
14391489
debug!("ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}",
14401490
key, value);
1441-
let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value.clone()));
1491+
let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value));
14421492
assert!(!fresh_key, "never started projecting `{:?}`", key);
14431493
}
14441494

src/librustc/ty/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -1015,6 +1015,10 @@ impl<'tcx> PolyProjectionPredicate<'tcx> {
10151015
// levels.
10161016
ty::Binder(self.0.projection_ty.trait_ref(tcx))
10171017
}
1018+
1019+
pub fn ty(&self) -> Binder<Ty<'tcx>> {
1020+
Binder(self.skip_binder().ty) // preserves binding levels
1021+
}
10181022
}
10191023

10201024
pub trait ToPolyTraitRef<'tcx> {

0 commit comments

Comments
 (0)