1
Fork 0

Re-introduce concept of projection cache 'completion'

Instead of clearing out the cache entirely, we store
the intermediate evaluation result into the cache entry.
This accomplishes several things:

* We avoid the performance hit associated with re-evaluating
  the sub-obligations
* We avoid causing issues with incremental compilation, since
  the final evaluation result is always the same
* We avoid affecting other uses of the same `InferCtxt` which
  might care about 'side effects' from processing the sub-obligations
  (e,g. region constraints). Only code that is specifically aware
   of the new 'complete' code is affected
This commit is contained in:
Aaron Hill 2021-10-09 11:29:39 -05:00
parent 91a0600a5c
commit 40ef1d3223
No known key found for this signature in database
GPG key ID: B4087E510E98B164
5 changed files with 138 additions and 6 deletions

View file

@ -16,6 +16,7 @@
#![feature(drain_filter)]
#![feature(derive_default_enum)]
#![feature(hash_drain_filter)]
#![feature(label_break_value)]
#![feature(let_else)]
#![feature(never_type)]
#![feature(crate_visibility_modifier)]

View file

@ -4,6 +4,7 @@ use rustc_data_structures::obligation_forest::ProcessResult;
use rustc_data_structures::obligation_forest::{Error, ForestObligation, Outcome};
use rustc_data_structures::obligation_forest::{ObligationForest, ObligationProcessor};
use rustc_errors::ErrorReported;
use rustc_infer::traits::ProjectionCacheKey;
use rustc_infer::traits::{SelectionError, TraitEngine, TraitEngineExt as _, TraitObligation};
use rustc_middle::mir::interpret::ErrorHandled;
use rustc_middle::thir::abstract_const::NotConstEvaluatable;
@ -20,12 +21,14 @@ use super::wf;
use super::CodeAmbiguity;
use super::CodeProjectionError;
use super::CodeSelectionError;
use super::EvaluationResult;
use super::Unimplemented;
use super::{FulfillmentError, FulfillmentErrorCode};
use super::{ObligationCause, PredicateObligation};
use crate::traits::error_reporting::InferCtxtExt as _;
use crate::traits::project::PolyProjectionObligation;
use crate::traits::project::ProjectionCacheKeyExt as _;
use crate::traits::query::evaluate_obligation::InferCtxtExt as _;
impl<'tcx> ForestObligation for PendingPredicateObligation<'tcx> {
@ -709,6 +712,20 @@ impl<'a, 'b, 'tcx> FulfillProcessor<'a, 'b, 'tcx> {
// no type variables present, can use evaluation for better caching.
// FIXME: consider caching errors too.
if self.selcx.infcx().predicate_must_hold_considering_regions(obligation) {
if let Some(key) = ProjectionCacheKey::from_poly_projection_predicate(
&mut self.selcx,
project_obligation.predicate,
) {
// If `predicate_must_hold_considering_regions` succeeds, then we've
// evaluated all sub-obligations. We can therefore mark the 'root'
// obligation as complete, and skip evaluating sub-obligations.
self.selcx
.infcx()
.inner
.borrow_mut()
.projection_cache()
.complete(key, EvaluationResult::EvaluatedToOk);
}
return ProcessResult::Changed(vec![]);
} else {
tracing::debug!("Does NOT hold: {:?}", obligation);

View file

@ -889,7 +889,7 @@ fn opt_normalize_projection_type<'a, 'b, 'tcx>(
debug!("recur cache");
return Err(InProgress);
}
Err(ProjectionCacheEntry::NormalizedTy(ty)) => {
Err(ProjectionCacheEntry::NormalizedTy { ty, complete: _ }) => {
// This is the hottest path in this function.
//
// If we find the value in the cache, then return it along

View file

@ -25,6 +25,8 @@ use super::{ObligationCause, PredicateObligation, TraitObligation};
use crate::infer::{InferCtxt, InferOk, TypeFreshener};
use crate::traits::error_reporting::InferCtxtExt;
use crate::traits::project::ProjectionCacheKeyExt;
use crate::traits::ProjectionCacheKey;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_data_structures::sync::Lrc;
@ -550,8 +552,54 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let project_obligation = obligation.with(data);
match project::poly_project_and_unify_type(self, &project_obligation) {
Ok(Ok(Some(mut subobligations))) => {
self.add_depth(subobligations.iter_mut(), obligation.recursion_depth);
self.evaluate_predicates_recursively(previous_stack, subobligations)
'compute_res: {
// If we've previously marked this projection as 'complete', thne
// use the final cached result (either `EvaluatedToOk` or
// `EvaluatedToOkModuloRegions`), and skip re-evaluating the
// sub-obligations.
if let Some(key) =
ProjectionCacheKey::from_poly_projection_predicate(self, data)
{
if let Some(cached_res) = self
.infcx
.inner
.borrow_mut()
.projection_cache()
.is_complete(key)
{
break 'compute_res Ok(cached_res);
}
}
self.add_depth(
subobligations.iter_mut(),
obligation.recursion_depth,
);
let res = self.evaluate_predicates_recursively(
previous_stack,
subobligations,
);
if let Ok(res) = res {
if res == EvaluatedToOk || res == EvaluatedToOkModuloRegions {
if let Some(key) =
ProjectionCacheKey::from_poly_projection_predicate(
self, data,
)
{
// If the result is something that we can cache, then mark this
// entry as 'complete'. This will allow us to skip evaluating the
// suboligations at all the next time we evaluate the projection
// predicate.
self.infcx
.inner
.borrow_mut()
.projection_cache()
.complete(key, res);
}
}
}
res
}
}
Ok(Ok(None)) => Ok(EvaluatedToAmbig),
Ok(Err(project::InProgress)) => Ok(EvaluatedToRecur),