From 388b5bc2cdcda1b2484577cd0d450abbfd603d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jana=20D=C3=B6nszelmann?= Date: Tue, 16 Jun 2026 15:12:47 +0200 Subject: [PATCH] strip param envs --- compiler/rustc_middle/src/ty/mod.rs | 4 + .../src/solve/eval_ctxt/mod.rs | 101 +++++++++++++++++- compiler/rustc_type_ir/src/inherent.rs | 1 + compiler/rustc_type_ir/src/visit.rs | 8 ++ 4 files changed, 113 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 3d8ab41b37a31..59042eb199990 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1009,6 +1009,10 @@ pub struct ParamEnv<'tcx> { } impl<'tcx> rustc_type_ir::inherent::ParamEnv> for ParamEnv<'tcx> { + fn empty() -> Self { + Self::empty() + } + fn caller_bounds(self) -> impl inherent::SliceLike> { self.caller_bounds() } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 6273506b8adc7..8261e8466a56d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -15,7 +15,7 @@ use rustc_type_ir::solve::{ RerunNonErased, RerunReason, RerunResultExt, SmallCopyList, }; use rustc_type_ir::{ - self as ty, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, + self as ty, CanonicalVarValues, ClauseKind, ConstKind, InferCtxtLike, Interner, MayBeErased, OpaqueTypeKey, PredicateKind, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, }; @@ -563,6 +563,102 @@ where WontMakeProgress(stalled_certainty) } + /// This is a fast path optimization: + /// If *all* the self types of all the where clauses in the goals `ParamEnv` are a + /// generic arg (this is common if for example the `ParamEnv` only contains `T: Clone` + /// for some generic function `fn foo(t: T)`) + /// And the goal does not mention any generic args, then we already know for certain that + /// the evaluation of the goal doesn't depend on the `ParamEnv` in any way. That means that + /// it's equivalent to evaluating the goal with an *empty* `ParamEnv`. + /// + /// This is desirable because the `ParamEnv` is part of the cache key, so more cache keys will + /// match if they all mention the same empty `ParamEnv`. + fn try_strip_param_env(&self, goal: Goal) -> Goal { + let clause_relevant_for_goal_evaluation = |clause: ClauseKind| -> bool { + match clause { + ClauseKind::Trait(trait_predicate) => { + let irrelevant = + // If the self type of this clause is a generic parameter + // i.e. T: Clone as opposed to i32: Clone or Vec: Clone + matches!(trait_predicate.self_ty().kind(), ty::Param(_)) + // And the goal can never in any way use this clause because it + // doesn't mention any type params + && !goal.predicate.has_param() + // then the clause is irrelevant to the outcome of the goal. + ; + + !irrelevant + } + ClauseKind::RegionOutlives(_) => true, + // FIXME: atm never relevant for goal evaluation, but might be in the future so `true` + // to avoid future performance cliffs + ClauseKind::TypeOutlives(_) => true, + ClauseKind::Projection(projection_predicate) => { + let irrelevant = + // If the self type of this projection clause is a generic parameter + // i.e. ::Foo as opposed to ::Foo or as Bar>::Foo + matches!(projection_predicate.self_ty().kind(), ty::Param(_)) + // And the goal can never in any way use this clause because it + // doesn't mention any type params + && !goal.predicate.has_param() + // then the clause is irrelevant to the outcome of the goal. + ; + + !irrelevant + } + ClauseKind::ConstArgHasType(c, _) => { + let irrelevant = + // If the const this clause bounds, is a generic parameter, + // i.e. N: usize as opposed to 4: usize + matches!(c.kind(), ConstKind::Param(_)) + // and the goal can never in any way use this clause because it + // doesn't mention any const params + && !goal.predicate.has_const_param() + // then the clause is irrelevant to the outcome of the goal. + ; + + !irrelevant + } + ClauseKind::WellFormed(_) => true, + ClauseKind::ConstEvaluatable(c) => { + let irrelevant = + // If the const this clause bounds, is a generic parameter, + // i.e. N is evaluatable as opposed to 3 is evaluatable + matches!(c.kind(), ConstKind::Param(_)) + // and the goal can never in any way use this clause because it + // doesn't mention any const params + && !goal.predicate.has_const_param() + // then the clause is irrelevant to the outcome of the goal. + ; + + !irrelevant + } + ClauseKind::HostEffect(_) => true, + ClauseKind::UnstableFeature(_) => true, + } + }; + + let any_clause_relevant_for_goal = goal + .param_env + .caller_bounds() + .iter() + .any(|i| clause_relevant_for_goal_evaluation(i.kind().skip_binder())); + + // If no clause is relevant to the outcome of the goal, we can strip the `ParamEnv`. + if !any_clause_relevant_for_goal { + if !goal.param_env.caller_bounds().is_empty() { + tracing::debug!( + "stripping param env {:?} because it is irrelevant to prove {:?}", + goal.param_env, + goal.predicate + ); + } + Goal { param_env: ParamEnv::empty(), predicate: goal.predicate } + } else { + goal + } + } + /// Recursively evaluates `goal`, returning the nested goals in case /// the nested goal is a `NormalizesTo` goal. /// @@ -590,6 +686,7 @@ where )); } + let opaques = self.delegate.clone_opaque_types_lookup_table(); self.evaluate_goal_cold(source, goal) } @@ -618,6 +715,8 @@ where .entered(); let (result, orig_values, canonical_goal, succeeded_in_erased) = 'retry_canonicalize: { + let goal = self.try_strip_param_env(goal); + let skip_erased_attempt = if typing_mode.is_coherence() { true } else { diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index bffb6aa8060ed..86a24bcd14280 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -629,6 +629,7 @@ pub trait AdtDef: Copy + Debug + Hash + Eq { #[rust_analyzer::prefer_underscore_import] pub trait ParamEnv: Copy + Debug + Hash + Eq + TypeFoldable { + fn empty() -> Self; fn caller_bounds(self) -> impl SliceLike; } diff --git a/compiler/rustc_type_ir/src/visit.rs b/compiler/rustc_type_ir/src/visit.rs index 492c37481298b..f0e6013a38aa0 100644 --- a/compiler/rustc_type_ir/src/visit.rs +++ b/compiler/rustc_type_ir/src/visit.rs @@ -317,6 +317,14 @@ pub trait TypeVisitableExt: TypeVisitable { self.has_type_flags(TypeFlags::HAS_PARAM) } + fn has_type_param(&self) -> bool { + self.has_type_flags(TypeFlags::HAS_TY_PARAM) + } + + fn has_const_param(&self) -> bool { + self.has_type_flags(TypeFlags::HAS_CT_PARAM) + } + /// "Free" regions in this context means that it has any region /// that is not (a) erased or (b) late-bound. fn has_free_regions(&self) -> bool {