Skip to content

Commit cf70411

Browse files
authored
Rollup merge of #93064 - Aaron1011:provisional-dep-node, r=michaelwoerister
Properly track `DepNode`s in trait evaluation provisional cache Fixes #92987 During evaluation of an auto trait predicate, we may encounter a cycle. This causes us to store the evaluation result in a special 'provisional cache;. If we later end up determining that the type can legitimately implement the auto trait despite the cycle, we remove the entry from the provisional cache, and insert it into the evaluation cache. Additionally, trait evaluation creates a special anonymous `DepNode`. All queries invoked during the predicate evaluation are added as outoging dependency edges from the `DepNode`. This `DepNode` is then store in the evaluation cache - if a different query ends up reading from the cache entry, it will also perform a read of the stored `DepNode`. As a result, the cached evaluation will still end up (transitively) incurring all of the same dependencies that it would if it actually performed the uncached evaluation (e.g. a call to `type_of` to determine constituent types). Previously, we did not correctly handle the interaction between the provisional cache and the created `DepNode`. Storing an evaluation result in the provisional cache would cause us to lose the `DepNode` created during the evaluation. If we later moved the entry from the provisional cache to the evaluation cache, we would use the `DepNode` associated with the evaluation that caused us to 'complete' the cycle, not the evaluatoon where we first discovered the cycle. As a result, future reads from the evaluation cache would miss some incremental compilation dependencies that would have otherwise been added if the evaluation was *not* cached. Under the right circumstances, this could lead to us trying to force a query with a no-longer-existing `DefPathHash`, since we were missing the (red) dependency edge that would have caused us to bail out before attempting forcing. This commit makes the provisional cache store the `DepNode` create during the provisional evaluation. When we move an entry from the provisional cache to the evaluation cache, we create a *new* `DepNode` that has dependencies going to *both* of the evaluation `DepNodes` we have available. This ensures that cached reads will incur all of the necessary dependency edges.
2 parents 687bb58 + 02f1a56 commit cf70411

File tree

2 files changed

+75
-12
lines changed

2 files changed

+75
-12
lines changed

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

+51-12
Original file line numberDiff line numberDiff line change
@@ -765,14 +765,38 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
765765
debug!(?result, "CACHE MISS");
766766
self.insert_evaluation_cache(param_env, fresh_trait_pred, dep_node, result);
767767

768-
stack.cache().on_completion(stack.dfn, |fresh_trait_pred, provisional_result| {
769-
self.insert_evaluation_cache(
770-
param_env,
771-
fresh_trait_pred,
772-
dep_node,
773-
provisional_result.max(result),
774-
);
775-
});
768+
stack.cache().on_completion(
769+
stack.dfn,
770+
|fresh_trait_pred, provisional_result, provisional_dep_node| {
771+
// Create a new `DepNode` that has dependencies on:
772+
// * The `DepNode` for the original evaluation that resulted in a provisional cache
773+
// entry being crated
774+
// * The `DepNode` for the *current* evaluation, which resulted in us completing
775+
// provisional caches entries and inserting them into the evaluation cache
776+
//
777+
// This ensures that when a query reads this entry from the evaluation cache,
778+
// it will end up (transitively) dependening on all of the incr-comp dependencies
779+
// created during the evaluation of this trait. For example, evaluating a trait
780+
// will usually require us to invoke `type_of(field_def_id)` to determine the
781+
// constituent types, and we want any queries reading from this evaluation
782+
// cache entry to end up with a transitive `type_of(field_def_id`)` dependency.
783+
//
784+
// By using `in_task`, we're also creating an edge from the *current* query
785+
// to the newly-created `combined_dep_node`. This is probably redundant,
786+
// but it's better to add too many dep graph edges than to add too few
787+
// dep graph edges.
788+
let ((), combined_dep_node) = self.in_task(|this| {
789+
this.tcx().dep_graph.read_index(provisional_dep_node);
790+
this.tcx().dep_graph.read_index(dep_node);
791+
});
792+
self.insert_evaluation_cache(
793+
param_env,
794+
fresh_trait_pred,
795+
combined_dep_node,
796+
provisional_result.max(result),
797+
);
798+
},
799+
);
776800
} else {
777801
debug!(?result, "PROVISIONAL");
778802
debug!(
@@ -781,7 +805,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
781805
fresh_trait_pred, stack.depth, reached_depth,
782806
);
783807

784-
stack.cache().insert_provisional(stack.dfn, reached_depth, fresh_trait_pred, result);
808+
stack.cache().insert_provisional(
809+
stack.dfn,
810+
reached_depth,
811+
fresh_trait_pred,
812+
result,
813+
dep_node,
814+
);
785815
}
786816

787817
Ok(result)
@@ -2506,6 +2536,11 @@ struct ProvisionalEvaluation {
25062536
from_dfn: usize,
25072537
reached_depth: usize,
25082538
result: EvaluationResult,
2539+
/// The `DepNodeIndex` created for the `evaluate_stack` call for this provisional
2540+
/// evaluation. When we create an entry in the evaluation cache using this provisional
2541+
/// cache entry (see `on_completion`), we use this `dep_node` to ensure that future reads from
2542+
/// the cache will have all of the necessary incr comp dependencies tracked.
2543+
dep_node: DepNodeIndex,
25092544
}
25102545

25112546
impl<'tcx> Default for ProvisionalEvaluationCache<'tcx> {
@@ -2548,6 +2583,7 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
25482583
reached_depth: usize,
25492584
fresh_trait_pred: ty::PolyTraitPredicate<'tcx>,
25502585
result: EvaluationResult,
2586+
dep_node: DepNodeIndex,
25512587
) {
25522588
debug!(?from_dfn, ?fresh_trait_pred, ?result, "insert_provisional");
25532589

@@ -2573,7 +2609,10 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
25732609
}
25742610
}
25752611

2576-
map.insert(fresh_trait_pred, ProvisionalEvaluation { from_dfn, reached_depth, result });
2612+
map.insert(
2613+
fresh_trait_pred,
2614+
ProvisionalEvaluation { from_dfn, reached_depth, result, dep_node },
2615+
);
25772616
}
25782617

25792618
/// Invoked when the node with dfn `dfn` does not get a successful
@@ -2624,7 +2663,7 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
26242663
fn on_completion(
26252664
&self,
26262665
dfn: usize,
2627-
mut op: impl FnMut(ty::PolyTraitPredicate<'tcx>, EvaluationResult),
2666+
mut op: impl FnMut(ty::PolyTraitPredicate<'tcx>, EvaluationResult, DepNodeIndex),
26282667
) {
26292668
debug!(?dfn, "on_completion");
26302669

@@ -2633,7 +2672,7 @@ impl<'tcx> ProvisionalEvaluationCache<'tcx> {
26332672
{
26342673
debug!(?fresh_trait_pred, ?eval, "on_completion");
26352674

2636-
op(fresh_trait_pred, eval.result);
2675+
op(fresh_trait_pred, eval.result, eval.dep_node);
26372676
}
26382677
}
26392678
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// revisions: rpass1 rpass2
2+
3+
// Regression test for issue #92987
4+
// Tests that we properly manage `DepNode`s during trait evaluation
5+
// involing an auto-trait cycle.
6+
7+
#[cfg(rpass1)]
8+
struct CycleOne(Box<CycleTwo>);
9+
10+
#[cfg(rpass2)]
11+
enum CycleOne {
12+
Variant(Box<CycleTwo>)
13+
}
14+
15+
struct CycleTwo(CycleOne);
16+
17+
fn assert_send<T: Send>() {}
18+
19+
fn bar() {
20+
assert_send::<CycleOne>();
21+
assert_send::<CycleTwo>();
22+
}
23+
24+
fn main() {}

0 commit comments

Comments
 (0)