Skip to content

Commit c018ae5

Browse files
committed
Auto merge of #144991 - lcnr:ignore-usages-from-ignored-candidates, r=BoxyUwU
ignore head usages from ignored candidates Fixes rust-lang/trait-system-refactor-initiative#210. The test now takes 0.8s to compile, which seems good enough to me. We are actually still walking the entire graph here, we're just avoiding unnecessary reruns. The basic idea is that if we've only accessed a cycle head inside of a candidate which didn't impact the final result of our goal, we don't need to rerun that cycle head even if is the used provisional result differs from the final result. We also use this information when rebasing goals over their cycle heads. If a goal doesn't actually depend on the result of that cycle head, rebasing always succeeds. However, we still need to make sure we track the fact that we relied on the cycle head at all to avoid query instability. It is implemented by tracking the number of `HeadUsages` for every head while evaluating goals. We then also track the head usages while evaluating a single candidate, which the search graph returns as `CandidateHeadUsages`. If there is now an always applicable candidate candidate we know that all other candidates with that source did not matter. We then call `fn ignore_candidate_head_usages` to remove the usages while evaluating this single candidate from the total. If the final `HeadUsages` end up empty, we know that the result of this cycle head did not matter when evaluating its nested goals.
2 parents ba412a6 + 8afe306 commit c018ae5

File tree

8 files changed

+429
-149
lines changed

8 files changed

+429
-149
lines changed

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

Lines changed: 66 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::ops::ControlFlow;
88
use derive_where::derive_where;
99
use rustc_type_ir::inherent::*;
1010
use rustc_type_ir::lang_items::TraitSolverLangItem;
11+
use rustc_type_ir::search_graph::CandidateHeadUsages;
1112
use rustc_type_ir::solve::SizedTraitKind;
1213
use rustc_type_ir::{
1314
self as ty, Interner, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable,
@@ -33,10 +34,11 @@ enum AliasBoundKind {
3334
///
3435
/// It consists of both the `source`, which describes how that goal would be proven,
3536
/// and the `result` when using the given `source`.
36-
#[derive_where(Clone, Debug; I: Interner)]
37+
#[derive_where(Debug; I: Interner)]
3738
pub(super) struct Candidate<I: Interner> {
3839
pub(super) source: CandidateSource<I>,
3940
pub(super) result: CanonicalResponse<I>,
41+
pub(super) head_usages: CandidateHeadUsages,
4042
}
4143

4244
/// Methods used to assemble candidates for either trait or projection goals.
@@ -116,8 +118,11 @@ where
116118
ecx: &mut EvalCtxt<'_, D>,
117119
goal: Goal<I, Self>,
118120
assumption: I::Clause,
119-
) -> Result<Candidate<I>, NoSolution> {
120-
Self::fast_reject_assumption(ecx, goal, assumption)?;
121+
) -> Result<Candidate<I>, CandidateHeadUsages> {
122+
match Self::fast_reject_assumption(ecx, goal, assumption) {
123+
Ok(()) => {}
124+
Err(NoSolution) => return Err(CandidateHeadUsages::default()),
125+
}
121126

122127
// Dealing with `ParamEnv` candidates is a bit of a mess as we need to lazily
123128
// check whether the candidate is global while considering normalization.
@@ -126,18 +131,23 @@ where
126131
// in `probe` even if the candidate does not apply before we get there. We handle this
127132
// by using a `Cell` here. We only ever write into it inside of `match_assumption`.
128133
let source = Cell::new(CandidateSource::ParamEnv(ParamEnvSource::Global));
129-
ecx.probe(|result: &QueryResult<I>| inspect::ProbeKind::TraitCandidate {
130-
source: source.get(),
131-
result: *result,
132-
})
133-
.enter(|ecx| {
134-
Self::match_assumption(ecx, goal, assumption, |ecx| {
135-
ecx.try_evaluate_added_goals()?;
136-
source.set(ecx.characterize_param_env_assumption(goal.param_env, assumption)?);
137-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
134+
let (result, head_usages) = ecx
135+
.probe(|result: &QueryResult<I>| inspect::ProbeKind::TraitCandidate {
136+
source: source.get(),
137+
result: *result,
138138
})
139-
})
140-
.map(|result| Candidate { source: source.get(), result })
139+
.enter_single_candidate(|ecx| {
140+
Self::match_assumption(ecx, goal, assumption, |ecx| {
141+
ecx.try_evaluate_added_goals()?;
142+
source.set(ecx.characterize_param_env_assumption(goal.param_env, assumption)?);
143+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
144+
})
145+
});
146+
147+
match result {
148+
Ok(result) => Ok(Candidate { source: source.get(), result, head_usages }),
149+
Err(NoSolution) => Err(head_usages),
150+
}
141151
}
142152

143153
/// Try equating an assumption predicate against a goal's predicate. If it
@@ -355,6 +365,19 @@ pub(super) enum AssembleCandidatesFrom {
355365
EnvAndBounds,
356366
}
357367

368+
/// This is currently used to track the [CandidateHeadUsages] of all failed `ParamEnv`
369+
/// candidates. This is then used to ignore their head usages in case there's another
370+
/// always applicable `ParamEnv` candidate. Look at how `param_env_head_usages` is
371+
/// used in the code for more details.
372+
///
373+
/// We could easily extend this to also ignore head usages of other ignored candidates.
374+
/// However, we currently don't have any tests where this matters and the complexity of
375+
/// doing so does not feel worth it for now.
376+
#[derive(Debug)]
377+
pub(super) struct FailedCandidateInfo {
378+
pub param_env_head_usages: CandidateHeadUsages,
379+
}
380+
358381
impl<D, I> EvalCtxt<'_, D>
359382
where
360383
D: SolverDelegate<Interner = I>,
@@ -364,16 +387,20 @@ where
364387
&mut self,
365388
goal: Goal<I, G>,
366389
assemble_from: AssembleCandidatesFrom,
367-
) -> Vec<Candidate<I>> {
390+
) -> (Vec<Candidate<I>>, FailedCandidateInfo) {
391+
let mut candidates = vec![];
392+
let mut failed_candidate_info =
393+
FailedCandidateInfo { param_env_head_usages: CandidateHeadUsages::default() };
368394
let Ok(normalized_self_ty) =
369395
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
370396
else {
371-
return vec![];
397+
return (candidates, failed_candidate_info);
372398
};
373399

374400
if normalized_self_ty.is_ty_var() {
375401
debug!("self type has been normalized to infer");
376-
return self.forced_ambiguity(MaybeCause::Ambiguity).into_iter().collect();
402+
candidates.extend(self.forced_ambiguity(MaybeCause::Ambiguity));
403+
return (candidates, failed_candidate_info);
377404
}
378405

379406
let goal: Goal<I, G> = goal
@@ -382,16 +409,15 @@ where
382409
// normalizing the self type as well, since type variables are not uniquified.
383410
let goal = self.resolve_vars_if_possible(goal);
384411

385-
let mut candidates = vec![];
386-
387412
if let TypingMode::Coherence = self.typing_mode()
388413
&& let Ok(candidate) = self.consider_coherence_unknowable_candidate(goal)
389414
{
390-
return vec![candidate];
415+
candidates.push(candidate);
416+
return (candidates, failed_candidate_info);
391417
}
392418

393419
self.assemble_alias_bound_candidates(goal, &mut candidates);
394-
self.assemble_param_env_candidates(goal, &mut candidates);
420+
self.assemble_param_env_candidates(goal, &mut candidates, &mut failed_candidate_info);
395421

396422
match assemble_from {
397423
AssembleCandidatesFrom::All => {
@@ -423,7 +449,7 @@ where
423449
AssembleCandidatesFrom::EnvAndBounds => {}
424450
}
425451

426-
candidates
452+
(candidates, failed_candidate_info)
427453
}
428454

429455
pub(super) fn forced_ambiguity(
@@ -584,9 +610,15 @@ where
584610
&mut self,
585611
goal: Goal<I, G>,
586612
candidates: &mut Vec<Candidate<I>>,
613+
failed_candidate_info: &mut FailedCandidateInfo,
587614
) {
588615
for assumption in goal.param_env.caller_bounds().iter() {
589-
candidates.extend(G::probe_and_consider_param_env_candidate(self, goal, assumption));
616+
match G::probe_and_consider_param_env_candidate(self, goal, assumption) {
617+
Ok(candidate) => candidates.push(candidate),
618+
Err(head_usages) => {
619+
failed_candidate_info.param_env_head_usages.merge_usages(head_usages)
620+
}
621+
}
590622
}
591623
}
592624

@@ -661,7 +693,11 @@ where
661693
if let Ok(result) =
662694
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
663695
{
664-
candidates.push(Candidate { source: CandidateSource::AliasBound, result });
696+
candidates.push(Candidate {
697+
source: CandidateSource::AliasBound,
698+
result,
699+
head_usages: CandidateHeadUsages::default(),
700+
});
665701
}
666702
return;
667703
}
@@ -959,7 +995,7 @@ where
959995
// Even when a trait bound has been proven using a where-bound, we
960996
// still need to consider alias-bounds for normalization, see
961997
// `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
962-
let mut candidates: Vec<_> = self
998+
let (mut candidates, _) = self
963999
.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);
9641000

9651001
// We still need to prefer where-bounds over alias-bounds however.
@@ -972,23 +1008,20 @@ where
9721008
return inject_normalize_to_rigid_candidate(self);
9731009
}
9741010

975-
if let Some(response) = self.try_merge_candidates(&candidates) {
1011+
if let Some((response, _)) = self.try_merge_candidates(&candidates) {
9761012
Ok(response)
9771013
} else {
9781014
self.flounder(&candidates)
9791015
}
9801016
}
9811017
TraitGoalProvenVia::Misc => {
982-
let mut candidates =
1018+
let (mut candidates, _) =
9831019
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);
9841020

9851021
// Prefer "orphaned" param-env normalization predicates, which are used
9861022
// (for example, and ideally only) when proving item bounds for an impl.
987-
let candidates_from_env: Vec<_> = candidates
988-
.extract_if(.., |c| matches!(c.source, CandidateSource::ParamEnv(_)))
989-
.collect();
990-
if let Some(response) = self.try_merge_candidates(&candidates_from_env) {
991-
return Ok(response);
1023+
if candidates.iter().any(|c| matches!(c.source, CandidateSource::ParamEnv(_))) {
1024+
candidates.retain(|c| matches!(c.source, CandidateSource::ParamEnv(_)));
9921025
}
9931026

9941027
// We drop specialized impls to allow normalization via a final impl here. In case
@@ -997,7 +1030,7 @@ where
9971030
// means we can just ignore inference constraints and don't have to special-case
9981031
// constraining the normalized-to `term`.
9991032
self.filter_specialized_impls(AllowInferenceConstraints::Yes, &mut candidates);
1000-
if let Some(response) = self.try_merge_candidates(&candidates) {
1033+
if let Some((response, _)) = self.try_merge_candidates(&candidates) {
10011034
Ok(response)
10021035
} else {
10031036
self.flounder(&candidates)

compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt;
88
use rustc_type_ir::inherent::*;
99
use rustc_type_ir::relate::Relate;
1010
use rustc_type_ir::relate::solver_relating::RelateExt;
11-
use rustc_type_ir::search_graph::PathKind;
11+
use rustc_type_ir::search_graph::{CandidateHeadUsages, PathKind};
1212
use rustc_type_ir::{
1313
self as ty, CanonicalVarValues, InferCtxtLike, Interner, TypeFoldable, TypeFolder,
1414
TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
@@ -399,6 +399,10 @@ where
399399
result
400400
}
401401

402+
pub(super) fn ignore_candidate_head_usages(&mut self, usages: CandidateHeadUsages) {
403+
self.search_graph.ignore_candidate_head_usages(usages);
404+
}
405+
402406
/// Recursively evaluates `goal`, returning whether any inference vars have
403407
/// been constrained and the certainty of the result.
404408
fn evaluate_goal(

compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::marker::PhantomData;
22

3+
use rustc_type_ir::search_graph::CandidateHeadUsages;
34
use rustc_type_ir::{InferCtxtLike, Interner};
45
use tracing::instrument;
56

@@ -25,6 +26,20 @@ where
2526
D: SolverDelegate<Interner = I>,
2627
I: Interner,
2728
{
29+
pub(in crate::solve) fn enter_single_candidate(
30+
self,
31+
f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T,
32+
) -> (T, CandidateHeadUsages) {
33+
self.ecx.search_graph.enter_single_candidate();
34+
let mut candidate_usages = CandidateHeadUsages::default();
35+
let result = self.enter(|ecx| {
36+
let result = f(ecx);
37+
candidate_usages = ecx.search_graph.finish_single_candidate();
38+
result
39+
});
40+
(result, candidate_usages)
41+
}
42+
2843
pub(in crate::solve) fn enter(self, f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T) -> T {
2944
let ProbeCtxt { ecx: outer, probe_kind, _result } = self;
3045

@@ -78,7 +93,8 @@ where
7893
self,
7994
f: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
8095
) -> Result<Candidate<I>, NoSolution> {
81-
self.cx.enter(|ecx| f(ecx)).map(|result| Candidate { source: self.source, result })
96+
let (result, head_usages) = self.cx.enter_single_candidate(f);
97+
result.map(|result| Candidate { source: self.source, result, head_usages })
8298
}
8399
}
84100

compiler/rustc_next_trait_solver/src/solve/mod.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,12 @@ where
236236
}
237237
}
238238

239+
#[derive(Debug)]
240+
enum MergeCandidateInfo {
241+
AlwaysApplicable(usize),
242+
EqualResponse,
243+
}
244+
239245
impl<D, I> EvalCtxt<'_, D>
240246
where
241247
D: SolverDelegate<Interner = I>,
@@ -248,23 +254,25 @@ where
248254
fn try_merge_candidates(
249255
&mut self,
250256
candidates: &[Candidate<I>],
251-
) -> Option<CanonicalResponse<I>> {
257+
) -> Option<(CanonicalResponse<I>, MergeCandidateInfo)> {
252258
if candidates.is_empty() {
253259
return None;
254260
}
255261

262+
let always_applicable = candidates.iter().enumerate().find(|(_, candidate)| {
263+
candidate.result.value.certainty == Certainty::Yes
264+
&& has_no_inference_or_external_constraints(candidate.result)
265+
});
266+
if let Some((i, c)) = always_applicable {
267+
return Some((c.result, MergeCandidateInfo::AlwaysApplicable(i)));
268+
}
269+
256270
let one: CanonicalResponse<I> = candidates[0].result;
257271
if candidates[1..].iter().all(|candidate| candidate.result == one) {
258-
return Some(one);
272+
return Some((one, MergeCandidateInfo::EqualResponse));
259273
}
260274

261-
candidates
262-
.iter()
263-
.find(|candidate| {
264-
candidate.result.value.certainty == Certainty::Yes
265-
&& has_no_inference_or_external_constraints(candidate.result)
266-
})
267-
.map(|candidate| candidate.result)
275+
None
268276
}
269277

270278
fn bail_with_ambiguity(&mut self, candidates: &[Candidate<I>]) -> CanonicalResponse<I> {

0 commit comments

Comments
 (0)