Skip to content

Commit bd005a2

Browse files
committed
forego caching for all participants in cycles, apart from root node
1 parent 47e0803 commit bd005a2

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed

src/librustc/traits/select.rs

+48-2
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ use crate::hir;
4343
use rustc_data_structures::bit_set::GrowableBitSet;
4444
use rustc_data_structures::sync::Lock;
4545
use rustc_target::spec::abi::Abi;
46+
use std::cell::Cell;
4647
use std::cmp;
4748
use std::fmt::{self, Display};
4849
use std::iter;
@@ -153,6 +154,31 @@ struct TraitObligationStack<'prev, 'tcx: 'prev> {
153154
/// selection-context's freshener. Used to check for recursion.
154155
fresh_trait_ref: ty::PolyTraitRef<'tcx>,
155156

157+
/// Starts out as false -- if, during evaluation, we encounter a
158+
/// cycle, then we will set this flag to true for all participants
159+
/// in the cycle (apart from the "head" node). These participants
160+
/// will then forego caching their results. This is not the most
161+
/// efficient solution, but it addresses #60010. The problem we
162+
/// are trying to prevent:
163+
///
164+
/// - If you have `A: AutoTrait` requires `B: AutoTrait` and `C: NonAutoTrait`
165+
/// - `B: AutoTrait` requires `A: AutoTrait` (coinductive cycle, ok)
166+
/// - `C: NonAutoTrait` requires `A: AutoTrait` (non-coinductive cycle, not ok)
167+
///
168+
/// you don't want to cache that `B: AutoTrait` or `A: AutoTrait`
169+
/// is `EvaluatedToOk`; this is because they were only considered
170+
/// ok on the premise that if `A: AutoTrait` held, but we indeed
171+
/// encountered a problem (later on) with `A: AutoTrait. So we
172+
/// currently set a flag on the stack node for `B: AutoTrait` (as
173+
/// well as the second instance of `A: AutoTrait`) to supress
174+
/// caching.
175+
///
176+
/// This is a simple, targeted fix. The correct fix requires
177+
/// deeper changes, but would permit more caching: we could
178+
/// basically defer caching until we have fully evaluated the
179+
/// tree, and then cache the entire tree at once.
180+
in_cycle: Cell<bool>,
181+
156182
previous: TraitObligationStackList<'prev, 'tcx>,
157183
}
158184

@@ -840,8 +866,16 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
840866
let (result, dep_node) = self.in_task(|this| this.evaluate_stack(&stack));
841867
let result = result?;
842868

843-
debug!("CACHE MISS: EVAL({:?})={:?}", fresh_trait_ref, result);
844-
self.insert_evaluation_cache(obligation.param_env, fresh_trait_ref, dep_node, result);
869+
if !stack.in_cycle.get() {
870+
debug!("CACHE MISS: EVAL({:?})={:?}", fresh_trait_ref, result);
871+
self.insert_evaluation_cache(obligation.param_env, fresh_trait_ref, dep_node, result);
872+
} else {
873+
debug!(
874+
"evaluate_trait_predicate_recursively: skipping cache because {:?} \
875+
is a cycle participant",
876+
fresh_trait_ref,
877+
);
878+
}
845879

846880
Ok(result)
847881
}
@@ -948,6 +982,17 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
948982
{
949983
debug!("evaluate_stack({:?}) --> recursive", stack.fresh_trait_ref);
950984

985+
// If we have a stack like `A B C D E A`, where the top of
986+
// the stack is the final `A`, then this will iterate over
987+
// `A, E, D, C, B` -- i.e., all the participants apart
988+
// from the cycle head. We mark them as participating in a
989+
// cycle. This suppresses caching for those nodes. See
990+
// `in_cycle` field for more details.
991+
for item in stack.iter().take(rec_index + 1) {
992+
debug!("evaluate_stack: marking {:?} as cycle participant", item.fresh_trait_ref);
993+
item.in_cycle.set(true);
994+
}
995+
951996
// Subtle: when checking for a coinductive cycle, we do
952997
// not compare using the "freshened trait refs" (which
953998
// have erased regions) but rather the fully explicit
@@ -3692,6 +3737,7 @@ impl<'cx, 'gcx, 'tcx> SelectionContext<'cx, 'gcx, 'tcx> {
36923737
TraitObligationStack {
36933738
obligation,
36943739
fresh_trait_ref,
3740+
in_cycle: Cell::new(false),
36953741
previous: previous_stack,
36963742
}
36973743
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Test that we properly detect the cycle amongst the traits
2+
// here and report an error.
3+
4+
use std::panic::RefUnwindSafe;
5+
6+
trait Database {
7+
type Storage;
8+
}
9+
trait HasQueryGroup {}
10+
trait Query<DB> {
11+
type Data;
12+
}
13+
trait SourceDatabase {
14+
fn parse(&self) {
15+
loop {}
16+
}
17+
}
18+
19+
struct ParseQuery;
20+
struct RootDatabase {
21+
_runtime: Runtime<RootDatabase>,
22+
}
23+
struct Runtime<DB: Database> {
24+
_storage: Box<DB::Storage>,
25+
}
26+
struct SalsaStorage {
27+
_parse: <ParseQuery as Query<RootDatabase>>::Data, //~ ERROR overflow
28+
}
29+
30+
impl Database for RootDatabase { //~ ERROR overflow
31+
type Storage = SalsaStorage;
32+
}
33+
impl HasQueryGroup for RootDatabase {}
34+
impl<DB> Query<DB> for ParseQuery
35+
where
36+
DB: SourceDatabase,
37+
DB: Database,
38+
{
39+
type Data = RootDatabase;
40+
}
41+
impl<T> SourceDatabase for T
42+
where
43+
T: RefUnwindSafe,
44+
T: HasQueryGroup,
45+
{
46+
}
47+
48+
pub(crate) fn goto_implementation(db: &RootDatabase) -> u32 {
49+
// This is not satisfied:
50+
//
51+
// - `RootDatabase: SourceDatabase`
52+
// - requires `RootDatabase: RefUnwindSafe` + `RootDatabase: HasQueryGroup`
53+
// - `RootDatabase: RefUnwindSafe`
54+
// - requires `Runtime<RootDatabase>: RefUnwindSafe`
55+
// - `Runtime<RootDatabase>: RefUnwindSafe`
56+
// - requires `DB::Storage: RefUnwindSafe` (`SalsaStorage: RefUnwindSafe`)
57+
// - `SalsaStorage: RefUnwindSafe`
58+
// - requires `<ParseQuery as Query<RootDatabase>>::Data: RefUnwindSafe`,
59+
// which means `ParseQuery: Query<RootDatabase>`
60+
// - `ParseQuery: Query<RootDatabase>`
61+
// - requires `RootDatabase: SourceDatabase`,
62+
// - `RootDatabase: SourceDatabase` is already on the stack, so we have a
63+
// cycle with non-coinductive participants
64+
//
65+
// we used to fail to report an error here because we got the
66+
// caching wrong.
67+
SourceDatabase::parse(db);
68+
22
69+
}
70+
71+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error[E0275]: overflow evaluating the requirement `RootDatabase: SourceDatabase`
2+
--> $DIR/cycle-cache-err-60010.rs:27:5
3+
|
4+
LL | _parse: <ParseQuery as Query<RootDatabase>>::Data,
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= note: required because of the requirements on the impl of `Query<RootDatabase>` for `ParseQuery`
8+
9+
error[E0275]: overflow evaluating the requirement `RootDatabase: SourceDatabase`
10+
--> $DIR/cycle-cache-err-60010.rs:30:6
11+
|
12+
LL | impl Database for RootDatabase {
13+
| ^^^^^^^^
14+
|
15+
= note: required because of the requirements on the impl of `Query<RootDatabase>` for `ParseQuery`
16+
= note: required because it appears within the type `SalsaStorage`
17+
18+
error: aborting due to 2 previous errors
19+
20+
For more information about this error, try `rustc --explain E0275`.

0 commit comments

Comments
 (0)