From cf0893c54ef72b1c0d0f803657b4af348bb24e11 Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 28 Jul 2025 09:34:44 +0000 Subject: [PATCH 1/2] dont assemble shadowed impl candidates --- .../src/solve/assembly/mod.rs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index b2d4014634855..1010f3bd0a120 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -21,7 +21,7 @@ use crate::delegate::SolverDelegate; use crate::solve::inspect::ProbeKind; use crate::solve::{ BuiltinImplSource, CandidateSource, CanonicalResponse, Certainty, EvalCtxt, Goal, GoalSource, - MaybeCause, NoSolution, ParamEnvSource, QueryResult, + MaybeCause, NoSolution, ParamEnvSource, QueryResult, has_no_inference_or_external_constraints, }; enum AliasBoundKind { @@ -395,9 +395,28 @@ where match assemble_from { AssembleCandidatesFrom::All => { - self.assemble_impl_candidates(goal, &mut candidates); self.assemble_builtin_impl_candidates(goal, &mut candidates); - self.assemble_object_bound_candidates(goal, &mut candidates); + // For performance we only assemble impls if there are no candidates + // which would shadow them. This is necessary to avoid hangs in rayon. + // + // We always assemble builtin impls as trivial builtin impls have a higher + // priority than where-clauses. + // + // We only do this if any such candidate applies without any constraints + // as we may want to weaken inference guidance in the future and don't want + // to worry about causing major performance regressions when doing so. + if TypingMode::Coherence == self.typing_mode() + || !candidates.iter().any(|c| { + matches!( + c.source, + CandidateSource::ParamEnv(ParamEnvSource::NonGlobal) + | CandidateSource::AliasBound + ) && has_no_inference_or_external_constraints(c.result) + }) + { + self.assemble_impl_candidates(goal, &mut candidates); + self.assemble_object_bound_candidates(goal, &mut candidates); + } } AssembleCandidatesFrom::EnvAndBounds => {} } From 64ba6ba831c14d8702ba3ebbffb093855362f30a Mon Sep 17 00:00:00 2001 From: lcnr Date: Mon, 28 Jul 2025 09:36:10 +0000 Subject: [PATCH 2/2] candidate assembly structural identity fast path --- .../src/solve/assembly/mod.rs | 18 ++++++-- .../src/solve/trait_goals.rs | 45 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 1010f3bd0a120..2595ea8568808 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -112,6 +112,13 @@ where alias_ty: ty::AliasTy, ) -> Vec>; + fn assemble_param_env_candidates_fast_path( + _ecx: &mut EvalCtxt<'_, D>, + _goal: Goal, + ) -> Option> { + None + } + fn probe_and_consider_param_env_candidate( ecx: &mut EvalCtxt<'_, D>, goal: Goal, @@ -583,8 +590,13 @@ where goal: Goal, candidates: &mut Vec>, ) { - for assumption in goal.param_env.caller_bounds().iter() { - candidates.extend(G::probe_and_consider_param_env_candidate(self, goal, assumption)); + if let Some(candidate) = G::assemble_param_env_candidates_fast_path(self, goal) { + candidates.push(candidate); + } else { + for assumption in goal.param_env.caller_bounds().iter() { + candidates + .extend(G::probe_and_consider_param_env_candidate(self, goal, assumption)); + } } } @@ -1034,7 +1046,7 @@ where /// The `i32: From` bound is non-global before normalization, but is global after. /// Since the old trait solver normalized param-envs eagerly, we want to emulate this /// behavior lazily. - fn characterize_param_env_assumption( + pub(super) fn characterize_param_env_assumption( &mut self, param_env: I::ParamEnv, assumption: I::Clause, diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 650b85d99d2cd..ae86bae826402 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -1,5 +1,7 @@ //! Dealing with trait goals, i.e. `T: Trait<'a, U>`. +use std::cell::Cell; + use rustc_type_ir::data_structures::IndexSet; use rustc_type_ir::fast_reject::DeepRejectCtxt; use rustc_type_ir::inherent::*; @@ -14,7 +16,7 @@ use tracing::{debug, instrument, trace}; use crate::delegate::SolverDelegate; use crate::solve::assembly::structural_traits::{self, AsyncCallableRelevantTypes}; use crate::solve::assembly::{self, AllowInferenceConstraints, AssembleCandidatesFrom, Candidate}; -use crate::solve::inspect::ProbeKind; +use crate::solve::inspect::{self, ProbeKind}; use crate::solve::{ BuiltinImplSource, CandidateSource, Certainty, EvalCtxt, Goal, GoalSource, MaybeCause, NoSolution, ParamEnvSource, QueryResult, has_only_region_constraints, @@ -41,6 +43,47 @@ where self.def_id() } + fn assemble_param_env_candidates_fast_path( + ecx: &mut EvalCtxt<'_, D>, + goal: Goal, + ) -> Option> { + // This is kind of a mess. We need to detect whether there's an applicable + // `ParamEnv` candidate without fully normalizing other candidates to avoid the + // hang in trait-system-refactor-initiative#210. This currently uses structural + // identity, which means it does not apply if there are normalizeable aliases + // in the environment or if the goal is higher ranked. + // + // We generally need such a fast path if multiple potentially applicable where-bounds + // contain aliases whose normalization tries to apply all these where-bounds yet again. + // This can easily result in exponential blowup. + for assumption in goal.param_env.caller_bounds().iter().filter_map(|c| c.as_trait_clause()) + { + if assumption.no_bound_vars().is_some_and(|c| c == goal.predicate) { + let source = Cell::new(CandidateSource::ParamEnv(ParamEnvSource::Global)); + let Ok(candidate) = ecx + .probe(|result: &QueryResult| inspect::ProbeKind::TraitCandidate { + source: source.get(), + result: *result, + }) + .enter(|ecx| { + source.set(ecx.characterize_param_env_assumption( + goal.param_env, + assumption.upcast(ecx.cx()), + )?); + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + .map(|result| Candidate { source: source.get(), result }) + else { + continue; + }; + if candidate.source == CandidateSource::ParamEnv(ParamEnvSource::NonGlobal) { + return Some(candidate); + } + } + } + None + } + fn consider_additional_alias_assumptions( _ecx: &mut EvalCtxt<'_, D>, _goal: Goal,