Skip to content

Commit 89c58ea

Browse files
committed
have on_completion record subcycles
Rework `on_completion` method so that it removes all provisional cache entries that are "below" a completed node (while leaving those entries that are not below the node). This corrects an imprecise result that could in turn lead to an incremental compilation failure. Under the old scheme, if you had: * A depends on... * B depends on A * C depends on... * D depends on C * T: 'static then the provisional results for A, B, C, and D would all be entangled. Thus, if A was `EvaluatedToOkModuloRegions` (because of that final condition), then the result for C and D would also be demoted to "ok modulo regions". In reality, though, the result for C depends only on C and itself, and is not dependent on regions. If we happen to evaluate the cycle starting from C, we would never reach A, and hence the result would be "ok". Under the new scheme, the provisional results for C and D are moved to the permanent cache immediately and are not affected by the result of A.
1 parent c7cb728 commit 89c58ea

File tree

7 files changed

+122
-84
lines changed

7 files changed

+122
-84
lines changed

compiler/rustc_trait_selection/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#![feature(bool_to_option)]
1515
#![feature(box_patterns)]
1616
#![feature(drain_filter)]
17+
#![feature(hash_drain_filter)]
1718
#![feature(in_band_lifetimes)]
1819
#![feature(iter_zip)]
1920
#![feature(never_type)]

compiler/rustc_trait_selection/src/traits/select/mod.rs

+60-48
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
636636

637637
if let Some(result) = stack.cache().get_provisional(fresh_trait_ref) {
638638
debug!(?result, "PROVISIONAL CACHE HIT");
639-
stack.update_reached_depth(stack.cache().current_reached_depth());
640-
return Ok(result);
639+
stack.update_reached_depth(result.reached_depth);
640+
return Ok(result.result);
641641
}
642642

643643
// Check if this is a match for something already on the
@@ -661,7 +661,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
661661
debug!(?result, "CACHE MISS");
662662
self.insert_evaluation_cache(obligation.param_env, fresh_trait_ref, dep_node, result);
663663

664-
stack.cache().on_completion(stack.depth, |fresh_trait_ref, provisional_result| {
664+
stack.cache().on_completion(stack.dfn, |fresh_trait_ref, provisional_result| {
665665
self.insert_evaluation_cache(
666666
obligation.param_env,
667667
fresh_trait_ref,
@@ -2162,7 +2162,7 @@ impl<'o, 'tcx> TraitObligationStack<'o, 'tcx> {
21622162
/// required accessing something from the stack at depth `reached_depth`.
21632163
fn update_reached_depth(&self, reached_depth: usize) {
21642164
assert!(
2165-
self.depth > reached_depth,
2165+
self.depth >= reached_depth,
21662166
"invoked `update_reached_depth` with something under this stack: \
21672167
self.depth={} reached_depth={}",
21682168
self.depth,
@@ -2235,23 +2235,6 @@ struct ProvisionalEvaluationCache<'tcx> {
22352235
/// next "depth first number" to issue -- just a counter
22362236
dfn: Cell<usize>,
22372237

2238-
/// Stores the "coldest" depth (bottom of stack) reached by any of
2239-
/// the evaluation entries. The idea here is that all things in the provisional
2240-
/// cache are always dependent on *something* that is colder in the stack:
2241-
/// therefore, if we add a new entry that is dependent on something *colder still*,
2242-
/// we have to modify the depth for all entries at once.
2243-
///
2244-
/// Example:
2245-
///
2246-
/// Imagine we have a stack `A B C D E` (with `E` being the top of
2247-
/// the stack). We cache something with depth 2, which means that
2248-
/// it was dependent on C. Then we pop E but go on and process a
2249-
/// new node F: A B C D F. Now F adds something to the cache with
2250-
/// depth 1, meaning it is dependent on B. Our original cache
2251-
/// entry is also dependent on B, because there is a path from E
2252-
/// to C and then from C to F and from F to B.
2253-
reached_depth: Cell<usize>,
2254-
22552238
/// Map from cache key to the provisionally evaluated thing.
22562239
/// The cache entries contain the result but also the DFN in which they
22572240
/// were added. The DFN is used to clear out values on failure.
@@ -2275,12 +2258,13 @@ struct ProvisionalEvaluationCache<'tcx> {
22752258
#[derive(Copy, Clone, Debug)]
22762259
struct ProvisionalEvaluation {
22772260
from_dfn: usize,
2261+
reached_depth: usize,
22782262
result: EvaluationResult,
22792263
}
22802264

22812265
impl<'tcx> Default for ProvisionalEvaluationCache<'tcx> {
22822266
fn default() -> Self {
2283-
Self { dfn: Cell::new(0), reached_depth: Cell::new(usize::MAX), map: Default::default() }
2267+
Self { dfn: Cell::new(0), map: Default::default() }
22842268
}
22852269
}
22862270

@@ -2295,22 +2279,17 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
22952279
/// Check the provisional cache for any result for
22962280
/// `fresh_trait_ref`. If there is a hit, then you must consider
22972281
/// it an access to the stack slots at depth
2298-
/// `self.current_reached_depth()` and above.
2299-
fn get_provisional(&self, fresh_trait_ref: ty::PolyTraitRef<'tcx>) -> Option<EvaluationResult> {
2282+
/// `reached_depth` (from the returned value).
2283+
fn get_provisional(
2284+
&self,
2285+
fresh_trait_ref: ty::PolyTraitRef<'tcx>,
2286+
) -> Option<ProvisionalEvaluation> {
23002287
debug!(
23012288
?fresh_trait_ref,
2302-
reached_depth = ?self.reached_depth.get(),
23032289
"get_provisional = {:#?}",
23042290
self.map.borrow().get(&fresh_trait_ref),
23052291
);
2306-
Some(self.map.borrow().get(&fresh_trait_ref)?.result)
2307-
}
2308-
2309-
/// Current value of the `reached_depth` counter -- all the
2310-
/// provisional cache entries are dependent on the item at this
2311-
/// depth.
2312-
fn current_reached_depth(&self) -> usize {
2313-
self.reached_depth.get()
2292+
Some(self.map.borrow().get(&fresh_trait_ref)?.clone())
23142293
}
23152294

23162295
/// Insert a provisional result into the cache. The result came
@@ -2324,13 +2303,31 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
23242303
fresh_trait_ref: ty::PolyTraitRef<'tcx>,
23252304
result: EvaluationResult,
23262305
) {
2327-
debug!(?from_dfn, ?reached_depth, ?fresh_trait_ref, ?result, "insert_provisional");
2328-
let r_d = self.reached_depth.get();
2329-
self.reached_depth.set(r_d.min(reached_depth));
2306+
debug!(?from_dfn, ?fresh_trait_ref, ?result, "insert_provisional");
23302307

2331-
debug!(reached_depth = self.reached_depth.get());
2308+
let mut map = self.map.borrow_mut();
2309+
2310+
// Subtle: when we complete working on the DFN `from_dfn`, anything
2311+
// that remains in the provisional cache must be dependent on some older
2312+
// stack entry than `from_dfn`. We have to update their depth with our transitive
2313+
// depth in that case or else it would be referring to some popped note.
2314+
//
2315+
// Example:
2316+
// A (reached depth 0)
2317+
// ...
2318+
// B // depth 1 -- reached depth = 0
2319+
// C // depth 2 -- reached depth = 1 (should be 0)
2320+
// B
2321+
// A // depth 0
2322+
// D (reached depth 1)
2323+
// C (cache -- reached depth = 2)
2324+
for (_k, v) in &mut *map {
2325+
if v.from_dfn >= from_dfn {
2326+
v.reached_depth = reached_depth.min(v.reached_depth);
2327+
}
2328+
}
23322329

2333-
self.map.borrow_mut().insert(fresh_trait_ref, ProvisionalEvaluation { from_dfn, result });
2330+
map.insert(fresh_trait_ref, ProvisionalEvaluation { from_dfn, reached_depth, result });
23342331
}
23352332

23362333
/// Invoked when the node with dfn `dfn` does not get a successful
@@ -2358,25 +2355,40 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
23582355
/// was a failure, then `on_failure` should have been invoked
23592356
/// already). The callback `op` will be invoked for each
23602357
/// provisional entry that we can now confirm.
2358+
///
2359+
/// Note that we may still have provisional cache items remaining
2360+
/// in the cache when this is done. For example, if there is a
2361+
/// cycle:
2362+
///
2363+
/// * A depends on...
2364+
/// * B depends on A
2365+
/// * C depends on...
2366+
/// * D depends on C
2367+
/// * ...
2368+
///
2369+
/// Then as we complete the C node we will have a provisional cache
2370+
/// with results for A, B, C, and D. This method would clear out
2371+
/// the C and D results, but leave A and B provisional.
2372+
///
2373+
/// This is determined based on the DFN: we remove any provisional
2374+
/// results created since `dfn` started (e.g., in our example, dfn
2375+
/// would be 2, representing the C node, and hence we would
2376+
/// remove the result for D, which has DFN 3, but not the results for
2377+
/// A and B, which have DFNs 0 and 1 respectively).
23612378
fn on_completion(
23622379
&self,
2363-
depth: usize,
2380+
dfn: usize,
23642381
mut op: impl FnMut(ty::PolyTraitRef<'tcx>, EvaluationResult),
23652382
) {
2366-
debug!(?depth, reached_depth = ?self.reached_depth.get(), "on_completion");
2383+
debug!(?dfn, "on_completion");
23672384

2368-
if self.reached_depth.get() < depth {
2369-
debug!("on_completion: did not yet reach depth to complete");
2370-
return;
2371-
}
2372-
2373-
for (fresh_trait_ref, eval) in self.map.borrow_mut().drain() {
2385+
for (fresh_trait_ref, eval) in
2386+
self.map.borrow_mut().drain_filter(|_k, eval| eval.from_dfn >= dfn)
2387+
{
23742388
debug!(?fresh_trait_ref, ?eval, "on_completion");
23752389

23762390
op(fresh_trait_ref, eval.result);
23772391
}
2378-
2379-
self.reached_depth.set(usize::MAX);
23802392
}
23812393
}
23822394

src/test/ui/cycle-me.rs

-31
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#![feature(rustc_attrs)]
2+
3+
// Test for a particular corner case where the evaluation
4+
// cache can get out of date. The problem here is that
5+
// when we cache C, we have observed that it reaches
6+
// to depth 2 (the node for B), but we later realize
7+
// that B itself depends on A (reached depth 0). We
8+
// failed to update the depth for C transitively, which
9+
// resulted in an assertion failure when it was referenced
10+
// from D.
11+
//
12+
// A (reached depth 0)
13+
// E
14+
// B // depth 2 -- reached depth = 0
15+
// C // depth 3 -- reached depth = 2 (should be 0)
16+
// B
17+
// A // depth 0
18+
// D (depth 1)
19+
// C (cache -- reached depth = 2)
20+
21+
struct A {
22+
e: E,
23+
d: C,
24+
}
25+
26+
struct E {
27+
b: B,
28+
}
29+
30+
struct B {
31+
a: Option<Box<A>>,
32+
c: C,
33+
}
34+
35+
struct C {
36+
b: Option<Box<B>>,
37+
}
38+
39+
#[rustc_evaluate_where_clauses]
40+
fn test<X: ?Sized + Send>() {}
41+
42+
fn main() {
43+
test::<A>();
44+
//~^ ERROR evaluate(Binder(TraitPredicate(<A as std::marker::Send>), [])) = Ok(EvaluatedToOk)
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error: evaluate(Binder(TraitPredicate(<A as std::marker::Send>), [])) = Ok(EvaluatedToOk)
2+
--> $DIR/cache-reached-depth-ice.rs:43:5
3+
|
4+
LL | fn test<X: ?Sized + Send>() {}
5+
| ---- predicate
6+
...
7+
LL | test::<A>();
8+
| ^^^^^^^^^
9+
10+
error: aborting due to previous error
11+

src/test/ui/traits/issue-83538-tainted-cache-after-cycle.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ where
5454
}
5555

5656
fn main() {
57-
// The only ERROR included here is the one that is totally wrong:
57+
// Key is that Vec<First> is "ok" and Third<Ty> is "ok modulo regions":
5858

5959
forward();
60-
//~^ ERROR evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOkModuloRegions)
60+
//~^ ERROR evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOk)
6161
//~| ERROR evaluate(Binder(TraitPredicate(<Third<Ty> as std::marker::Unpin>), [])) = Ok(EvaluatedToOkModuloRegions)
6262

6363
reverse();
64-
//~^ ERROR evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOkModuloRegions)
64+
//~^ ERROR evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOk)
6565
//~| ERROR evaluate(Binder(TraitPredicate(<Third<Ty> as std::marker::Unpin>), [])) = Ok(EvaluatedToOkModuloRegions)
6666
}

src/test/ui/traits/issue-83538-tainted-cache-after-cycle.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
error: evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOkModuloRegions)
1+
error: evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOk)
22
--> $DIR/issue-83538-tainted-cache-after-cycle.rs:59:5
33
|
44
LL | Vec<First>: Unpin,
@@ -25,7 +25,7 @@ LL | Third<Ty>: Unpin,
2525
LL | reverse();
2626
| ^^^^^^^
2727

28-
error: evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOkModuloRegions)
28+
error: evaluate(Binder(TraitPredicate(<std::vec::Vec<First> as std::marker::Unpin>), [])) = Ok(EvaluatedToOk)
2929
--> $DIR/issue-83538-tainted-cache-after-cycle.rs:63:5
3030
|
3131
LL | Vec<First>: Unpin,

0 commit comments

Comments
 (0)