| author | bors <bors@rust-lang.org> 2026-07-06 08:29:46 UTC |
| committer | bors <bors@rust-lang.org> 2026-07-06 08:29:46 UTC |
| log | 3c00c96d3af4d5b5e101e56cc161a608b21366ee |
| tree | 29cf8928cd822f00211d3acce0ad34634a0f86f7 |
| parent | 3659db0d3e2cd634c766fcda79ed118eca31a9fd |
| parent | ed57bc8b67440e0f42ec15ac3046e877fe516a2c |
Use `LowerAvailableDepth::No` for normalizes-to goal itself instead of its nested goals
Might fix perf regressions in https://github.com/rust-lang/rust/pull/157718#issuecomment-48536016644 files changed, 31 insertions(+), 37 deletions(-)
compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs+4-4| ... | ... | @@ -8,7 +8,7 @@ use rustc_type_ir::inherent::*; |
| 8 | 8 | use rustc_type_ir::region_constraint::RegionConstraint; |
| 9 | 9 | use rustc_type_ir::relate::Relate; |
| 10 | 10 | use rustc_type_ir::relate::solver_relating::RelateExt; |
| 11 | use rustc_type_ir::search_graph::{CandidateHeadUsages, IncreaseDepthForNested, PathKind}; | |
| 11 | use rustc_type_ir::search_graph::{CandidateHeadUsages, LowerAvailableDepth, PathKind}; | |
| 12 | 12 | use rustc_type_ir::solve::{ |
| 13 | 13 | AccessedOpaques, ExternalRegionConstraints, FetchEligibleAssocItemResponse, MaybeInfo, |
| 14 | 14 | NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunCondition, |
| ... | ... | @@ -484,7 +484,7 @@ where |
| 484 | 484 | stalled_on: Option<GoalStalledOn<I>>, |
| 485 | 485 | ) -> Result<GoalEvaluation<I>, NoSolutionOrRerunNonErased> { |
| 486 | 486 | let (normalization_nested_goals, goal_evaluation) = |
| 487 | self.evaluate_goal_raw(source, goal, stalled_on, IncreaseDepthForNested::Yes)?; | |
| 487 | self.evaluate_goal_raw(source, goal, stalled_on, LowerAvailableDepth::Yes)?; | |
| 488 | 488 | assert!(normalization_nested_goals.is_empty()); |
| 489 | 489 | Ok(goal_evaluation) |
| 490 | 490 | } |
| ... | ... | @@ -576,7 +576,7 @@ where |
| 576 | 576 | source: GoalSource, |
| 577 | 577 | goal: Goal<I, I::Predicate>, |
| 578 | 578 | stalled_on: Option<GoalStalledOn<I>>, |
| 579 | increase_depth_for_nested: IncreaseDepthForNested, | |
| 579 | increase_depth_for_nested: LowerAvailableDepth, | |
| 580 | 580 | ) -> Result<(NestedNormalizationGoals<I>, GoalEvaluation<I>), NoSolutionOrRerunNonErased> { |
| 581 | 581 | if let RerunStalled::WontMakeProgress(stalled_certainty) = |
| 582 | 582 | self.rerunning_stalled_goal_may_make_progress(stalled_on.as_ref()) |
| ... | ... | @@ -601,7 +601,7 @@ where |
| 601 | 601 | &mut self, |
| 602 | 602 | source: GoalSource, |
| 603 | 603 | goal: Goal<I, I::Predicate>, |
| 604 | increase_depth_for_nested: IncreaseDepthForNested, | |
| 604 | increase_depth_for_nested: LowerAvailableDepth, | |
| 605 | 605 | ) -> Result<(NestedNormalizationGoals<I>, GoalEvaluation<I>), NoSolutionOrRerunNonErased> { |
| 606 | 606 | // We only care about one entry per `OpaqueTypeKey` here, |
| 607 | 607 | // so we only canonicalize the lookup table and ignore |
compiler/rustc_next_trait_solver/src/solve/project_goals/mod.rs+7-9| ... | ... | @@ -3,7 +3,7 @@ mod free_alias; |
| 3 | 3 | mod inherent; |
| 4 | 4 | mod opaque_types; |
| 5 | 5 | |
| 6 | use rustc_type_ir::search_graph::IncreaseDepthForNested; | |
| 6 | use rustc_type_ir::search_graph::LowerAvailableDepth; | |
| 7 | 7 | use rustc_type_ir::solve::QueryResultOrRerunNonErased; |
| 8 | 8 | use rustc_type_ir::{self as ty, Interner, ProjectionPredicate}; |
| 9 | 9 | use tracing::{instrument, trace}; |
| ... | ... | @@ -72,14 +72,12 @@ where |
| 72 | 72 | GoalSource::TypeRelating, |
| 73 | 73 | normalizes_to, |
| 74 | 74 | None, |
| 75 | // We don't increase depth for nested goals for this `NormalizesTo` goal, as | |
| 76 | // evaluating `NormalizesTo` is an extra step only exists in the new solver | |
| 77 | // that behaves like a function call rather than an independent nested goal | |
| 78 | // evaluation, so increasing the depth may end up regressions which hit the | |
| 79 | // recursion limits for crates compiled well with the old solver. Furthermore, | |
| 80 | // those nested goals from `NormalizesTo` will be evaluated again as the | |
| 81 | // caller's nested goals with increased depths anyway. | |
| 82 | IncreaseDepthForNested::No, | |
| 75 | // We don't lower thr available depth for this `NormalizesTo` goal, as evaluating | |
| 76 | // it is an extra step only exists in the new solver that behaves like a function | |
| 77 | // call rather than an independent nested goal evaluation. So, decreasing the | |
| 78 | // available depth may end up regressions which hit the recursion limits for crates | |
| 79 | // compiled well with the old solver. | |
| 80 | LowerAvailableDepth::No, | |
| 83 | 81 | )?; |
| 84 | 82 | |
| 85 | 83 | trace!(?nested_goals); |
compiler/rustc_type_ir/src/search_graph/mod.rs+19-11| ... | ... | @@ -262,8 +262,16 @@ impl CandidateHeadUsages { |
| 262 | 262 | } |
| 263 | 263 | } |
| 264 | 264 | |
| 265 | /// Whether evaluating a given goal should be done with a lower available depth from | |
| 266 | /// its parent goal. | |
| 267 | /// | |
| 268 | /// Normally, it should be `Yes`, but among rustc's predicate goals, `normalizes-to` | |
| 269 | /// goals are exceptions. They act like functions that used for normalizing associated | |
| 270 | /// terms while evaluating projection goals with fully unconstrained expected term. | |
| 271 | /// We don't want to lower the available depths for those function-like goals, otherwise | |
| 272 | /// we will encounter recursion limit overflows more often. | |
| 265 | 273 | #[derive(Debug, Clone, Copy)] |
| 266 | pub enum IncreaseDepthForNested { | |
| 274 | pub enum LowerAvailableDepth { | |
| 267 | 275 | Yes, |
| 268 | 276 | No, |
| 269 | 277 | } |
| ... | ... | @@ -280,11 +288,12 @@ impl AvailableDepth { |
| 280 | 288 | fn allowed_depth_for_nested<D: Delegate>( |
| 281 | 289 | root_depth: AvailableDepth, |
| 282 | 290 | stack: &Stack<D::Cx>, |
| 291 | lower_available_depth: LowerAvailableDepth, | |
| 283 | 292 | ) -> Option<AvailableDepth> { |
| 284 | 293 | if let Some(last) = stack.last() { |
| 285 | match last.increase_depth_for_nested { | |
| 286 | IncreaseDepthForNested::Yes => {} | |
| 287 | IncreaseDepthForNested::No => { | |
| 294 | match lower_available_depth { | |
| 295 | LowerAvailableDepth::Yes => {} | |
| 296 | LowerAvailableDepth::No => { | |
| 288 | 297 | return Some(last.available_depth); |
| 289 | 298 | } |
| 290 | 299 | } |
| ... | ... | @@ -754,7 +763,6 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> { |
| 754 | 763 | available_depth, |
| 755 | 764 | min_reached_available_depth: available_depth, |
| 756 | 765 | provisional_result: None, |
| 757 | increase_depth_for_nested: IncreaseDepthForNested::Yes, | |
| 758 | 766 | heads: Default::default(), |
| 759 | 767 | encountered_overflow: false, |
| 760 | 768 | usages: None, |
| ... | ... | @@ -775,12 +783,14 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> { |
| 775 | 783 | cx: X, |
| 776 | 784 | input: X::Input, |
| 777 | 785 | step_kind_from_parent: PathKind, |
| 778 | increase_depth_for_nested: IncreaseDepthForNested, | |
| 786 | lower_available_depth: LowerAvailableDepth, | |
| 779 | 787 | inspect: &mut D::ProofTreeBuilder, |
| 780 | 788 | ) -> X::Result { |
| 781 | let Some(available_depth) = | |
| 782 | AvailableDepth::allowed_depth_for_nested::<D>(self.root_depth, &self.stack) | |
| 783 | else { | |
| 789 | let Some(available_depth) = AvailableDepth::allowed_depth_for_nested::<D>( | |
| 790 | self.root_depth, | |
| 791 | &self.stack, | |
| 792 | lower_available_depth, | |
| 793 | ) else { | |
| 784 | 794 | return self.handle_overflow(cx, input); |
| 785 | 795 | }; |
| 786 | 796 | |
| ... | ... | @@ -832,7 +842,6 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D> { |
| 832 | 842 | available_depth, |
| 833 | 843 | provisional_result: None, |
| 834 | 844 | min_reached_available_depth: available_depth, |
| 835 | increase_depth_for_nested, | |
| 836 | 845 | heads: Default::default(), |
| 837 | 846 | encountered_overflow: false, |
| 838 | 847 | usages: None, |
| ... | ... | @@ -1430,7 +1439,6 @@ impl<D: Delegate<Cx = X>, X: Cx> SearchGraph<D, X> { |
| 1430 | 1439 | // We can keep these goals from previous iterations as they are only |
| 1431 | 1440 | // ever read after finalizing this evaluation. |
| 1432 | 1441 | min_reached_available_depth: stack_entry.min_reached_available_depth, |
| 1433 | increase_depth_for_nested: stack_entry.increase_depth_for_nested, | |
| 1434 | 1442 | heads: stack_entry.heads, |
| 1435 | 1443 | nested_goals: stack_entry.nested_goals, |
| 1436 | 1444 | // We reset these two fields when rerunning this goal. We could |
compiler/rustc_type_ir/src/search_graph/stack.rs+1-13| ... | ... | @@ -4,8 +4,7 @@ use derive_where::derive_where; |
| 4 | 4 | use rustc_index::IndexVec; |
| 5 | 5 | |
| 6 | 6 | use crate::search_graph::{ |
| 7 | AvailableDepth, CandidateHeadUsages, Cx, CycleHeads, HeadUsages, IncreaseDepthForNested, | |
| 8 | NestedGoals, PathKind, | |
| 7 | AvailableDepth, CandidateHeadUsages, Cx, CycleHeads, HeadUsages, NestedGoals, PathKind, | |
| 9 | 8 | }; |
| 10 | 9 | |
| 11 | 10 | rustc_index::newtype_index! { |
| ... | ... | @@ -33,17 +32,6 @@ pub(super) struct StackEntry<X: Cx> { |
| 33 | 32 | /// If there's no nested goal, this is equal to the `available_depth`. |
| 34 | 33 | pub min_reached_available_depth: AvailableDepth, |
| 35 | 34 | |
| 36 | /// Whether evaluating nested goals of a given goal should increase the depth. | |
| 37 | /// | |
| 38 | /// Normally, it should be `Yes`, but among rustc's predicate goals, `normalizes-to` | |
| 39 | /// goals are exceptions. They act like functions that used for normalizing associated | |
| 40 | /// terms while evaluating projection goals and since their expected terms are always fully | |
| 41 | /// unconstrained intentionally, they often return ambiguous nested goals to the caller's | |
| 42 | /// context. As these nested goals are evaluated again in the caller's context, we don't | |
| 43 | /// want to increase depths when they are evaluated as nested goals for `normalizes-to` | |
| 44 | /// goals, otherwise we will encounter recursion limit overflows more often. | |
| 45 | pub increase_depth_for_nested: IncreaseDepthForNested, | |
| 46 | ||
| 47 | 35 | /// Starts out as `None` and gets set when rerunning this |
| 48 | 36 | /// goal in case we encounter a cycle. |
| 49 | 37 | pub provisional_result: Option<X::Result>, |