Skip to content

Commit 88271de

Browse files
committed
avoid always rerunning in case of a cycle
1 parent 118453c commit 88271de

File tree

3 files changed

+73
-34
lines changed

3 files changed

+73
-34
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -4563,6 +4563,7 @@ checksum = "8ba09476327c4b70ccefb6180f046ef588c26a24cf5d269a9feba316eb4f029f"
45634563
name = "rustc_trait_selection"
45644564
version = "0.0.0"
45654565
dependencies = [
4566+
"bitflags 2.4.1",
45664567
"itertools",
45674568
"rustc_ast",
45684569
"rustc_attr",

compiler/rustc_trait_selection/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ edition = "2021"
55

66
[dependencies]
77
# tidy-alphabetical-start
8+
bitflags = "2.4.1"
89
itertools = "0.11.0"
910
rustc_ast = { path = "../rustc_ast" }
1011
rustc_attr = { path = "../rustc_attr" }

compiler/rustc_trait_selection/src/solve/search_graph.rs

+71-34
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ rustc_index::newtype_index! {
1818
pub struct StackDepth {}
1919
}
2020

21+
bitflags::bitflags! {
22+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23+
struct HasBeenUsed: u8 {
24+
const INDUCTIVE_CYCLE = 1 << 0;
25+
const COINDUCTIVE_CYCLE = 1 << 1;
26+
}
27+
}
28+
2129
#[derive(Debug)]
2230
struct StackEntry<'tcx> {
2331
input: CanonicalInput<'tcx>,
@@ -32,7 +40,7 @@ struct StackEntry<'tcx> {
3240
non_root_cycle_participant: Option<StackDepth>,
3341

3442
encountered_overflow: bool,
35-
has_been_used: bool,
43+
has_been_used: HasBeenUsed,
3644
/// Starts out as `None` and gets set when rerunning this
3745
/// goal in case we encounter a cycle.
3846
provisional_result: Option<QueryResult<'tcx>>,
@@ -195,9 +203,11 @@ impl<'tcx> SearchGraph<'tcx> {
195203
fn tag_cycle_participants(
196204
stack: &mut IndexVec<StackDepth, StackEntry<'tcx>>,
197205
cycle_participants: &mut FxHashSet<CanonicalInput<'tcx>>,
206+
usage_kind: HasBeenUsed,
198207
head: StackDepth,
199208
) {
200-
stack[head].has_been_used = true;
209+
stack[head].has_been_used |= usage_kind;
210+
debug_assert!(!stack[head].has_been_used.is_empty());
201211
for entry in &mut stack.raw[head.index() + 1..] {
202212
entry.non_root_cycle_participant = entry.non_root_cycle_participant.max(Some(head));
203213
cycle_participants.insert(entry.input);
@@ -272,29 +282,33 @@ impl<'tcx> SearchGraph<'tcx> {
272282

273283
// Check whether the goal is in the provisional cache.
274284
let cache_entry = self.provisional_cache.entry(input).or_default();
275-
if let Some(with_coinductive_stack) = &mut cache_entry.with_coinductive_stack
285+
if let Some(with_coinductive_stack) = &cache_entry.with_coinductive_stack
276286
&& Self::stack_coinductive_from(tcx, &self.stack, with_coinductive_stack.head)
277287
{
278288
// We have a nested goal which is already in the provisional cache, use
279-
// its result.
289+
// its result. We do not provide any usage kind as that should have been
290+
// already set correctly while computing the cache entry.
280291
inspect
281292
.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::ProvisionalCacheHit);
282293
Self::tag_cycle_participants(
283294
&mut self.stack,
284295
&mut self.cycle_participants,
296+
HasBeenUsed::empty(),
285297
with_coinductive_stack.head,
286298
);
287299
return with_coinductive_stack.result;
288-
} else if let Some(with_inductive_stack) = &mut cache_entry.with_inductive_stack
300+
} else if let Some(with_inductive_stack) = &cache_entry.with_inductive_stack
289301
&& !Self::stack_coinductive_from(tcx, &self.stack, with_inductive_stack.head)
290302
{
291303
// We have a nested goal which is already in the provisional cache, use
292-
// its result.
304+
// its result. We do not provide any usage kind as that should have been
305+
// already set correctly while computing the cache entry.
293306
inspect
294307
.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::ProvisionalCacheHit);
295308
Self::tag_cycle_participants(
296309
&mut self.stack,
297310
&mut self.cycle_participants,
311+
HasBeenUsed::empty(),
298312
with_inductive_stack.head,
299313
);
300314
return with_inductive_stack.result;
@@ -310,21 +324,27 @@ impl<'tcx> SearchGraph<'tcx> {
310324
// Finally we can return either the provisional response for that goal if we have a
311325
// coinductive cycle or an ambiguous result if the cycle is inductive.
312326
inspect.goal_evaluation_kind(inspect::WipCanonicalGoalEvaluationKind::CycleInStack);
327+
let is_coinductive_cycle = Self::stack_coinductive_from(tcx, &self.stack, stack_depth);
328+
let usage_kind = if is_coinductive_cycle {
329+
HasBeenUsed::COINDUCTIVE_CYCLE
330+
} else {
331+
HasBeenUsed::INDUCTIVE_CYCLE
332+
};
313333
Self::tag_cycle_participants(
314334
&mut self.stack,
315335
&mut self.cycle_participants,
336+
usage_kind,
316337
stack_depth,
317338
);
339+
340+
// Return the provisional result or, if we're in the first iteration,
341+
// start with no constraints.
318342
return if let Some(result) = self.stack[stack_depth].provisional_result {
319343
result
344+
} else if is_coinductive_cycle {
345+
Self::response_no_constraints(tcx, input, Certainty::Yes)
320346
} else {
321-
// If we don't have a provisional result yet we're in the first iteration,
322-
// so we start with no constraints.
323-
if Self::stack_coinductive_from(tcx, &self.stack, stack_depth) {
324-
Self::response_no_constraints(tcx, input, Certainty::Yes)
325-
} else {
326-
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW)
327-
}
347+
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW)
328348
};
329349
} else {
330350
// No entry, we push this goal on the stack and try to prove it.
@@ -335,7 +355,7 @@ impl<'tcx> SearchGraph<'tcx> {
335355
reached_depth: depth,
336356
non_root_cycle_participant: None,
337357
encountered_overflow: false,
338-
has_been_used: false,
358+
has_been_used: HasBeenUsed::empty(),
339359
provisional_result: None,
340360
};
341361
assert_eq!(self.stack.push(entry), depth);
@@ -354,41 +374,58 @@ impl<'tcx> SearchGraph<'tcx> {
354374
// point we are done.
355375
for _ in 0..self.local_overflow_limit() {
356376
let result = prove_goal(self, inspect);
377+
let stack_entry = self.pop_stack();
378+
debug_assert_eq!(stack_entry.input, input);
379+
380+
// If the current goal is not the root of a cycle, we are done.
381+
if stack_entry.has_been_used.is_empty() {
382+
return (stack_entry, result);
383+
}
357384

358-
// Check whether the current goal is the root of a cycle.
359-
// If so, we have to retry proving the cycle head
360-
// until its result reaches a fixpoint. We need to do so for
361-
// all cycle heads, not only for the root.
385+
// If it is a cycle head, we have to keep trying to prove it until
386+
// we reach a fixpoint. We need to do so for all cycle heads,
387+
// not only for the root.
362388
//
363389
// See tests/ui/traits/next-solver/cycles/fixpoint-rerun-all-cycle-heads.rs
364390
// for an example.
365-
let stack_entry = self.pop_stack();
366-
debug_assert_eq!(stack_entry.input, input);
367-
if stack_entry.has_been_used {
368-
Self::clear_dependent_provisional_results(
369-
&mut self.provisional_cache,
370-
self.stack.next_index(),
371-
);
372-
}
373391

374-
if stack_entry.has_been_used
375-
&& stack_entry.provisional_result.map_or(true, |r| r != result)
376-
{
377-
// If so, update its provisional result and reevaluate it.
392+
// Start by clearing all provisional cache entries which depend on this
393+
// the current goal.
394+
Self::clear_dependent_provisional_results(
395+
&mut self.provisional_cache,
396+
self.stack.next_index(),
397+
);
398+
399+
// Check whether we reached a fixpoint, either because the final result
400+
// is equal to the provisional result of the previous iteration, or because
401+
// this was only the root of either coinductive or inductive cycles, and the
402+
// final result is equal to the initial response for that case.
403+
let reached_fixpoint = if let Some(r) = stack_entry.provisional_result {
404+
r == result
405+
} else if stack_entry.has_been_used == HasBeenUsed::COINDUCTIVE_CYCLE {
406+
Self::response_no_constraints(tcx, input, Certainty::Yes) == result
407+
} else if stack_entry.has_been_used == HasBeenUsed::INDUCTIVE_CYCLE {
408+
Self::response_no_constraints(tcx, input, Certainty::OVERFLOW) == result
409+
} else {
410+
false
411+
};
412+
413+
if reached_fixpoint {
414+
return (stack_entry, result);
415+
} else {
416+
// Did not reach a fixpoint, update the provisional result and reevaluate.
378417
let depth = self.stack.push(StackEntry {
379-
has_been_used: false,
418+
has_been_used: HasBeenUsed::empty(),
380419
provisional_result: Some(result),
381420
..stack_entry
382421
});
383422
debug_assert_eq!(self.provisional_cache[&input].stack_depth, Some(depth));
384-
} else {
385-
return (stack_entry, result);
386423
}
387424
}
388425

389426
debug!("canonical cycle overflow");
390427
let current_entry = self.pop_stack();
391-
debug_assert!(!current_entry.has_been_used);
428+
debug_assert!(current_entry.has_been_used.is_empty());
392429
let result = Self::response_no_constraints(tcx, input, Certainty::OVERFLOW);
393430
(current_entry, result)
394431
});

0 commit comments

Comments
 (0)