]> git.lizzy.rs Git - rust.git/commitdiff
limit and clear cache obligations opportunistically
authorNiko Matsakis <niko@alum.mit.edu>
Sat, 2 Sep 2017 12:35:07 +0000 (08:35 -0400)
committerNiko Matsakis <niko@alum.mit.edu>
Sat, 2 Sep 2017 12:35:48 +0000 (08:35 -0400)
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 #43613

src/librustc/infer/mod.rs
src/librustc/infer/resolve.rs
src/librustc/traits/project.rs
src/librustc/ty/mod.rs

index 21af92a25e6845249be4e56f9f732dba18fc7063..d838b2226e863efbd030175baa6c150ef16c242c 100644 (file)
@@ -1160,6 +1160,18 @@ pub fn resolve_type_vars_if_possible<T>(&self, value: &T) -> T
         value.fold_with(&mut r)
     }
 
+    /// Returns true if `T` contains unresolved type variables. In the
+    /// process of visiting `T`, this will resolve (where possible)
+    /// type variables in `T`, but it never constructs the final,
+    /// resolved type, so it's more efficient than
+    /// `resolve_type_vars_if_possible()`.
+    pub fn any_unresolved_type_vars<T>(&self, value: &T) -> bool
+        where T: TypeFoldable<'tcx>
+    {
+        let mut r = resolve::UnresolvedTypeFinder::new(self);
+        value.visit_with(&mut r)
+    }
+
     pub fn resolve_type_and_region_vars_if_possible<T>(&self, value: &T) -> T
         where T: TypeFoldable<'tcx>
     {
index 639a330dc6e67dffdb4d7026154f3a8bd2556030..10899e42afb819b74ad124db8506ca87a939f930 100644 (file)
@@ -10,7 +10,7 @@
 
 use super::{InferCtxt, FixupError, FixupResult};
 use ty::{self, Ty, TyCtxt, TypeFoldable};
-use ty::fold::TypeFolder;
+use ty::fold::{TypeFolder, TypeVisitor};
 
 ///////////////////////////////////////////////////////////////////////////
 // OPPORTUNISTIC TYPE RESOLVER
@@ -80,6 +80,43 @@ fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
     }
 }
 
+///////////////////////////////////////////////////////////////////////////
+// UNRESOLVED TYPE FINDER
+
+/// The unresolved type **finder** walks your type and searches for
+/// type variables that don't yet have a value. They get pushed into a
+/// vector. It does not construct the fully resolved type (which might
+/// involve some hashing and so forth).
+pub struct UnresolvedTypeFinder<'a, 'gcx: 'a+'tcx, 'tcx: 'a> {
+    infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
+}
+
+impl<'a, 'gcx, 'tcx> UnresolvedTypeFinder<'a, 'gcx, 'tcx> {
+    pub fn new(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>) -> Self {
+        UnresolvedTypeFinder { infcx }
+    }
+}
+
+impl<'a, 'gcx, 'tcx> TypeVisitor<'tcx> for UnresolvedTypeFinder<'a, 'gcx, 'tcx> {
+    fn visit_ty(&mut self, t: Ty<'tcx>) -> bool {
+        let t = self.infcx.shallow_resolve(t);
+        if t.has_infer_types() {
+            if let ty::TyInfer(_) = t.sty {
+                // Since we called `shallow_resolve` above, this must
+                // be an (as yet...) unresolved inference variable.
+                true
+            } else {
+                // Otherwise, visit its contents.
+                t.super_visit_with(self)
+            }
+        } else {
+            // Micro-optimize: no inference types at all Can't have unresolved type
+            // variables, no need to visit the contents.
+            false
+        }
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////
 // FULL TYPE RESOLUTION
 
index 512cfee12b05f5b7b1de359b7ddf88d083440441..242281a0c77263069bdc1ba25eda1fbe92f5ad9b 100644 (file)
@@ -25,7 +25,7 @@
 use super::util;
 
 use hir::def_id::DefId;
-use infer::InferOk;
+use infer::{InferCtxt, InferOk};
 use infer::type_variable::TypeVariableOrigin;
 use rustc_data_structures::snapshot_map::{Snapshot, SnapshotMap};
 use syntax::ast;
@@ -416,7 +416,8 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
     // bounds. It might be the case that we want two distinct caches,
     // or else another kind of cache entry.
 
-    match infcx.projection_cache.borrow_mut().try_start(cache_key) {
+    let cache_result = infcx.projection_cache.borrow_mut().try_start(cache_key);
+    match cache_result {
         Ok(()) => { }
         Err(ProjectionCacheEntry::Ambiguous) => {
             // If we found ambiguity the last time, that generally
@@ -466,7 +467,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
                                                     projection_ty);
             selcx.infcx().report_overflow_error(&obligation, false);
         }
-        Err(ProjectionCacheEntry::NormalizedTy(ty)) => {
+        Err(ProjectionCacheEntry::NormalizedTy(mut ty)) => {
             // If we find the value in the cache, then return it along
             // with the obligations that went along with it. Note
             // that, when using a fulfillment context, these
@@ -479,6 +480,14 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
             debug!("opt_normalize_projection_type: \
                     found normalized ty `{:?}`",
                    ty);
+
+            // Once we have inferred everything we need to know, we
+            // can ignore the `obligations` from that point on.
+            if !infcx.any_unresolved_type_vars(&ty.value) {
+                infcx.projection_cache.borrow_mut().complete(cache_key);
+                ty.obligations = vec![];
+            }
+
             return Some(ty);
         }
         Err(ProjectionCacheEntry::Error) => {
@@ -527,7 +536,10 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
                     obligations,
                 }
             };
-            infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
+
+            let cache_value = prune_cache_value_obligations(infcx, &result);
+            infcx.projection_cache.borrow_mut().insert_ty(cache_key, cache_value);
+
             Some(result)
         }
         Ok(ProjectedTy::NoProgress(projected_ty)) => {
@@ -538,7 +550,7 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
                 value: projected_ty,
                 obligations: vec![]
             };
-            infcx.projection_cache.borrow_mut().insert_ty(cache_key, &result);
+            infcx.projection_cache.borrow_mut().insert_ty(cache_key, result.clone());
             Some(result)
         }
         Err(ProjectionTyError::TooManyCandidates) => {
@@ -562,6 +574,44 @@ fn opt_normalize_projection_type<'a, 'b, 'gcx, 'tcx>(
     }
 }
 
+/// If there are unresolved type variables, then we need to include
+/// any subobligations that bind them, at least until those type
+/// variables are fully resolved.
+fn prune_cache_value_obligations<'a, 'gcx, 'tcx>(infcx: &'a InferCtxt<'a, 'gcx, 'tcx>,
+                                                 result: &NormalizedTy<'tcx>)
+                                                 -> NormalizedTy<'tcx> {
+    if !infcx.any_unresolved_type_vars(&result.value) {
+        return NormalizedTy { value: result.value, obligations: vec![] };
+    }
+
+    let mut obligations: Vec<_> =
+        result.obligations
+              .iter()
+              .filter(|obligation| match obligation.predicate {
+                  // We found a `T: Foo<X = U>` predicate, let's check
+                  // if `U` references any unresolved type
+                  // variables. In principle, we only care if this
+                  // projection can help resolve any of the type
+                  // variables found in `result.value` -- but we just
+                  // check for any type variables here, for fear of
+                  // indirect obligations (e.g., we project to `?0`,
+                  // but we have `T: Foo<X = ?1>` and `?1: Bar<X =
+                  // ?0>`).
+                  ty::Predicate::Projection(ref data) =>
+                      !infcx.any_unresolved_type_vars(&data.ty()),
+
+                  // We are only interested in `T: Foo<X = U>` predicates, whre
+                  // `U` references one of `unresolved_type_vars`. =)
+                  _ => false,
+              })
+              .cloned()
+              .collect();
+
+    obligations.shrink_to_fit();
+
+    NormalizedTy { value: result.value, obligations }
+}
+
 /// If we are projecting `<T as Trait>::Item`, but `T: Trait` does not
 /// hold. In various error cases, we cannot generate a valid
 /// normalized projection. Therefore, we create an inference variable
@@ -1493,10 +1543,10 @@ fn try_start(&mut self, key: ProjectionCacheKey<'tcx>)
     }
 
     /// Indicates that `key` was normalized to `value`.
-    fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: &NormalizedTy<'tcx>) {
+    fn insert_ty(&mut self, key: ProjectionCacheKey<'tcx>, value: NormalizedTy<'tcx>) {
         debug!("ProjectionCacheEntry::insert_ty: adding cache entry: key={:?}, value={:?}",
                key, value);
-        let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value.clone()));
+        let fresh_key = self.map.insert(key, ProjectionCacheEntry::NormalizedTy(value));
         assert!(!fresh_key, "never started projecting `{:?}`", key);
     }
 
index 1851e1b8d34bbe595469f394cfe04c43104ddd9b..1920cdb3f738f86fc1495334ce6f7b445ee80cc9 100644 (file)
@@ -1017,6 +1017,10 @@ pub fn to_poly_trait_ref(&self, tcx: TyCtxt) -> PolyTraitRef<'tcx> {
         // levels.
         ty::Binder(self.0.projection_ty.trait_ref(tcx))
     }
+
+    pub fn ty(&self) -> Binder<Ty<'tcx>> {
+        Binder(self.skip_binder().ty) // preserves binding levels
+    }
 }
 
 pub trait ToPolyTraitRef<'tcx> {