Skip to content

Commit 61d6b20

Browse files
committed
do not track root depth of cycles
results in slightly cleaner logic while making the following commit easier
1 parent 41b624d commit 61d6b20

File tree

1 file changed

+32
-37
lines changed

1 file changed

+32
-37
lines changed

compiler/rustc_trait_selection/src/solve/search_graph.rs

+32-37
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use rustc_middle::ty;
1212
use rustc_middle::ty::TyCtxt;
1313
use rustc_session::Limit;
1414
use std::collections::hash_map::Entry;
15+
use std::mem;
1516

1617
rustc_index::newtype_index! {
1718
#[orderable]
@@ -25,23 +26,17 @@ struct StackEntry<'tcx> {
2526
/// The maximum depth reached by this stack entry, only up-to date
2627
/// for the top of the stack and lazily updated for the rest.
2728
reached_depth: StackDepth,
28-
/// In case of a cycle, the depth of the root.
29-
cycle_root_depth: StackDepth,
29+
/// Whether this entry is a cycle participant which is not a root.
30+
///
31+
/// If so, it must not be moved to the global cache. See
32+
/// [SearchGraph::cycle_participants] for more details.
33+
non_root_cycle_participant: bool,
3034

3135
encountered_overflow: bool,
3236
has_been_used: bool,
3337
/// Starts out as `None` and gets set when rerunning this
3438
/// goal in case we encounter a cycle.
3539
provisional_result: Option<QueryResult<'tcx>>,
36-
37-
/// We put only the root goal of a coinductive cycle into the global cache.
38-
///
39-
/// If we were to use that result when later trying to prove another cycle
40-
/// participant, we can end up with unstable query results.
41-
///
42-
/// See tests/ui/next-solver/coinduction/incompleteness-unstable-result.rs for
43-
/// an example of where this is needed.
44-
cycle_participants: FxHashSet<CanonicalInput<'tcx>>,
4540
}
4641

4742
pub(super) struct SearchGraph<'tcx> {
@@ -52,6 +47,14 @@ pub(super) struct SearchGraph<'tcx> {
5247
/// An element is *deeper* in the stack if its index is *lower*.
5348
stack: IndexVec<StackDepth, StackEntry<'tcx>>,
5449
stack_entries: FxHashMap<CanonicalInput<'tcx>, StackDepth>,
50+
/// We put only the root goal of a coinductive cycle into the global cache.
51+
///
52+
/// If we were to use that result when later trying to prove another cycle
53+
/// participant, we can end up with unstable query results.
54+
///
55+
/// See tests/ui/next-solver/coinduction/incompleteness-unstable-result.rs for
56+
/// an example of where this is needed.
57+
cycle_participants: FxHashSet<CanonicalInput<'tcx>>,
5558
}
5659

5760
impl<'tcx> SearchGraph<'tcx> {
@@ -61,6 +64,7 @@ impl<'tcx> SearchGraph<'tcx> {
6164
local_overflow_limit: tcx.recursion_limit().0.checked_ilog2().unwrap_or(0) as usize,
6265
stack: Default::default(),
6366
stack_entries: Default::default(),
67+
cycle_participants: Default::default(),
6468
}
6569
}
6670

@@ -109,7 +113,13 @@ impl<'tcx> SearchGraph<'tcx> {
109113
}
110114

111115
pub(super) fn is_empty(&self) -> bool {
112-
self.stack.is_empty()
116+
if self.stack.is_empty() {
117+
debug_assert!(self.stack_entries.is_empty());
118+
debug_assert!(self.cycle_participants.is_empty());
119+
true
120+
} else {
121+
false
122+
}
113123
}
114124

115125
pub(super) fn current_goal_is_normalizes_to(&self) -> bool {
@@ -209,11 +219,10 @@ impl<'tcx> SearchGraph<'tcx> {
209219
input,
210220
available_depth,
211221
reached_depth: depth,
212-
cycle_root_depth: depth,
222+
non_root_cycle_participant: false,
213223
encountered_overflow: false,
214224
has_been_used: false,
215225
provisional_result: None,
216-
cycle_participants: Default::default(),
217226
};
218227
assert_eq!(self.stack.push(entry), depth);
219228
v.insert(depth);
@@ -232,26 +241,11 @@ impl<'tcx> SearchGraph<'tcx> {
232241

233242
let stack_depth = *entry.get();
234243
debug!("encountered cycle with depth {stack_depth:?}");
235-
// We start by updating the root depth of all cycle participants, and
236-
// add all cycle participants to the root.
237-
let root_depth = self.stack[stack_depth].cycle_root_depth;
238-
let (prev, participants) = self.stack.raw.split_at_mut(stack_depth.as_usize() + 1);
239-
let root = &mut prev[root_depth.as_usize()];
244+
// We start by tagging all non-root cycle participants.
245+
let participants = self.stack.raw.iter_mut().skip(stack_depth.as_usize() + 1);
240246
for entry in participants {
241-
debug_assert!(entry.cycle_root_depth >= root_depth);
242-
entry.cycle_root_depth = root_depth;
243-
root.cycle_participants.insert(entry.input);
244-
// FIXME(@lcnr): I believe that this line is needed as we could
245-
// otherwise access a cache entry for the root of a cycle while
246-
// computing the result for a cycle participant. This can result
247-
// in unstable results due to incompleteness.
248-
//
249-
// However, a test for this would be an even more complex version of
250-
// tests/ui/traits/next-solver/coinduction/incompleteness-unstable-result.rs.
251-
// I did not bother to write such a test and we have no regression test
252-
// for this. It would be good to have such a test :)
253-
#[allow(rustc::potential_query_instability)]
254-
root.cycle_participants.extend(entry.cycle_participants.drain());
247+
entry.non_root_cycle_participant = true;
248+
self.cycle_participants.insert(entry.input);
255249
}
256250

257251
// If we're in a cycle, we have to retry proving the cycle head
@@ -325,23 +319,24 @@ impl<'tcx> SearchGraph<'tcx> {
325319
//
326320
// It is not possible for any nested goal to depend on something deeper on the
327321
// stack, as this would have also updated the depth of the current goal.
328-
if final_entry.cycle_root_depth == self.stack.next_index() {
322+
if !final_entry.non_root_cycle_participant {
329323
// When encountering a cycle, both inductive and coinductive, we only
330324
// move the root into the global cache. We also store all other cycle
331325
// participants involved.
332326
//
333-
// We disable the global cache entry of the root goal if a cycle
327+
// We must not use the global cache entry of a root goal if a cycle
334328
// participant is on the stack. This is necessary to prevent unstable
335-
// results. See the comment of `StackEntry::cycle_participants` for
329+
// results. See the comment of `SearchGraph::cycle_participants` for
336330
// more details.
337331
let reached_depth = final_entry.reached_depth.as_usize() - self.stack.len();
332+
let cycle_participants = mem::take(&mut self.cycle_participants);
338333
self.global_cache(tcx).insert(
339334
tcx,
340335
input,
341336
proof_tree,
342337
reached_depth,
343338
final_entry.encountered_overflow,
344-
final_entry.cycle_participants,
339+
cycle_participants,
345340
dep_node,
346341
result,
347342
)

0 commit comments

Comments
 (0)