From e3dfdc6c4f601b79904dbb0562f94be6f89959ef Mon Sep 17 00:00:00 2001 From: Makro Date: Fri, 31 Jul 2026 06:24:04 +0000 Subject: [PATCH 1/2] perf: Avoid intermediate allocations in nominal_obligations --- .../rustc_trait_selection/src/traits/wf.rs | 80 +++++++++++++------ 1 file changed, 54 insertions(+), 26 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 6231f781db2c5..5ecdfbe6e3ca6 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -3,8 +3,6 @@ //! well formed is performed elsewhere (e.g. during type checking or item well formedness //! checking). -use std::iter; - use rustc_hir as hir; use rustc_hir::lang_items::LangItem; use rustc_infer::traits::{ObligationCauseCode, PredicateObligations}; @@ -579,32 +577,62 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { return Default::default(); } - let gen_clauses = self.tcx().clauses_of(def_id); - let mut origins = vec![def_id; gen_clauses.clauses.len()]; - let mut head = gen_clauses; - while let Some(parent) = head.parent { - head = self.tcx().clauses_of(parent); - origins.extend(iter::repeat(parent).take(head.clauses.len())); - } + let tcx = self.tcx(); + let clauses = tcx.clauses_of(def_id); + let mut obligations = match clauses.parent { + Some(parent) => { + self.nominal_obligations_for_parents(tcx, parent, args, clauses.clauses.len()) + } + None => PredicateObligations::with_capacity(clauses.clauses.len()), + }; + // Most items have no parent; keeping that case out of the recursion lets this call inline. + self.push_own_obligations(tcx, def_id, args, clauses, &mut obligations); + trace!(?obligations); + obligations + } - let gen_clauses = gen_clauses.instantiate(self.tcx(), args); - trace!("{:#?}", gen_clauses); - debug_assert_eq!(gen_clauses.clauses.len(), origins.len()); + /// Emits the obligations for `def_id` and every ancestor in its `clauses_of` parent + /// chain, outermost first because diagnostics rely on that order. + fn nominal_obligations_for_parents( + &mut self, + tcx: TyCtxt<'tcx>, + def_id: DefId, + args: GenericArgsRef<'tcx>, + descendant_count: usize, + ) -> PredicateObligations<'tcx> { + let clauses = tcx.clauses_of(def_id); + let count = descendant_count + clauses.clauses.len(); + let mut obligations = match clauses.parent { + Some(parent) => self.nominal_obligations_for_parents(tcx, parent, args, count), + None => PredicateObligations::with_capacity(count), + }; + self.push_own_obligations(tcx, def_id, args, clauses, &mut obligations); + obligations + } - iter::zip(gen_clauses, origins.into_iter().rev()) - .map(|((clause, span), origin_def_id)| { - let code = ObligationCauseCode::WhereClause(origin_def_id, span); - let cause = self.cause(code); - traits::Obligation::with_depth( - self.tcx(), - cause, - self.recursion_depth, - self.param_env, - clause.skip_norm_wip(), - ) - }) - .filter(|clause| !clause.has_escaping_bound_vars()) - .collect() + #[inline(always)] + fn push_own_obligations( + &self, + tcx: TyCtxt<'tcx>, + def_id: DefId, + args: GenericArgsRef<'tcx>, + clauses: ty::GenericClauses<'tcx>, + obligations: &mut PredicateObligations<'tcx>, + ) { + for (clause, span) in clauses.instantiate_own(tcx, args) { + let code = ObligationCauseCode::WhereClause(def_id, span); + let cause = self.cause(code); + let obligation = traits::Obligation::with_depth( + tcx, + cause, + self.recursion_depth, + self.param_env, + clause.skip_norm_wip(), + ); + if !obligation.has_escaping_bound_vars() { + obligations.push(obligation); + } + } } fn add_wf_preds_for_dyn_ty( From 0c666d6f932b73d8259d3dc0758e381baabf0516 Mon Sep 17 00:00:00 2001 From: Makro Date: Thu, 20 Aug 2026 03:25:55 +0000 Subject: [PATCH 2/2] Push nominal obligations via callback instead of returning them --- .../rustc_trait_selection/src/traits/wf.rs | 138 +++++++++--------- 1 file changed, 68 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 5ecdfbe6e3ca6..b0935dab03b89 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -5,7 +5,7 @@ use rustc_hir as hir; use rustc_hir::lang_items::LangItem; -use rustc_infer::traits::{ObligationCauseCode, PredicateObligations}; +use rustc_infer::traits::{ObligationCauseCode, PredicateObligation, PredicateObligations}; use rustc_middle::bug; use rustc_middle::ty::{ self, GenericArgsRef, Term, TermKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, @@ -14,7 +14,7 @@ use rustc_middle::ty::{ use rustc_session::diagnostics::feature_err; use rustc_span::def_id::{DefId, LocalDefId}; use rustc_span::{Span, sym}; -use tracing::{debug, instrument, trace}; +use tracing::{debug, instrument}; use crate::infer::InferCtxt; use crate::traits; @@ -385,10 +385,6 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { return; } - // if the trait predicate is not const, the wf obligations should not be const as well. - let obligations = self.nominal_obligations(trait_ref.def_id, trait_ref.args); - - debug!("compute_trait_pred obligations {:?}", obligations); let param_env = self.param_env; let depth = self.recursion_depth; @@ -405,12 +401,20 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { traits::Obligation::with_depth(tcx, cause, depth, param_env, predicate) }; + // if the trait predicate is not const, the wf obligations should not be const as well. if let Elaborate::All = elaborate { + let mut obligations = PredicateObligations::new(); + self.nominal_obligations(trait_ref.def_id, trait_ref.args, |_, obligation| { + obligations.push(obligation) + }); + debug!("compute_trait_pred obligations {:?}", obligations); let implied_obligations = traits::util::elaborate(tcx, obligations); let implied_obligations = implied_obligations.map(extend); self.out.extend(implied_obligations); } else { - self.out.extend(obligations); + self.nominal_obligations(trait_ref.def_id, trait_ref.args, |this, obligation| { + this.out.push(obligation) + }); } self.out.extend( @@ -474,8 +478,9 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { // `i32: Clone` // `i32: Copy` // ] - let obligations = self.nominal_obligations(data.expect_projection_def_id(), data.args); - self.out.extend(obligations); + self.nominal_obligations(data.expect_projection_def_id(), data.args, |this, obligation| { + this.out.push(obligation) + }); self.add_wf_preds_for_projection_args(data.args); } @@ -504,8 +509,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { &mut self.out, ); let def_id = data.expect_inherent_def_id(); - let obligations = self.nominal_obligations(def_id, args); - self.out.extend(obligations); + self.nominal_obligations(def_id, args, |this, obligation| this.out.push(obligation)); } data.args.visit_with(self); @@ -558,37 +562,33 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { debug!(?self.out); } - #[instrument(level = "debug", skip(self))] + #[instrument(level = "debug", skip(self, push_obligation))] fn nominal_obligations( &mut self, def_id: DefId, args: GenericArgsRef<'tcx>, - ) -> PredicateObligations<'tcx> { + mut push_obligation: impl FnMut(&mut Self, PredicateObligation<'tcx>), + ) { // PERF: `Sized`'s predicates include `MetaSized`, but both are compiler implemented marker // traits, so `MetaSized` will always be WF if `Sized` is WF and vice-versa. Determining // the nominal obligations of `Sized` would in-effect just elaborate `MetaSized` and make // the compiler do a bunch of work needlessly. if self.tcx().is_lang_item(def_id, LangItem::Sized) { - return Default::default(); + return; } if self.tcx().is_lang_item(def_id, LangItem::ConstParamTy) && self.tcx().features().const_param_ty_unchecked() { - return Default::default(); + return; } let tcx = self.tcx(); let clauses = tcx.clauses_of(def_id); - let mut obligations = match clauses.parent { - Some(parent) => { - self.nominal_obligations_for_parents(tcx, parent, args, clauses.clauses.len()) - } - None => PredicateObligations::with_capacity(clauses.clauses.len()), - }; + if let Some(parent) = clauses.parent { + self.nominal_obligations_for_parents(tcx, parent, args, &mut push_obligation); + } // Most items have no parent; keeping that case out of the recursion lets this call inline. - self.push_own_obligations(tcx, def_id, args, clauses, &mut obligations); - trace!(?obligations); - obligations + self.push_own_obligations(tcx, def_id, args, clauses, &mut push_obligation); } /// Emits the obligations for `def_id` and every ancestor in its `clauses_of` parent @@ -598,26 +598,23 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { tcx: TyCtxt<'tcx>, def_id: DefId, args: GenericArgsRef<'tcx>, - descendant_count: usize, - ) -> PredicateObligations<'tcx> { + push_obligation: &mut impl FnMut(&mut Self, PredicateObligation<'tcx>), + ) { let clauses = tcx.clauses_of(def_id); - let count = descendant_count + clauses.clauses.len(); - let mut obligations = match clauses.parent { - Some(parent) => self.nominal_obligations_for_parents(tcx, parent, args, count), - None => PredicateObligations::with_capacity(count), - }; - self.push_own_obligations(tcx, def_id, args, clauses, &mut obligations); - obligations + if let Some(parent) = clauses.parent { + self.nominal_obligations_for_parents(tcx, parent, args, push_obligation); + } + self.push_own_obligations(tcx, def_id, args, clauses, push_obligation); } #[inline(always)] fn push_own_obligations( - &self, + &mut self, tcx: TyCtxt<'tcx>, def_id: DefId, args: GenericArgsRef<'tcx>, clauses: ty::GenericClauses<'tcx>, - obligations: &mut PredicateObligations<'tcx>, + push_obligation: &mut impl FnMut(&mut Self, PredicateObligation<'tcx>), ) { for (clause, span) in clauses.instantiate_own(tcx, args) { let code = ObligationCauseCode::WhereClause(def_id, span); @@ -630,7 +627,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> { clause.skip_norm_wip(), ); if !obligation.has_escaping_bound_vars() { - obligations.push(obligation); + push_obligation(self, obligation); } } } @@ -837,8 +834,9 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { .. }, ) => { - let obligations = self.nominal_obligations(def_id, args); - self.out.extend(obligations); + self.nominal_obligations(def_id, args, |this, obligation| { + this.out.push(obligation) + }); } ty::Alias(_, data @ ty::AliasTy { kind: ty::Inherent { .. }, .. }) => { self.add_wf_preds_for_inherent_projection(data.into()); @@ -847,8 +845,9 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { ty::Adt(def, args) => { // WfNominalType - let obligations = self.nominal_obligations(def.did(), args); - self.out.extend(obligations); + self.nominal_obligations(def.did(), args, |this, obligation| { + this.out.push(obligation) + }); } ty::FnDef(did, args) => { @@ -861,8 +860,7 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { let fn_sig = tcx.fn_sig(did).instantiate(tcx, args).skip_norm_wip(); fn_sig.output().skip_binder().visit_with(self); - let obligations = self.nominal_obligations(did, args); - self.out.extend(obligations); + self.nominal_obligations(did, args, |this, obligation| this.out.push(obligation)); } ty::Ref(r, rty, _) => { @@ -889,8 +887,7 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { // about the signature of the closure. We don't // have the problem of implied bounds here since // coroutines don't take arguments. - let obligations = self.nominal_obligations(did, args); - self.out.extend(obligations); + self.nominal_obligations(did, args, |this, obligation| this.out.push(obligation)); } ty::Closure(did, args) => { @@ -909,8 +906,7 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { // can cause compiler crashes when the user abuses unsafe // code to procure such a closure. // See tests/ui/type-alias-impl-trait/wf_check_closures.rs - let obligations = self.nominal_obligations(did, args); - self.out.extend(obligations); + self.nominal_obligations(did, args, |this, obligation| this.out.push(obligation)); // Only check the upvar types for WF, not the rest // of the types within. This is needed because we // capture the signature and it may not be WF @@ -937,8 +933,7 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { ty::CoroutineClosure(did, args) => { // See the above comments. The same apply to coroutine-closures. - let obligations = self.nominal_obligations(did, args); - self.out.extend(obligations); + self.nominal_obligations(did, args, |this, obligation| this.out.push(obligation)); let upvars = args.as_coroutine_closure().tupled_upvars_ty(); return upvars.visit_with(self); } @@ -1007,27 +1002,27 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { // // See also: https://rustc-dev-guide.rust-lang.org/const-generics.html let args = principal.skip_binder().with_self_ty(self.tcx(), t).args; - let obligations = - self.nominal_obligations(principal_def_id, args).into_iter().filter(|o| { - let kind = o.predicate.kind().skip_binder(); - match kind { - ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType( - ct, - _, - )) if matches!(ct.kind(), ty::ConstKind::Param(..)) => { - // ConstArgHasType clauses are not higher kinded. Assert as - // such so we can fix this up if that ever changes. - assert!(o.predicate.kind().bound_vars().is_empty()); - // In stable rust, variables from the trait object binder - // cannot be referenced by a ConstArgHasType clause. However, - // under `generic_const_parameter_types`, it can. Ignore those - // predicates for now, to not have HKT-ConstArgHasTypes. - !kind.has_escaping_bound_vars() - } - _ => false, + self.nominal_obligations(principal_def_id, args, |this, obligation| { + let kind = obligation.predicate.kind().skip_binder(); + let keep = match kind { + ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, _)) + if matches!(ct.kind(), ty::ConstKind::Param(..)) => + { + // ConstArgHasType clauses are not higher kinded. Assert as + // such so we can fix this up if that ever changes. + assert!(obligation.predicate.kind().bound_vars().is_empty()); + // In stable rust, variables from the trait object binder + // cannot be referenced by a ConstArgHasType clause. However, + // under `generic_const_parameter_types`, it can. Ignore those + // predicates for now, to not have HKT-ConstArgHasTypes. + !kind.has_escaping_bound_vars() } - }); - self.out.extend(obligations); + _ => false, + }; + if keep { + this.out.push(obligation); + } + }); } if !t.has_escaping_bound_vars() { @@ -1119,8 +1114,11 @@ impl<'a, 'tcx> TypeVisitor> for WfPredicates<'a, 'tcx> { ty::AliasConstKind::Projection { def_id } | ty::AliasConstKind::Free { def_id } | ty::AliasConstKind::Anon { def_id } => { - let obligations = self.nominal_obligations(def_id, alias_const.args); - self.out.extend(obligations); + self.nominal_obligations( + def_id, + alias_const.args, + |this, obligation| this.out.push(obligation), + ); } } }