add the "provisional cache"
This commit is contained in:
parent
a1a8a7b986
commit
9639d8ec34
@ -43,7 +43,7 @@ use crate::hir;
|
||||
use rustc_data_structures::bit_set::GrowableBitSet;
|
||||
use rustc_data_structures::sync::Lock;
|
||||
use rustc_target::spec::abi::Abi;
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::cmp;
|
||||
use std::fmt::{self, Display};
|
||||
use std::iter;
|
||||
@ -191,7 +191,6 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
|
||||
|
||||
/// Depth-first number of this node in the search graph -- a
|
||||
/// pre-order index. Basically a freshly incremented counter.
|
||||
#[allow(dead_code)] // TODO
|
||||
dfn: usize,
|
||||
}
|
||||
|
||||
@ -880,6 +879,12 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
if let Some(result) = stack.cache().get_provisional(fresh_trait_ref) {
|
||||
debug!("PROVISIONAL CACHE HIT: EVAL({:?})={:?}", fresh_trait_ref, result);
|
||||
stack.update_reached_depth(stack.cache().current_reached_depth());
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
// Check if this is a match for something already on the
|
||||
// stack. If so, we don't want to insert the result into the
|
||||
// main cache (it is cycle dependent) nor the provisional
|
||||
@ -892,20 +897,42 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
|
||||
let (result, dep_node) = self.in_task(|this| this.evaluate_stack(&stack));
|
||||
let result = result?;
|
||||
|
||||
if !result.must_apply_modulo_regions() {
|
||||
stack.cache().on_failure(stack.dfn);
|
||||
}
|
||||
|
||||
let reached_depth = stack.reached_depth.get();
|
||||
if reached_depth >= stack.depth {
|
||||
debug!("CACHE MISS: EVAL({:?})={:?}", fresh_trait_ref, result);
|
||||
self.insert_evaluation_cache(obligation.param_env, fresh_trait_ref, dep_node, result);
|
||||
|
||||
stack.cache().on_completion(stack.depth, |fresh_trait_ref, provisional_result| {
|
||||
self.insert_evaluation_cache(
|
||||
obligation.param_env,
|
||||
fresh_trait_ref,
|
||||
dep_node,
|
||||
provisional_result.max(result),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
debug!("PROVISIONAL: {:?}={:?}", fresh_trait_ref, result);
|
||||
debug!(
|
||||
"evaluate_trait_predicate_recursively: skipping cache because {:?} \
|
||||
"evaluate_trait_predicate_recursively: caching provisionally because {:?} \
|
||||
is a cycle participant (at depth {}, reached depth {})",
|
||||
fresh_trait_ref,
|
||||
stack.depth,
|
||||
reached_depth,
|
||||
);
|
||||
|
||||
stack.cache().insert_provisional(
|
||||
stack.dfn,
|
||||
reached_depth,
|
||||
fresh_trait_ref,
|
||||
result,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
@ -4004,18 +4031,179 @@ impl<'o, 'tcx> TraitObligationStack<'o, 'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The "provisional evaluation cache" is used to store intermediate cache results
|
||||
/// when solving auto traits. Auto traits are unusual in that they can support
|
||||
/// cycles. So, for example, a "proof tree" like this would be ok:
|
||||
///
|
||||
/// - `Foo<T>: Send` :-
|
||||
/// - `Bar<T>: Send` :-
|
||||
/// - `Foo<T>: Send` -- cycle, but ok
|
||||
/// - `Baz<T>: Send`
|
||||
///
|
||||
/// Here, to prove `Foo<T>: Send`, we have to prove `Bar<T>: Send` and
|
||||
/// `Baz<T>: Send`. Proving `Bar<T>: Send` in turn required `Foo<T>: Send`.
|
||||
/// For non-auto traits, this cycle would be an error, but for auto traits (because
|
||||
/// they are coinductive) it is considered ok.
|
||||
///
|
||||
/// However, there is a complication: at the point where we have
|
||||
/// "proven" `Bar<T>: Send`, we have in fact only proven it
|
||||
/// *provisionally*. In particular, we proved that `Bar<T>: Send`
|
||||
/// *under the assumption* that `Foo<T>: Send`. But what if we later
|
||||
/// find out this assumption is wrong? Specifically, we could
|
||||
/// encounter some kind of error proving `Baz<T>: Send`. In that case,
|
||||
/// `Bar<T>: Send` didn't turn out to be true.
|
||||
///
|
||||
/// In Issue #60010, we found a bug in rustc where it would cache
|
||||
/// these intermediate results. This was fixed in #60444 by disabling
|
||||
/// *all* caching for things involved in a cycle -- in our example,
|
||||
/// that would mean we don't cache that `Bar<T>: Send`. But this led
|
||||
/// to large slowdowns.
|
||||
///
|
||||
/// Specifically, imagine this scenario, where proving `Baz<T>: Send`
|
||||
/// first requires proving `Bar<T>: Send` (which is true:
|
||||
///
|
||||
/// - `Foo<T>: Send` :-
|
||||
/// - `Bar<T>: Send` :-
|
||||
/// - `Foo<T>: Send` -- cycle, but ok
|
||||
/// - `Baz<T>: Send`
|
||||
/// - `Bar<T>: Send` -- would be nice for this to be a cache hit!
|
||||
/// - `*const T: Send` -- but what if we later encounter an error?
|
||||
///
|
||||
/// The *provisional evaluation cache* resolves this issue. It stores
|
||||
/// cache results that we've proven but which were involved in a cycle
|
||||
/// in some way. We track the minimal stack depth (i.e., the
|
||||
/// farthest from the top of the stack) that we are dependent on.
|
||||
/// The idea is that the cache results within are all valid -- so long as
|
||||
/// none of the nodes in between the current node and the node at that minimum
|
||||
/// depth result in an error (in which case the cached results are just thrown away).
|
||||
///
|
||||
/// During evaluation, we consult this provisional cache and rely on
|
||||
/// it. Accessing a cached value is considered equivalent to accessing
|
||||
/// a result at `reached_depth`, so it marks the *current* solution as
|
||||
/// provisional as well. If an error is encountered, we toss out any
|
||||
/// provisional results added from the subtree that encountered the
|
||||
/// error. When we pop the node at `reached_depth` from the stack, we
|
||||
/// can commit all the things that remain in the provisional cache.
|
||||
#[derive(Default)]
|
||||
struct ProvisionalEvaluationCache<'tcx> {
|
||||
/// next "depth first number" to issue -- just a counter
|
||||
dfn: Cell<usize>,
|
||||
_dummy: Vec<&'tcx ()>,
|
||||
|
||||
/// Stores the "coldest" depth (bottom of stack) reached by any of
|
||||
/// the evaluation entries. The idea here is that all things in the provisional
|
||||
/// cache are always dependent on *something* that is colder in the stack:
|
||||
/// therefore, if we add a new entry that is dependent on something *colder still*,
|
||||
/// we have to modify the depth for all entries at once.
|
||||
///
|
||||
/// Example:
|
||||
///
|
||||
/// Imagine we have a stack `A B C D E` (with `E` being the top of
|
||||
/// the stack). We cache something with depth 2, which means that
|
||||
/// it was dependent on C. Then we pop E but go on and process a
|
||||
/// new node F: A B C D F. Now F adds something to the cache with
|
||||
/// depth 1, meaning it is dependent on B. Our original cache
|
||||
/// entry is also dependent on B, because there is a path from E
|
||||
/// to C and then from C to F and from F to B.
|
||||
reached_depth: Cell<usize>,
|
||||
|
||||
/// Map from cache key to the provisionally evaluated thing.
|
||||
/// The cache entries contain the result but also the DFN in which they
|
||||
/// were added. The DFN is used to clear out values on failure.
|
||||
///
|
||||
/// Imagine we have a stack like:
|
||||
///
|
||||
/// - `A B C` and we add a cache for the result of C (DFN 2)
|
||||
/// - Then we have a stack `A B D` where `D` has DFN 3
|
||||
/// - We try to solve D by evaluating E: `A B D E` (DFN 4)
|
||||
/// - `E` generates various cache entries which have cyclic dependices on `B`
|
||||
/// - `A B D E F` and so forth
|
||||
/// - the DFN of `F` for example would be 5
|
||||
/// - then we determine that `E` is in error -- we will then clear
|
||||
/// all cache values whose DFN is >= 4 -- in this case, that
|
||||
/// means the cached value for `F`.
|
||||
map: RefCell<FxHashMap<ty::PolyTraitRef<'tcx>, ProvisionalEvaluation>>,
|
||||
}
|
||||
|
||||
/// A cache value for the provisional cache: contains the depth-first
|
||||
/// number (DFN) and result.
|
||||
#[derive(Copy, Clone)]
|
||||
struct ProvisionalEvaluation {
|
||||
from_dfn: usize,
|
||||
result: EvaluationResult,
|
||||
}
|
||||
|
||||
impl<'tcx> ProvisionalEvaluationCache<'tcx> {
|
||||
/// Get the next DFN in sequence (basically a counter).
|
||||
fn next_dfn(&self) -> usize {
|
||||
let result = self.dfn.get();
|
||||
self.dfn.set(result + 1);
|
||||
result
|
||||
}
|
||||
|
||||
/// Check the provisional cache for any result for
|
||||
/// `fresh_trait_ref`. If there is a hit, then you must consider
|
||||
/// it an access to the stack slots at depth
|
||||
/// `self.current_reached_depth()` and above.
|
||||
fn get_provisional(&self, fresh_trait_ref: ty::PolyTraitRef<'tcx>) -> Option<EvaluationResult> {
|
||||
Some(self.map.borrow().get(&fresh_trait_ref)?.result)
|
||||
}
|
||||
|
||||
/// Current value of the `reached_depth` counter -- all the
|
||||
/// provisional cache entries are dependent on the item at this
|
||||
/// depth.
|
||||
fn current_reached_depth(&self) -> usize {
|
||||
self.reached_depth.get()
|
||||
}
|
||||
|
||||
/// Insert a provisional result into the cache. The result came
|
||||
/// from the node with the given DFN. It accessed a minimum depth
|
||||
/// of `reached_depth` to compute. It evaluated `fresh_trait_ref`
|
||||
/// and resulted in `result`.
|
||||
fn insert_provisional(
|
||||
&self,
|
||||
from_dfn: usize,
|
||||
reached_depth: usize,
|
||||
fresh_trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
result: EvaluationResult,
|
||||
) {
|
||||
let r_d = self.reached_depth.get();
|
||||
self.reached_depth.set(r_d.min(reached_depth));
|
||||
|
||||
self.map.borrow_mut().insert(fresh_trait_ref, ProvisionalEvaluation { from_dfn, result });
|
||||
}
|
||||
|
||||
/// Invoked when the node with dfn `dfn` does not get a successful
|
||||
/// result. This will clear out any provisional cache entries
|
||||
/// that were added since `dfn` was created. This is because the
|
||||
/// provisional entries are things which must assume that the
|
||||
/// things on the stack at the time of their creation succeeded --
|
||||
/// since the failing node is presently at the top of the stack,
|
||||
/// these provisional entries must either depend on it or some
|
||||
/// ancestor of it.
|
||||
fn on_failure(&self, dfn: usize) {
|
||||
self.map.borrow_mut().retain(|_key, eval| eval.from_dfn >= dfn)
|
||||
}
|
||||
|
||||
/// Invoked when the node at depth `depth` completed without
|
||||
/// depending on anything higher in the stack (if that completion
|
||||
/// was a failure, then `on_failure` should have been invoked
|
||||
/// already). The callback `op` will be invoked for each
|
||||
/// provisional entry that we can now confirm.
|
||||
fn on_completion(
|
||||
&self,
|
||||
depth: usize,
|
||||
mut op: impl FnMut(ty::PolyTraitRef<'tcx>, EvaluationResult),
|
||||
) {
|
||||
if self.reached_depth.get() < depth {
|
||||
return;
|
||||
}
|
||||
|
||||
for (fresh_trait_ref, eval) in self.map.borrow_mut().drain() {
|
||||
op(fresh_trait_ref, eval.result);
|
||||
}
|
||||
|
||||
self.reached_depth.set(depth);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
Loading…
x
Reference in New Issue
Block a user