Skip to content

Commit a745cbb

Browse files
committed
handle overflow in the EvalCtxt separately
1 parent c046831 commit a745cbb

File tree

6 files changed

+156
-189
lines changed

6 files changed

+156
-189
lines changed

compiler/rustc_trait_selection/src/solve/assembly/mod.rs

Lines changed: 34 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
//! Code shared by trait and projection goals for candidate assembly.
22
3-
use super::search_graph::OverflowHandler;
43
use super::{EvalCtxt, SolverMode};
54
use crate::traits::coherence;
65
use rustc_hir::def_id::DefId;
@@ -315,7 +314,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
315314
return ambig;
316315
}
317316

318-
let mut candidates = self.assemble_candidates_via_self_ty(goal);
317+
let mut candidates = self.assemble_candidates_via_self_ty(goal, 0);
319318

320319
self.assemble_blanket_impl_candidates(goal, &mut candidates);
321320

@@ -351,6 +350,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
351350
fn assemble_candidates_via_self_ty<G: GoalKind<'tcx>>(
352351
&mut self,
353352
goal: Goal<'tcx, G>,
353+
num_steps: usize,
354354
) -> Vec<Candidate<'tcx>> {
355355
debug_assert_eq!(goal, self.resolve_vars_if_possible(goal));
356356
if let Some(ambig) = self.assemble_self_ty_infer_ambiguity_response(goal) {
@@ -369,7 +369,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
369369

370370
self.assemble_coherence_unknowable_candidates(goal, &mut candidates);
371371

372-
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates);
372+
self.assemble_candidates_after_normalizing_self_ty(goal, &mut candidates, num_steps);
373373

374374
candidates
375375
}
@@ -393,46 +393,40 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
393393
&mut self,
394394
goal: Goal<'tcx, G>,
395395
candidates: &mut Vec<Candidate<'tcx>>,
396+
num_steps: usize,
396397
) {
397398
let tcx = self.tcx();
398399
let &ty::Alias(_, projection_ty) = goal.predicate.self_ty().kind() else { return };
399400

400-
let normalized_self_candidates: Result<_, NoSolution> =
401-
self.probe(|_| CandidateKind::NormalizedSelfTyAssembly).enter(|ecx| {
402-
ecx.with_incremented_depth(
403-
|ecx| {
404-
let result = ecx.evaluate_added_goals_and_make_canonical_response(
405-
Certainty::OVERFLOW,
406-
)?;
407-
Ok(vec![Candidate {
408-
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
409-
result,
410-
}])
411-
},
412-
|ecx| {
413-
let normalized_ty = ecx.next_ty_infer();
414-
let normalizes_to_goal = goal.with(
415-
tcx,
416-
ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
417-
);
418-
ecx.add_goal(normalizes_to_goal);
419-
let _ = ecx.try_evaluate_added_goals().inspect_err(|_| {
420-
debug!("self type normalization failed");
421-
})?;
422-
let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty);
423-
debug!(?normalized_ty, "self type normalized");
424-
// NOTE: Alternatively we could call `evaluate_goal` here and only
425-
// have a `Normalized` candidate. This doesn't work as long as we
426-
// use `CandidateSource` in winnowing.
427-
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
428-
Ok(ecx.assemble_candidates_via_self_ty(goal))
429-
},
430-
)
431-
});
432-
433-
if let Ok(normalized_self_candidates) = normalized_self_candidates {
434-
candidates.extend(normalized_self_candidates);
435-
}
401+
candidates.extend(self.probe(|_| CandidateKind::NormalizedSelfTyAssembly).enter(|ecx| {
402+
if num_steps < ecx.local_overflow_limit() {
403+
let normalized_ty = ecx.next_ty_infer();
404+
let normalizes_to_goal = goal.with(
405+
tcx,
406+
ty::ProjectionPredicate { projection_ty, term: normalized_ty.into() },
407+
);
408+
ecx.add_goal(normalizes_to_goal);
409+
if let Err(NoSolution) = ecx.try_evaluate_added_goals() {
410+
debug!("self type normalization failed");
411+
return vec![];
412+
}
413+
let normalized_ty = ecx.resolve_vars_if_possible(normalized_ty);
414+
debug!(?normalized_ty, "self type normalized");
415+
// NOTE: Alternatively we could call `evaluate_goal` here and only
416+
// have a `Normalized` candidate. This doesn't work as long as we
417+
// use `CandidateSource` in winnowing.
418+
let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty));
419+
ecx.assemble_candidates_via_self_ty(goal, num_steps + 1)
420+
} else {
421+
match ecx.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW) {
422+
Ok(result) => vec![Candidate {
423+
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
424+
result,
425+
}],
426+
Err(NoSolution) => vec![],
427+
}
428+
}
429+
}));
436430
}
437431

438432
#[instrument(level = "debug", skip_all)]
@@ -530,7 +524,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
530524
ty::Alias(_, _) | ty::Placeholder(..) | ty::Error(_) => (),
531525

532526
// FIXME: These should ideally not exist as a self type. It would be nice for
533-
// the builtin auto trait impls of generators should instead directly recurse
527+
// the builtin auto trait impls of generators to instead directly recurse
534528
// into the witness.
535529
ty::GeneratorWitness(_) | ty::GeneratorWitnessMIR(_, _) => (),
536530

compiler/rustc_trait_selection/src/solve/eval_ctxt.rs

Lines changed: 96 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use rustc_middle::traits::solve::{
1515
CanonicalInput, CanonicalResponse, Certainty, IsNormalizesToHack, PredefinedOpaques,
1616
PredefinedOpaquesData, QueryResult,
1717
};
18-
use rustc_middle::traits::DefiningAnchor;
18+
use rustc_middle::traits::{specialization_graph, DefiningAnchor};
1919
use rustc_middle::ty::{
2020
self, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
2121
TypeVisitableExt, TypeVisitor,
@@ -25,11 +25,10 @@ use rustc_span::DUMMY_SP;
2525
use std::io::Write;
2626
use std::ops::ControlFlow;
2727

28-
use crate::traits::specialization_graph;
2928
use crate::traits::vtable::{count_own_vtable_entries, prepare_vtable_segments, VtblSegment};
3029

3130
use super::inspect::ProofTreeBuilder;
32-
use super::search_graph::{self, OverflowHandler};
31+
use super::search_graph;
3332
use super::SolverMode;
3433
use super::{search_graph::SearchGraph, Goal};
3534
pub use select::InferCtxtSelectExt;
@@ -175,6 +174,10 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
175174
self.search_graph.solver_mode()
176175
}
177176

177+
pub(super) fn local_overflow_limit(&self) -> usize {
178+
self.search_graph.local_overflow_limit()
179+
}
180+
178181
/// Creates a root evaluation context and search graph. This should only be
179182
/// used from outside of any evaluation, and other methods should be preferred
180183
/// over using this manually (such as [`InferCtxtEvalExt::evaluate_root_goal`]).
@@ -479,101 +482,22 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
479482
let inspect = self.inspect.new_evaluate_added_goals();
480483
let inspect = core::mem::replace(&mut self.inspect, inspect);
481484

482-
let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new());
483-
let mut new_goals = NestedGoals::new();
484-
485-
let response = self.repeat_while_none(
486-
|_| Ok(Certainty::OVERFLOW),
487-
|this| {
488-
this.inspect.evaluate_added_goals_loop_start();
489-
490-
let mut has_changed = Err(Certainty::Yes);
491-
492-
if let Some(goal) = goals.normalizes_to_hack_goal.take() {
493-
// Replace the goal with an unconstrained infer var, so the
494-
// RHS does not affect projection candidate assembly.
495-
let unconstrained_rhs = this.next_term_infer_of_kind(goal.predicate.term);
496-
let unconstrained_goal = goal.with(
497-
this.tcx(),
498-
ty::ProjectionPredicate {
499-
projection_ty: goal.predicate.projection_ty,
500-
term: unconstrained_rhs,
501-
},
502-
);
503-
504-
let (_, certainty, instantiate_goals) =
505-
match this.evaluate_goal(IsNormalizesToHack::Yes, unconstrained_goal) {
506-
Ok(r) => r,
507-
Err(NoSolution) => return Some(Err(NoSolution)),
508-
};
509-
new_goals.goals.extend(instantiate_goals);
510-
511-
// Finally, equate the goal's RHS with the unconstrained var.
512-
// We put the nested goals from this into goals instead of
513-
// next_goals to avoid needing to process the loop one extra
514-
// time if this goal returns something -- I don't think this
515-
// matters in practice, though.
516-
match this.eq_and_get_goals(
517-
goal.param_env,
518-
goal.predicate.term,
519-
unconstrained_rhs,
520-
) {
521-
Ok(eq_goals) => {
522-
goals.goals.extend(eq_goals);
523-
}
524-
Err(NoSolution) => return Some(Err(NoSolution)),
525-
};
526-
527-
// We only look at the `projection_ty` part here rather than
528-
// looking at the "has changed" return from evaluate_goal,
529-
// because we expect the `unconstrained_rhs` part of the predicate
530-
// to have changed -- that means we actually normalized successfully!
531-
if goal.predicate.projection_ty
532-
!= this.resolve_vars_if_possible(goal.predicate.projection_ty)
533-
{
534-
has_changed = Ok(())
535-
}
536-
537-
match certainty {
538-
Certainty::Yes => {}
539-
Certainty::Maybe(_) => {
540-
// We need to resolve vars here so that we correctly
541-
// deal with `has_changed` in the next iteration.
542-
new_goals.normalizes_to_hack_goal =
543-
Some(this.resolve_vars_if_possible(goal));
544-
has_changed = has_changed.map_err(|c| c.unify_with(certainty));
545-
}
546-
}
547-
}
548-
549-
for goal in goals.goals.drain(..) {
550-
let (changed, certainty, instantiate_goals) =
551-
match this.evaluate_goal(IsNormalizesToHack::No, goal) {
552-
Ok(result) => result,
553-
Err(NoSolution) => return Some(Err(NoSolution)),
554-
};
555-
new_goals.goals.extend(instantiate_goals);
556-
557-
if changed {
558-
has_changed = Ok(());
559-
}
560-
561-
match certainty {
562-
Certainty::Yes => {}
563-
Certainty::Maybe(_) => {
564-
new_goals.goals.push(goal);
565-
has_changed = has_changed.map_err(|c| c.unify_with(certainty));
566-
}
567-
}
485+
let mut response = Ok(Certainty::OVERFLOW);
486+
for _ in 0..self.local_overflow_limit() {
487+
// FIXME: This match is a bit ugly, it might be nice to change the inspect
488+
// stuff to use a closure instead. which should hopefully simplify this a bit.
489+
match self.evaluate_added_goals_step() {
490+
Ok(Some(cert)) => {
491+
response = Ok(cert);
492+
break;
568493
}
569-
570-
core::mem::swap(&mut new_goals, &mut goals);
571-
match has_changed {
572-
Ok(()) => None,
573-
Err(certainty) => Some(Ok(certainty)),
494+
Ok(None) => {}
495+
Err(NoSolution) => {
496+
response = Err(NoSolution);
497+
break;
574498
}
575-
},
576-
);
499+
}
500+
}
577501

578502
self.inspect.eval_added_goals_result(response);
579503

@@ -584,9 +508,84 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
584508
let goal_evaluations = std::mem::replace(&mut self.inspect, inspect);
585509
self.inspect.added_goals_evaluation(goal_evaluations);
586510

587-
self.nested_goals = goals;
588511
response
589512
}
513+
514+
/// Iterate over all added goals: returning `Ok(Some(_))` in case we can stop rerunning.
515+
///
516+
/// Goals for the next step get directly added the the nested goals of the `EvalCtxt`.
517+
fn evaluate_added_goals_step(&mut self) -> Result<Option<Certainty>, NoSolution> {
518+
let tcx = self.tcx();
519+
let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new());
520+
521+
self.inspect.evaluate_added_goals_loop_start();
522+
// If this loop did not result in any progress, what's our final certainty.
523+
let mut unchanged_certainty = Some(Certainty::Yes);
524+
if let Some(goal) = goals.normalizes_to_hack_goal.take() {
525+
// Replace the goal with an unconstrained infer var, so the
526+
// RHS does not affect projection candidate assembly.
527+
let unconstrained_rhs = self.next_term_infer_of_kind(goal.predicate.term);
528+
let unconstrained_goal = goal.with(
529+
tcx,
530+
ty::ProjectionPredicate {
531+
projection_ty: goal.predicate.projection_ty,
532+
term: unconstrained_rhs,
533+
},
534+
);
535+
536+
let (_, certainty, instantiate_goals) =
537+
self.evaluate_goal(IsNormalizesToHack::Yes, unconstrained_goal)?;
538+
self.add_goals(instantiate_goals);
539+
540+
// Finally, equate the goal's RHS with the unconstrained var.
541+
// We put the nested goals from this into goals instead of
542+
// next_goals to avoid needing to process the loop one extra
543+
// time if this goal returns something -- I don't think this
544+
// matters in practice, though.
545+
let eq_goals =
546+
self.eq_and_get_goals(goal.param_env, goal.predicate.term, unconstrained_rhs)?;
547+
goals.goals.extend(eq_goals);
548+
549+
// We only look at the `projection_ty` part here rather than
550+
// looking at the "has changed" return from evaluate_goal,
551+
// because we expect the `unconstrained_rhs` part of the predicate
552+
// to have changed -- that means we actually normalized successfully!
553+
if goal.predicate.projection_ty
554+
!= self.resolve_vars_if_possible(goal.predicate.projection_ty)
555+
{
556+
unchanged_certainty = None;
557+
}
558+
559+
match certainty {
560+
Certainty::Yes => {}
561+
Certainty::Maybe(_) => {
562+
// We need to resolve vars here so that we correctly
563+
// deal with `has_changed` in the next iteration.
564+
self.set_normalizes_to_hack_goal(self.resolve_vars_if_possible(goal));
565+
unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty));
566+
}
567+
}
568+
}
569+
570+
for goal in goals.goals.drain(..) {
571+
let (has_changed, certainty, instantiate_goals) =
572+
self.evaluate_goal(IsNormalizesToHack::No, goal)?;
573+
self.add_goals(instantiate_goals);
574+
if has_changed {
575+
unchanged_certainty = None;
576+
}
577+
578+
match certainty {
579+
Certainty::Yes => {}
580+
Certainty::Maybe(_) => {
581+
self.add_goal(goal);
582+
unchanged_certainty = unchanged_certainty.map(|c| c.unify_with(certainty));
583+
}
584+
}
585+
}
586+
587+
Ok(unchanged_certainty)
588+
}
590589
}
591590

592591
impl<'tcx> EvalCtxt<'_, 'tcx> {

compiler/rustc_trait_selection/src/solve/eval_ctxt/select.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ use rustc_span::DUMMY_SP;
1414
use crate::solve::assembly::{Candidate, CandidateSource};
1515
use crate::solve::eval_ctxt::{EvalCtxt, GenerateProofTree};
1616
use crate::solve::inspect::ProofTreeBuilder;
17-
use crate::solve::search_graph::OverflowHandler;
1817
use crate::traits::StructurallyNormalizeExt;
1918
use crate::traits::TraitEngineExt;
2019

@@ -143,7 +142,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
143142
// the cycle anyways one step later.
144143
EvalCtxt::enter_canonical(
145144
self.tcx(),
146-
self.search_graph(),
145+
self.search_graph,
147146
canonical_input,
148147
// FIXME: This is wrong, idk if we even want to track stuff here.
149148
&mut ProofTreeBuilder::new_noop(),

compiler/rustc_trait_selection/src/solve/search_graph/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct StackElem<'tcx> {
2727

2828
pub(super) struct SearchGraph<'tcx> {
2929
mode: SolverMode,
30+
local_overflow_limit: usize,
3031
/// The stack of goals currently being computed.
3132
///
3233
/// An element is *deeper* in the stack if its index is *lower*.
@@ -39,6 +40,7 @@ impl<'tcx> SearchGraph<'tcx> {
3940
pub(super) fn new(tcx: TyCtxt<'tcx>, mode: SolverMode) -> SearchGraph<'tcx> {
4041
Self {
4142
mode,
43+
local_overflow_limit: tcx.recursion_limit().0.ilog2() as usize,
4244
stack: Default::default(),
4345
overflow_data: OverflowData::new(tcx),
4446
provisional_cache: ProvisionalCache::empty(),
@@ -49,6 +51,10 @@ impl<'tcx> SearchGraph<'tcx> {
4951
self.mode
5052
}
5153

54+
pub(super) fn local_overflow_limit(&self) -> usize {
55+
self.local_overflow_limit
56+
}
57+
5258
/// We do not use the global cache during coherence.
5359
///
5460
/// The trait solver behavior is different for coherence

0 commit comments

Comments
 (0)