From ad2a4f88fe571b6ba65698d7bd7442eda0e0f18f Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Thu, 27 Aug 2026 19:14:00 +0330 Subject: [PATCH 1/7] avoid redundant next-solver fulfillment scans --- compiler/rustc_infer/src/infer/context.rs | 17 ++-- compiler/rustc_infer/src/infer/mod.rs | 53 +++++++++- .../src/infer/relate/generalize.rs | 7 +- .../src/infer/snapshot/undo_log.rs | 28 +++++- .../rustc_infer/src/infer/type_variable.rs | 9 ++ .../src/solve/fulfill.rs | 99 ++++++++++++++++++- 6 files changed, 193 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index 38dab6ec4875d..ac556d285fa12 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -209,15 +209,15 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { } fn equate_int_vids_raw(&self, a: ty::IntVid, b: ty::IntVid) { - self.inner.borrow_mut().int_unification_table().union(a, b); + self.inner.borrow_mut().equate_int_vids(a, b); } fn equate_float_vids_raw(&self, a: ty::FloatVid, b: ty::FloatVid) { - self.inner.borrow_mut().float_unification_table().union(a, b); + self.inner.borrow_mut().equate_float_vids(a, b); } fn equate_const_vids_raw(&self, a: ty::ConstVid, b: ty::ConstVid) { - self.inner.borrow_mut().const_unification_table().union(a, b); + self.inner.borrow_mut().equate_const_vids(a, b); } fn instantiate_ty_var_raw(&self, vid: ty::TyVid, ty: Ty<'tcx>) { @@ -229,10 +229,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn instantiate_const_var_raw(&self, vid: ty::ConstVid, ct: ty::Const<'tcx>) { let ct = lower_universe(self, self.try_resolve_const_var(vid).unwrap_err(), ct); - self.inner - .borrow_mut() - .const_unification_table() - .union_value(vid, ConstVariableValue::Known { value: ct }); + self.inner.borrow_mut().instantiate_const_var(vid, ct); } fn instantiate_ty_var>( @@ -253,11 +250,11 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { } fn instantiate_int_var_raw(&self, vid: ty::IntVid, value: ty::IntVarValue) { - self.inner.borrow_mut().int_unification_table().union_value(vid, value); + self.inner.borrow_mut().instantiate_int_var(vid, value); } fn instantiate_float_var_raw(&self, vid: ty::FloatVid, value: ty::FloatVarValue) { - self.inner.borrow_mut().float_unification_table().union_value(vid, value); + self.inner.borrow_mut().instantiate_float_var(vid, value); } fn instantiate_const_var>( @@ -507,7 +504,7 @@ impl<'a, 'tcx> ty::TypeFolder> for LowerUniverseFolder<'a, 'tcx> { }) .vid; - self.infcx.inner.borrow_mut().const_unification_table().union(vid, new_var_id); + self.infcx.inner.borrow_mut().equate_const_vids(vid, new_var_id); ty::Const::new_var(self.cx(), new_var_id) } diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index d3ed447e5201f..e4966214b9d9f 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -167,9 +167,9 @@ pub struct InferCtxtInner<'tcx> { } impl<'tcx> InferCtxtInner<'tcx> { - fn new() -> InferCtxtInner<'tcx> { + fn new(next_trait_solver: bool) -> InferCtxtInner<'tcx> { InferCtxtInner { - undo_log: InferCtxtUndoLogs::default(), + undo_log: InferCtxtUndoLogs::new(next_trait_solver), projection_cache: Default::default(), type_variable_storage: Default::default(), @@ -232,6 +232,44 @@ impl<'tcx> InferCtxtInner<'tcx> { self.const_unification_storage.with_log(&mut self.undo_log) } + // Keep mutations to non-type inference variables synchronized with the + // generation used by the stalled-goal fulfillment fast path. + #[inline] + fn equate_int_vids(&mut self, a: ty::IntVid, b: ty::IntVid) { + self.undo_log.bump_stalled_goal_generation(); + self.int_unification_table().union(a, b); + } + + #[inline] + fn equate_float_vids(&mut self, a: ty::FloatVid, b: ty::FloatVid) { + self.undo_log.bump_stalled_goal_generation(); + self.float_unification_table().union(a, b); + } + + #[inline] + fn equate_const_vids(&mut self, a: ty::ConstVid, b: ty::ConstVid) { + self.undo_log.bump_stalled_goal_generation(); + self.const_unification_table().union(a, b); + } + + #[inline] + fn instantiate_int_var(&mut self, vid: ty::IntVid, value: ty::IntVarValue) { + self.undo_log.bump_stalled_goal_generation(); + self.int_unification_table().union_value(vid, value); + } + + #[inline] + fn instantiate_float_var(&mut self, vid: ty::FloatVid, value: ty::FloatVarValue) { + self.undo_log.bump_stalled_goal_generation(); + self.float_unification_table().union_value(vid, value); + } + + #[inline] + fn instantiate_const_var(&mut self, vid: ty::ConstVid, value: ty::Const<'tcx>) { + self.undo_log.bump_stalled_goal_generation(); + self.const_unification_table().union_value(vid, ConstVariableValue::Known { value }); + } + #[inline] pub fn unwrap_region_constraints(&mut self) -> RegionConstraintCollector<'_, 'tcx> { self.region_constraint_storage @@ -683,7 +721,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> { considering_regions, in_hir_typeck, skip_leak_check, - inner: RefCell::new(InferCtxtInner::new()), + inner: RefCell::new(InferCtxtInner::new(next_trait_solver)), lexical_region_resolutions: RefCell::new(None), selection_cache: Default::default(), evaluation_cache: Default::default(), @@ -1714,6 +1752,15 @@ impl<'tcx> InferCtxt<'tcx> { self.typing_env(param_env).as_query_input(value) } + #[inline] + pub fn stalled_goal_generation(&self) -> u64 { + self.inner + .borrow() + .undo_log + .stalled_goal_generation() + .expect("stalled-goal generation requires the next trait solver") + } + /// The returned function is used in a fast path. If it returns `true` the variable is /// unchanged, `false` indicates that the status is unknown. #[inline] diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index a81ec3d527a6e..92f41aa9492ca 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -258,17 +258,14 @@ impl<'tcx> InferCtxt<'tcx> { } (TermVid::Const(l), ty::TermKind::Const(r)) => { if let Some(r) = r.ct_vid() { - self.inner.borrow_mut().const_unification_table().union(l, r) + self.inner.borrow_mut().equate_const_vids(l, r) } else { debug_assert!( self.try_resolve_const_var(l) .unwrap_err() .can_name(ty::max_universe(self, r)) ); - self.inner - .borrow_mut() - .const_unification_table() - .union_value(l, ConstVariableValue::Known { value: r }) + self.inner.borrow_mut().instantiate_const_var(l, r) } } _ => bug!("mismatched term kinds in generalize: {l:?}, {r:?}"), diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index a10026f2f77c7..0981758324691 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -12,6 +12,7 @@ use crate::traits; pub struct Snapshot<'tcx> { pub(crate) undo_len: usize, + stalled_goal_generation: Option, _marker: PhantomData<&'tcx ()>, } @@ -99,6 +100,7 @@ impl<'tcx> Rollback> for InferCtxtInner<'tcx> { pub(crate) struct InferCtxtUndoLogs<'tcx> { logs: Vec>, num_open_snapshots: usize, + stalled_goal_generation: Option, } /// The UndoLogs trait defines how we undo a particular kind of action (of type T). We can undo any @@ -145,6 +147,7 @@ impl<'tcx> InferCtxtInner<'tcx> { } self.type_variable_storage.finalize_rollback(); + self.undo_log.stalled_goal_generation = snapshot.stalled_goal_generation; if self.undo_log.num_open_snapshots == 1 { // After the root snapshot the undo log should be empty. @@ -171,9 +174,32 @@ impl<'tcx> InferCtxtInner<'tcx> { } impl<'tcx> InferCtxtUndoLogs<'tcx> { + pub(crate) fn new(track_stalled_goal_generation: bool) -> Self { + Self { + stalled_goal_generation: track_stalled_goal_generation.then_some(0), + ..Default::default() + } + } + + #[inline] + pub(crate) fn stalled_goal_generation(&self) -> Option { + self.stalled_goal_generation + } + + #[inline] + pub(crate) fn bump_stalled_goal_generation(&mut self) { + if let Some(generation) = &mut self.stalled_goal_generation { + *generation = generation.wrapping_add(1); + } + } + pub(crate) fn start_snapshot(&mut self) -> Snapshot<'tcx> { self.num_open_snapshots += 1; - Snapshot { undo_len: self.logs.len(), _marker: PhantomData } + Snapshot { + undo_len: self.logs.len(), + stalled_goal_generation: self.stalled_goal_generation, + _marker: PhantomData, + } } pub(crate) fn region_constraints_in_snapshot( diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index c74cc92e7d006..8c6beb0408144 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -177,6 +177,9 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { pub(crate) fn equate(&mut self, a: ty::TyVid, b: ty::TyVid) { debug_assert!(self.probe(a).is_unknown()); debug_assert!(self.probe(b).is_unknown()); + + self.undo_log.bump_stalled_goal_generation(); + self.eq_relations().union(a, b); self.sub_unification_table().union(a, b); } @@ -188,6 +191,9 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { pub(crate) fn sub_unify(&mut self, a: ty::TyVid, b: ty::TyVid) { debug_assert!(self.probe(a).is_unknown()); debug_assert!(self.probe(b).is_unknown()); + + self.undo_log.bump_stalled_goal_generation(); + self.sub_unification_table().union(a, b); } @@ -203,6 +209,9 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { "instantiating type variable `{vid:?}` twice: new-value = {ty:?}, old-value={:?}", self.eq_relations().probe_value(vid) ); + + self.undo_log.bump_stalled_goal_generation(); + self.eq_relations().union_value(vid, TypeVariableValue::Known { value: ty }); } diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 7b09baf7fe03e..9b3e3cb974f06 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -9,7 +9,8 @@ use rustc_infer::traits::{ use rustc_middle::ty::{self, TyCtxt, TypeVisitableExt, TypingMode}; use rustc_next_trait_solver::solve::fast_path::compute_goal_fast_path; use rustc_next_trait_solver::solve::{ - GoalEvaluation, GoalStalledOn, HasChanged, SolverDelegateEvalExt as _, StalledOnCoroutines, + GoalEvaluation, GoalStalledOn, GoalStalledOnOpaques, HasChanged, SolverDelegateEvalExt as _, + StalledOnCoroutines, }; use thin_vec::ThinVec; use tracing::instrument; @@ -49,6 +50,17 @@ pub struct FulfillmentCtxt<'tcx, E: 'tcx> { /// gets rolled back. Because of this we explicitly check that we only /// use the context in exactly this snapshot. usable_in_snapshot: usize, + + last_stalled_goal_generation: u64, + + /// Whether any trackable stalled obligation requires the opaque + /// type storage to remain empty. + stalled_on_empty_opaques: bool, + + /// Whether every pending obligation can use the context-wide + /// stalled-goal fast path. + all_pending_trackable: bool, + _errors: PhantomData, } @@ -99,13 +111,33 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { "new trait solver fulfillment context created when \ infcx is set up for old trait solver" ); + let generation = infcx.stalled_goal_generation(); + FulfillmentCtxt { obligations: Default::default(), usable_in_snapshot: infcx.num_open_snapshots(), + last_stalled_goal_generation: generation, + stalled_on_empty_opaques: false, + all_pending_trackable: true, _errors: PhantomData, } } + fn record_trackable_stalled_on( + stalled_on: &GoalStalledOn>, + stalled_on_empty_opaques: &mut bool, + ) -> bool { + match stalled_on.opaques { + GoalStalledOnOpaques::No => {} + GoalStalledOnOpaques::Yes { num_opaques_in_storage: 0, .. } => { + *stalled_on_empty_opaques = true; + } + GoalStalledOnOpaques::Yes { .. } => return false, + } + + true + } + fn inspect_evaluated_obligation( infcx: &InferCtxt<'tcx>, obligation: &PredicateObligation<'tcx>, @@ -142,10 +174,22 @@ where match certainty { Certainty::Yes => {} Certainty::Maybe(_) => { + if let Some(stalled_on) = &stalled_on { + if !Self::record_trackable_stalled_on( + stalled_on, + &mut self.stalled_on_empty_opaques, + ) { + self.all_pending_trackable = false; + } + } else { + self.all_pending_trackable = false; + } + self.obligations.register(obligation, stalled_on); } } } else { + self.all_pending_trackable = false; self.obligations.register(obligation, None); } } @@ -165,8 +209,35 @@ where assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); let mut errors = TraitErrors::NoErrors; let delegate = <&SolverDelegate<'tcx>>::from(infcx); + + let generation = infcx.stalled_goal_generation(); + + if self.obligations.pending.is_empty() { + self.last_stalled_goal_generation = generation; + self.stalled_on_empty_opaques = false; + self.all_pending_trackable = true; + return errors; + } + + if !infcx.tcx.disable_trait_solver_fast_paths() + && self.all_pending_trackable + && self.last_stalled_goal_generation == generation + { + let opaques_unchanged = !self.stalled_on_empty_opaques + || infcx.inner.borrow_mut().opaque_types().is_empty(); + + if opaques_unchanged { + return errors; + } + } + loop { + let pass_generation = infcx.stalled_goal_generation(); + let mut any_changed = false; + let mut overflowed = false; + let mut all_pending_trackable = true; + let mut stalled_on_empty_opaques = false; self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| { // Common case: still stalled; keep the obligation. This path is extremely hot in @@ -174,6 +245,11 @@ where if let Some(stalled_on) = opt_stalled_on && delegate.goal_remains_stalled(stalled_on) { + if !Self::record_trackable_stalled_on(stalled_on, &mut stalled_on_empty_opaques) + { + all_pending_trackable = false; + } + return true; } @@ -249,12 +325,33 @@ where // Update `opt_stalled_on` goal, for the next retain_mut, because we are // running until a fixpoint. *opt_stalled_on = stalled_on; + + if let Some(stalled_on) = opt_stalled_on { + if !Self::record_trackable_stalled_on( + stalled_on, + &mut stalled_on_empty_opaques, + ) { + all_pending_trackable = false; + } + } else { + all_pending_trackable = false; + } + true } } }); + if overflowed { + self.all_pending_trackable = false; + self.obligations.on_fulfillment_overflow(infcx); + // Only return true errors that we have accumulated while processing. + return errors; + } if !any_changed { + self.all_pending_trackable = all_pending_trackable; + self.stalled_on_empty_opaques = stalled_on_empty_opaques; + self.last_stalled_goal_generation = pass_generation; break; } } From 9d6874b8fa9a462bc47d49b818ebc8baa7100630 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Mon, 31 Aug 2026 19:18:58 +0330 Subject: [PATCH 2/7] move stalled-goal generation onto InferCtxtInner Signed-off-by: Amirhossein Akhlaghpour --- compiler/rustc_infer/src/infer/mod.rs | 52 ++++++++++++++----- .../rustc_infer/src/infer/snapshot/mod.rs | 2 +- .../src/infer/snapshot/undo_log.rs | 33 +++--------- .../rustc_infer/src/infer/type_variable.rs | 17 ++++-- 4 files changed, 59 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index e4966214b9d9f..d50960e7ae7a7 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -96,6 +96,11 @@ pub(crate) type UnificationTable<'a, 'tcx, T> = ut::UnificationTable< pub struct InferCtxtInner<'tcx> { undo_log: InferCtxtUndoLogs<'tcx>, + /// Bumped whenever an inference change may let a stalled fulfillment goal + /// make progress. Snapshots save and restore the value, but individual bumps + /// are not undo-log entries. + stalled_goal_generation: Option, + /// Cache for projections. /// /// This cache is snapshotted along with the infcx. @@ -169,7 +174,8 @@ pub struct InferCtxtInner<'tcx> { impl<'tcx> InferCtxtInner<'tcx> { fn new(next_trait_solver: bool) -> InferCtxtInner<'tcx> { InferCtxtInner { - undo_log: InferCtxtUndoLogs::new(next_trait_solver), + undo_log: InferCtxtUndoLogs::default(), + stalled_goal_generation: next_trait_solver.then_some(0), projection_cache: Default::default(), type_variable_storage: Default::default(), @@ -209,7 +215,7 @@ impl<'tcx> InferCtxtInner<'tcx> { #[inline] fn type_variables(&mut self) -> type_variable::TypeVariableTable<'_, 'tcx> { - self.type_variable_storage.with_log(&mut self.undo_log) + self.type_variable_storage.with_log(&mut self.undo_log, &mut self.stalled_goal_generation) } #[inline] @@ -232,41 +238,58 @@ impl<'tcx> InferCtxtInner<'tcx> { self.const_unification_storage.with_log(&mut self.undo_log) } - // Keep mutations to non-type inference variables synchronized with the - // generation used by the stalled-goal fulfillment fast path. + #[inline] + pub(crate) fn start_snapshot(&mut self) -> snapshot::undo_log::Snapshot<'tcx> { + self.undo_log.start_snapshot(self.stalled_goal_generation) + } + + #[inline] + fn stalled_goal_generation(&self) -> Option { + self.stalled_goal_generation + } + + #[inline] + fn bump_stalled_goal_generation(&mut self) { + if let Some(generation) = &mut self.stalled_goal_generation { + *generation = generation.wrapping_add(1); + } + } + + // These mutations can unblock stalled goals too, so route them through the + // same generation bump. #[inline] fn equate_int_vids(&mut self, a: ty::IntVid, b: ty::IntVid) { - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.int_unification_table().union(a, b); } #[inline] fn equate_float_vids(&mut self, a: ty::FloatVid, b: ty::FloatVid) { - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.float_unification_table().union(a, b); } #[inline] fn equate_const_vids(&mut self, a: ty::ConstVid, b: ty::ConstVid) { - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.const_unification_table().union(a, b); } #[inline] fn instantiate_int_var(&mut self, vid: ty::IntVid, value: ty::IntVarValue) { - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.int_unification_table().union_value(vid, value); } #[inline] fn instantiate_float_var(&mut self, vid: ty::FloatVid, value: ty::FloatVarValue) { - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.float_unification_table().union_value(vid, value); } #[inline] fn instantiate_const_var(&mut self, vid: ty::ConstVid, value: ty::Const<'tcx>) { - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.const_unification_table().union_value(vid, ConstVariableValue::Known { value }); } @@ -1176,7 +1199,9 @@ impl<'tcx> InferCtxt<'tcx> { let ty_sub_vid = self.sub_unification_table_root_var(ty_vid); let inner = &mut *self.inner.borrow_mut(); - let mut type_variables = inner.type_variable_storage.with_log(&mut inner.undo_log); + let mut type_variables = inner + .type_variable_storage + .with_log(&mut inner.undo_log, &mut inner.stalled_goal_generation); inner.opaque_type_storage.iter_opaque_types().any(|(_, hidden_ty)| { if let ty::Infer(ty::TyVar(hidden_vid)) = *hidden_ty.ty.kind() { let opaque_sub_vid = type_variables.sub_unification_table_root_var(hidden_vid); @@ -1205,7 +1230,9 @@ impl<'tcx> InferCtxt<'tcx> { let inner = &mut *self.inner.borrow_mut(); // This is iffy, can't call `type_variables()` as we're already // borrowing the `opaque_type_storage` here. - let mut type_variables = inner.type_variable_storage.with_log(&mut inner.undo_log); + let mut type_variables = inner + .type_variable_storage + .with_log(&mut inner.undo_log, &mut inner.stalled_goal_generation); inner .opaque_type_storage .iter_opaque_types() @@ -1756,7 +1783,6 @@ impl<'tcx> InferCtxt<'tcx> { pub fn stalled_goal_generation(&self) -> u64 { self.inner .borrow() - .undo_log .stalled_goal_generation() .expect("stalled-goal generation requires the next trait solver") } diff --git a/compiler/rustc_infer/src/infer/snapshot/mod.rs b/compiler/rustc_infer/src/infer/snapshot/mod.rs index b16c80cf20143..3c2e12fe22916 100644 --- a/compiler/rustc_infer/src/infer/snapshot/mod.rs +++ b/compiler/rustc_infer/src/infer/snapshot/mod.rs @@ -51,7 +51,7 @@ impl<'tcx> InferCtxt<'tcx> { let mut inner = self.inner.borrow_mut(); CombinedSnapshot { - undo_snapshot: inner.undo_log.start_snapshot(), + undo_snapshot: inner.start_snapshot(), region_constraints_snapshot: inner.unwrap_region_constraints().start_snapshot(), universe: self.universe(), } diff --git a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs index 0981758324691..6acc479fe62d5 100644 --- a/compiler/rustc_infer/src/infer/snapshot/undo_log.rs +++ b/compiler/rustc_infer/src/infer/snapshot/undo_log.rs @@ -100,7 +100,6 @@ impl<'tcx> Rollback> for InferCtxtInner<'tcx> { pub(crate) struct InferCtxtUndoLogs<'tcx> { logs: Vec>, num_open_snapshots: usize, - stalled_goal_generation: Option, } /// The UndoLogs trait defines how we undo a particular kind of action (of type T). We can undo any @@ -147,7 +146,7 @@ impl<'tcx> InferCtxtInner<'tcx> { } self.type_variable_storage.finalize_rollback(); - self.undo_log.stalled_goal_generation = snapshot.stalled_goal_generation; + self.stalled_goal_generation = snapshot.stalled_goal_generation; if self.undo_log.num_open_snapshots == 1 { // After the root snapshot the undo log should be empty. @@ -174,32 +173,12 @@ impl<'tcx> InferCtxtInner<'tcx> { } impl<'tcx> InferCtxtUndoLogs<'tcx> { - pub(crate) fn new(track_stalled_goal_generation: bool) -> Self { - Self { - stalled_goal_generation: track_stalled_goal_generation.then_some(0), - ..Default::default() - } - } - - #[inline] - pub(crate) fn stalled_goal_generation(&self) -> Option { - self.stalled_goal_generation - } - - #[inline] - pub(crate) fn bump_stalled_goal_generation(&mut self) { - if let Some(generation) = &mut self.stalled_goal_generation { - *generation = generation.wrapping_add(1); - } - } - - pub(crate) fn start_snapshot(&mut self) -> Snapshot<'tcx> { + pub(crate) fn start_snapshot( + &mut self, + stalled_goal_generation: Option, + ) -> Snapshot<'tcx> { self.num_open_snapshots += 1; - Snapshot { - undo_len: self.logs.len(), - stalled_goal_generation: self.stalled_goal_generation, - _marker: PhantomData, - } + Snapshot { undo_len: self.logs.len(), stalled_goal_generation, _marker: PhantomData } } pub(crate) fn region_constraints_in_snapshot( diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index 8c6beb0408144..883d74f616d8c 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -88,6 +88,7 @@ pub(crate) struct TypeVariableTable<'a, 'tcx> { storage: &'a mut TypeVariableStorage<'tcx>, undo_log: &'a mut InferCtxtUndoLogs<'tcx>, + stalled_goal_generation: &'a mut Option, } #[derive(Copy, Clone, Debug)] @@ -143,8 +144,9 @@ impl<'tcx> TypeVariableStorage<'tcx> { pub(crate) fn with_log<'a>( &'a mut self, undo_log: &'a mut InferCtxtUndoLogs<'tcx>, + stalled_goal_generation: &'a mut Option, ) -> TypeVariableTable<'a, 'tcx> { - TypeVariableTable { storage: self, undo_log } + TypeVariableTable { storage: self, undo_log, stalled_goal_generation } } #[inline] @@ -163,6 +165,13 @@ impl<'tcx> TypeVariableStorage<'tcx> { } impl<'tcx> TypeVariableTable<'_, 'tcx> { + #[inline] + fn bump_stalled_goal_generation(&mut self) { + if let Some(generation) = self.stalled_goal_generation.as_mut() { + *generation = generation.wrapping_add(1); + } + } + /// Returns the origin that was given when `vid` was created. /// /// Note that this function does not return care whether @@ -178,7 +187,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { debug_assert!(self.probe(a).is_unknown()); debug_assert!(self.probe(b).is_unknown()); - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.eq_relations().union(a, b); self.sub_unification_table().union(a, b); @@ -192,7 +201,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { debug_assert!(self.probe(a).is_unknown()); debug_assert!(self.probe(b).is_unknown()); - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.sub_unification_table().union(a, b); } @@ -210,7 +219,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { self.eq_relations().probe_value(vid) ); - self.undo_log.bump_stalled_goal_generation(); + self.bump_stalled_goal_generation(); self.eq_relations().union_value(vid, TypeVariableValue::Known { value: ty }); } From 30bfbe369c58e075b8cec36f1ac293a963f6cce4 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Tue, 1 Sep 2026 13:10:25 +0330 Subject: [PATCH 3/7] refactor stalled goal generation tracking Signed-off-by: Amirhossein Akhlaghpour --- compiler/rustc_infer/src/infer/context.rs | 6 ++-- compiler/rustc_infer/src/infer/mod.rs | 30 ++++++++++++++----- .../src/infer/relate/generalize.rs | 8 ++--- .../src/infer/relate/type_relating.rs | 2 +- .../rustc_infer/src/infer/type_variable.rs | 17 +---------- 5 files changed, 31 insertions(+), 32 deletions(-) diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index ac556d285fa12..817bc8191f4ed 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -201,7 +201,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { } fn equate_ty_vids_raw(&self, a: ty::TyVid, b: ty::TyVid) { - self.inner.borrow_mut().type_variables().equate(a, b); + self.inner.borrow_mut().equate_ty_vids(a, b); } fn sub_unify_ty_vids_raw(&self, a: ty::TyVid, b: ty::TyVid) { @@ -223,7 +223,7 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { fn instantiate_ty_var_raw(&self, vid: ty::TyVid, ty: Ty<'tcx>) { let ty = lower_universe(self, self.try_resolve_ty_var(vid).unwrap_err(), ty); - self.inner.borrow_mut().type_variables().instantiate(vid, ty); + self.inner.borrow_mut().instantiate_ty_var(vid, ty); } fn instantiate_const_var_raw(&self, vid: ty::ConstVid, ct: ty::Const<'tcx>) { @@ -464,7 +464,7 @@ impl<'a, 'tcx> ty::TypeFolder> for LowerUniverseFolder<'a, 'tcx> { let origin = inner.type_variables().var_origin(vid); let new_var_id = inner.type_variables().new_var(self.for_universe, origin); - inner.type_variables().equate(vid, new_var_id); + inner.equate_ty_vids(vid, new_var_id); Ty::new_var(self.cx(), new_var_id) } } diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index d50960e7ae7a7..d1e2a9138e321 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -215,7 +215,7 @@ impl<'tcx> InferCtxtInner<'tcx> { #[inline] fn type_variables(&mut self) -> type_variable::TypeVariableTable<'_, 'tcx> { - self.type_variable_storage.with_log(&mut self.undo_log, &mut self.stalled_goal_generation) + self.type_variable_storage.with_log(&mut self.undo_log) } #[inline] @@ -255,6 +255,24 @@ impl<'tcx> InferCtxtInner<'tcx> { } } + #[inline] + fn equate_ty_vids(&mut self, a: ty::TyVid, b: ty::TyVid) { + self.bump_stalled_goal_generation(); + self.type_variables().equate(a, b); + } + + #[inline] + fn sub_unify_ty_vids(&mut self, a: ty::TyVid, b: ty::TyVid) { + self.bump_stalled_goal_generation(); + self.type_variables().sub_unify(a, b); + } + + #[inline] + fn instantiate_ty_var(&mut self, vid: ty::TyVid, ty: Ty<'tcx>) { + self.bump_stalled_goal_generation(); + self.type_variables().instantiate(vid, ty); + } + // These mutations can unblock stalled goals too, so route them through the // same generation bump. #[inline] @@ -1199,9 +1217,7 @@ impl<'tcx> InferCtxt<'tcx> { let ty_sub_vid = self.sub_unification_table_root_var(ty_vid); let inner = &mut *self.inner.borrow_mut(); - let mut type_variables = inner - .type_variable_storage - .with_log(&mut inner.undo_log, &mut inner.stalled_goal_generation); + let mut type_variables = inner.type_variable_storage.with_log(&mut inner.undo_log); inner.opaque_type_storage.iter_opaque_types().any(|(_, hidden_ty)| { if let ty::Infer(ty::TyVar(hidden_vid)) = *hidden_ty.ty.kind() { let opaque_sub_vid = type_variables.sub_unification_table_root_var(hidden_vid); @@ -1230,9 +1246,7 @@ impl<'tcx> InferCtxt<'tcx> { let inner = &mut *self.inner.borrow_mut(); // This is iffy, can't call `type_variables()` as we're already // borrowing the `opaque_type_storage` here. - let mut type_variables = inner - .type_variable_storage - .with_log(&mut inner.undo_log, &mut inner.stalled_goal_generation); + let mut type_variables = inner.type_variable_storage.with_log(&mut inner.undo_log); inner .opaque_type_storage .iter_opaque_types() @@ -1446,7 +1460,7 @@ impl<'tcx> InferCtxt<'tcx> { } pub fn sub_unify_ty_vids_raw(&self, a: ty::TyVid, b: ty::TyVid) { - self.inner.borrow_mut().type_variables().sub_unify(a, b); + self.inner.borrow_mut().sub_unify_ty_vids(a, b); } pub fn sub_unification_table_root_var(&self, var: ty::TyVid) -> ty::TyVid { diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 92f41aa9492ca..2b5a9fe8f2052 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -245,7 +245,7 @@ impl<'tcx> InferCtxt<'tcx> { match (l, r.kind()) { (TermVid::Ty(l), ty::TermKind::Ty(r)) => { if let Some(r) = r.ty_vid() { - self.inner.borrow_mut().type_variables().equate(l, r) + self.inner.borrow_mut().equate_ty_vids(l, r) } else { // Ideally, we put this assert into `type_variables().instantiate()`. // But we can't pass the infcx into it as the infcx is already @@ -253,7 +253,7 @@ impl<'tcx> InferCtxt<'tcx> { debug_assert!( self.try_resolve_ty_var(l).unwrap_err().can_name(ty::max_universe(self, r)) ); - self.inner.borrow_mut().type_variables().instantiate(l, r) + self.inner.borrow_mut().instantiate_ty_var(l, r) } } (TermVid::Const(l), ty::TermKind::Const(r)) => { @@ -541,7 +541,7 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { // Record that `vid` and `new_var_id` have to be subtypes // of each other. This is currently only used for diagnostics. // To see why, see the docs in the `type_variables` module. - inner.type_variables().sub_unify(vid, new_var_id); + inner.sub_unify_ty_vids(vid, new_var_id); // If we're in the new solver and create a new inference // variable inside of an alias we eagerly constrain that // inference variable to prevent unexpected ambiguity errors. @@ -561,7 +561,7 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { && !self.infcx.typing_mode_raw().is_coherence() && self.in_alias { - inner.type_variables().equate(vid, new_var_id); + inner.equate_ty_vids(vid, new_var_id); } debug!("replacing original vid={:?} with new={:?}", vid, new_var_id); diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index 7c8e263db5582..d837b91bd492b 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -166,7 +166,7 @@ impl<'tcx> TypeRelation> for TypeRelating<'_, 'tcx> { )); } ty::Invariant => { - infcx.inner.borrow_mut().type_variables().equate(a_id, b_id); + infcx.inner.borrow_mut().equate_ty_vids(a_id, b_id); } ty::Bivariant => { unreachable!("Expected bivariance to be handled in relate_with_variance") diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index 883d74f616d8c..75671446342eb 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -88,7 +88,6 @@ pub(crate) struct TypeVariableTable<'a, 'tcx> { storage: &'a mut TypeVariableStorage<'tcx>, undo_log: &'a mut InferCtxtUndoLogs<'tcx>, - stalled_goal_generation: &'a mut Option, } #[derive(Copy, Clone, Debug)] @@ -144,9 +143,8 @@ impl<'tcx> TypeVariableStorage<'tcx> { pub(crate) fn with_log<'a>( &'a mut self, undo_log: &'a mut InferCtxtUndoLogs<'tcx>, - stalled_goal_generation: &'a mut Option, ) -> TypeVariableTable<'a, 'tcx> { - TypeVariableTable { storage: self, undo_log, stalled_goal_generation } + TypeVariableTable { storage: self, undo_log } } #[inline] @@ -165,13 +163,6 @@ impl<'tcx> TypeVariableStorage<'tcx> { } impl<'tcx> TypeVariableTable<'_, 'tcx> { - #[inline] - fn bump_stalled_goal_generation(&mut self) { - if let Some(generation) = self.stalled_goal_generation.as_mut() { - *generation = generation.wrapping_add(1); - } - } - /// Returns the origin that was given when `vid` was created. /// /// Note that this function does not return care whether @@ -187,8 +178,6 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { debug_assert!(self.probe(a).is_unknown()); debug_assert!(self.probe(b).is_unknown()); - self.bump_stalled_goal_generation(); - self.eq_relations().union(a, b); self.sub_unification_table().union(a, b); } @@ -201,8 +190,6 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { debug_assert!(self.probe(a).is_unknown()); debug_assert!(self.probe(b).is_unknown()); - self.bump_stalled_goal_generation(); - self.sub_unification_table().union(a, b); } @@ -219,8 +206,6 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { self.eq_relations().probe_value(vid) ); - self.bump_stalled_goal_generation(); - self.eq_relations().union_value(vid, TypeVariableValue::Known { value: ty }); } From 9cd3ee11608f87b8ff5a12e192c273e77e3a4c26 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Sun, 6 Sep 2026 14:41:59 +0330 Subject: [PATCH 4/7] route const generalization union through generation bump Signed-off-by: Amirhossein Akhlaghpour --- compiler/rustc_infer/src/infer/relate/generalize.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/generalize.rs b/compiler/rustc_infer/src/infer/relate/generalize.rs index 2b5a9fe8f2052..5ba16c520954c 100644 --- a/compiler/rustc_infer/src/infer/relate/generalize.rs +++ b/compiler/rustc_infer/src/infer/relate/generalize.rs @@ -670,8 +670,8 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { } let mut inner = self.infcx.inner.borrow_mut(); - let variable_table = &mut inner.const_unification_table(); - match variable_table.probe_value(vid) { + let vid_value = inner.const_unification_table().probe_value(vid); + match vid_value { ConstVariableValue::Known { value: u } => { drop(inner); self.relate(u, u) @@ -680,7 +680,8 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { if self.for_universe.can_name(universe) { Ok(c) } else { - let new_var_id = variable_table + let new_var_id = inner + .const_unification_table() .new_key(ConstVariableValue::Unknown { origin, universe: self.for_universe, @@ -693,7 +694,7 @@ impl<'tcx> TypeRelation> for Generalizer<'_, 'tcx> { && !self.infcx.typing_mode_raw().is_coherence() && self.in_alias { - variable_table.union(vid, new_var_id); + inner.equate_const_vids(vid, new_var_id); } Ok(ty::Const::new_var(tcx, new_var_id)) } From 928e444e78366ab8ca0159551b3742eaa8470a40 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Mon, 21 Sep 2026 14:04:16 +0330 Subject: [PATCH 5/7] invalidate stalled goals after opaque type changes Signed-off-by: Amirhossein Akhlaghpour --- .../src/infer/canonical/query_response.rs | 8 +----- compiler/rustc_infer/src/infer/context.rs | 8 +++--- compiler/rustc_infer/src/infer/mod.rs | 28 ++++++++++++++++++- .../rustc_infer/src/infer/opaque_types/mod.rs | 20 ++++++------- .../src/solve/fulfill.rs | 9 ------ .../stalled-goal-opaque-invalidation.rs | 14 ++++++++++ 6 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 tests/ui/traits/next-solver/stalled-goal-opaque-invalidation.rs diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs index a0c8eb22a8273..41e6c57a055f6 100644 --- a/compiler/rustc_infer/src/infer/canonical/query_response.rs +++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs @@ -155,13 +155,7 @@ impl<'tcx> InferCtxt<'tcx> { }); debug!(?region_constraints); - let opaque_types = self - .inner - .borrow_mut() - .opaque_type_storage - .take_opaque_types() - .map(|(k, v)| (k, v.ty)) - .collect(); + let opaque_types = self.take_opaque_types().into_iter().map(|(k, v)| (k, v.ty)).collect(); Ok(QueryResponse { var_values: inference_vars.var_values, diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index 817bc8191f4ed..218f6292fa6d8 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -383,10 +383,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { hidden_ty: Ty<'tcx>, span: Span, ) { - self.inner - .borrow_mut() - .opaque_types() - .add_duplicate(opaque_type_key, ty::ProvisionalHiddenType { span, ty: hidden_ty }) + self.inner.borrow_mut().add_duplicate_opaque_type( + opaque_type_key, + ty::ProvisionalHiddenType { span, ty: hidden_ty }, + ) } fn reset_opaque_types(&self) { diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index d1e2a9138e321..9eb04b5086d11 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -223,6 +223,26 @@ impl<'tcx> InferCtxtInner<'tcx> { self.opaque_type_storage.with_log(&mut self.undo_log) } + #[inline] + fn register_opaque_type( + &mut self, + key: OpaqueTypeKey<'tcx>, + hidden_type: ProvisionalHiddenType<'tcx>, + ) -> Option> { + self.bump_stalled_goal_generation(); + self.opaque_types().register(key, hidden_type) + } + + #[inline] + fn add_duplicate_opaque_type( + &mut self, + key: OpaqueTypeKey<'tcx>, + hidden_type: ProvisionalHiddenType<'tcx>, + ) { + self.bump_stalled_goal_generation(); + self.opaque_types().add_duplicate(key, hidden_type); + } + #[inline] fn int_unification_table(&mut self) -> UnificationTable<'_, 'tcx, ty::IntVid> { self.int_unification_storage.with_log(&mut self.undo_log) @@ -1202,7 +1222,13 @@ impl<'tcx> InferCtxt<'tcx> { #[instrument(level = "debug", skip(self), ret)] pub fn take_opaque_types(&self) -> Vec<(OpaqueTypeKey<'tcx>, ProvisionalHiddenType<'tcx>)> { - self.inner.borrow_mut().opaque_type_storage.take_opaque_types().collect() + let inner = &mut *self.inner.borrow_mut(); + + if !inner.opaque_type_storage.is_empty() { + inner.bump_stalled_goal_generation(); + } + + inner.opaque_type_storage.take_opaque_types().collect() } #[instrument(level = "debug", skip(self), ret)] diff --git a/compiler/rustc_infer/src/infer/opaque_types/mod.rs b/compiler/rustc_infer/src/infer/opaque_types/mod.rs index 6a922f75dbe9b..5a9d148705dd1 100644 --- a/compiler/rustc_infer/src/infer/opaque_types/mod.rs +++ b/compiler/rustc_infer/src/infer/opaque_types/mod.rs @@ -204,7 +204,7 @@ impl<'tcx> InferCtxt<'tcx> { opaque_type_key: OpaqueTypeKey<'tcx>, hidden_ty: ProvisionalHiddenType<'tcx>, ) -> Option> { - self.inner.borrow_mut().opaque_types().register(opaque_type_key, hidden_ty) + self.inner.borrow_mut().register_opaque_type(opaque_type_key, hidden_ty) } /// Insert a hidden type into the opaque type storage, equating it @@ -238,11 +238,10 @@ impl<'tcx> InferCtxt<'tcx> { goals.push(Goal::new(tcx, param_env, ty::PredicateKind::Ambiguous)); } ty::TypingMode::Typeck { .. } => { - let prev = self - .inner - .borrow_mut() - .opaque_types() - .register(opaque_type_key, ProvisionalHiddenType { ty: hidden_ty, span }); + let prev = self.inner.borrow_mut().register_opaque_type( + opaque_type_key, + ProvisionalHiddenType { ty: hidden_ty, span }, + ); if let Some(prev) = prev { goals.extend( self.at(&ObligationCause::dummy_with_span(span), param_env) @@ -254,11 +253,10 @@ impl<'tcx> InferCtxt<'tcx> { } } ty::TypingMode::PostTypeckThroughBorrowck { .. } => { - let prev = self - .inner - .borrow_mut() - .opaque_types() - .register(opaque_type_key, ProvisionalHiddenType { ty: hidden_ty, span }); + let prev = self.inner.borrow_mut().register_opaque_type( + opaque_type_key, + ProvisionalHiddenType { ty: hidden_ty, span }, + ); // We either equate the new hidden type with the previous entry or with the type // inferred by HIR typeck. diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 9b3e3cb974f06..1915baaedcba9 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -235,7 +235,6 @@ where let pass_generation = infcx.stalled_goal_generation(); let mut any_changed = false; - let mut overflowed = false; let mut all_pending_trackable = true; let mut stalled_on_empty_opaques = false; @@ -336,18 +335,10 @@ where } else { all_pending_trackable = false; } - true } } }); - if overflowed { - self.all_pending_trackable = false; - self.obligations.on_fulfillment_overflow(infcx); - // Only return true errors that we have accumulated while processing. - return errors; - } - if !any_changed { self.all_pending_trackable = all_pending_trackable; self.stalled_on_empty_opaques = stalled_on_empty_opaques; diff --git a/tests/ui/traits/next-solver/stalled-goal-opaque-invalidation.rs b/tests/ui/traits/next-solver/stalled-goal-opaque-invalidation.rs new file mode 100644 index 0000000000000..2af9a097aa99e --- /dev/null +++ b/tests/ui/traits/next-solver/stalled-goal-opaque-invalidation.rs @@ -0,0 +1,14 @@ +//@ check-pass +//@ edition: 2021 +//@ compile-flags: -Znext-solver + +// A recursive non-defining use of the async fn opaque relies on the +// registered opaque type to guide inference for the recursive result. +async fn mirror(t: T) -> T { + let value = Box::pin(mirror(String::new())).await; + let _ = value.len(); + + t +} + +fn main() {} From ea86221784440c20bcf256238c5ecf7eb7d29a90 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Thu, 24 Sep 2026 13:42:23 +0330 Subject: [PATCH 6/7] simplify stalled-goal fast path tracking Signed-off-by: Amirhossein Akhlaghpour --- .../src/solve/fulfill.rs | 81 +++++-------------- 1 file changed, 21 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 1915baaedcba9..743f3b139efee 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -53,13 +53,8 @@ pub struct FulfillmentCtxt<'tcx, E: 'tcx> { last_stalled_goal_generation: u64, - /// Whether any trackable stalled obligation requires the opaque - /// type storage to remain empty. - stalled_on_empty_opaques: bool, - - /// Whether every pending obligation can use the context-wide - /// stalled-goal fast path. - all_pending_trackable: bool, + /// Whether every pending goal stays stalled until the generation changes. + all_goals_known_to_be_stalled: bool, _errors: PhantomData, } @@ -117,25 +112,18 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { obligations: Default::default(), usable_in_snapshot: infcx.num_open_snapshots(), last_stalled_goal_generation: generation, - stalled_on_empty_opaques: false, - all_pending_trackable: true, + all_goals_known_to_be_stalled: true, _errors: PhantomData, } } - fn record_trackable_stalled_on( - stalled_on: &GoalStalledOn>, - stalled_on_empty_opaques: &mut bool, - ) -> bool { - match stalled_on.opaques { - GoalStalledOnOpaques::No => {} - GoalStalledOnOpaques::Yes { num_opaques_in_storage: 0, .. } => { - *stalled_on_empty_opaques = true; - } - GoalStalledOnOpaques::Yes { .. } => return false, - } - - true + fn goal_is_known_to_be_stalled(stalled_on: &GoalStalledOn>) -> bool { + // Registering or removing an opaque bumps the generation. A goal that + // observed an empty storage therefore remains stalled until it changes. + matches!( + stalled_on.opaques, + GoalStalledOnOpaques::No | GoalStalledOnOpaques::Yes { num_opaques_in_storage: 0, .. } + ) } fn inspect_evaluated_obligation( @@ -174,22 +162,14 @@ where match certainty { Certainty::Yes => {} Certainty::Maybe(_) => { - if let Some(stalled_on) = &stalled_on { - if !Self::record_trackable_stalled_on( - stalled_on, - &mut self.stalled_on_empty_opaques, - ) { - self.all_pending_trackable = false; - } - } else { - self.all_pending_trackable = false; - } + self.all_goals_known_to_be_stalled &= + stalled_on.as_ref().is_some_and(Self::goal_is_known_to_be_stalled); self.obligations.register(obligation, stalled_on); } } } else { - self.all_pending_trackable = false; + self.all_goals_known_to_be_stalled = false; self.obligations.register(obligation, None); } } @@ -214,29 +194,22 @@ where if self.obligations.pending.is_empty() { self.last_stalled_goal_generation = generation; - self.stalled_on_empty_opaques = false; - self.all_pending_trackable = true; + self.all_goals_known_to_be_stalled = true; return errors; } if !infcx.tcx.disable_trait_solver_fast_paths() - && self.all_pending_trackable + && self.all_goals_known_to_be_stalled && self.last_stalled_goal_generation == generation { - let opaques_unchanged = !self.stalled_on_empty_opaques - || infcx.inner.borrow_mut().opaque_types().is_empty(); - - if opaques_unchanged { - return errors; - } + return errors; } loop { let pass_generation = infcx.stalled_goal_generation(); let mut any_changed = false; - let mut all_pending_trackable = true; - let mut stalled_on_empty_opaques = false; + let mut all_goals_known_to_be_stalled = true; self.obligations.pending.retain_mut(|(obligation, opt_stalled_on)| { // Common case: still stalled; keep the obligation. This path is extremely hot in @@ -244,10 +217,7 @@ where if let Some(stalled_on) = opt_stalled_on && delegate.goal_remains_stalled(stalled_on) { - if !Self::record_trackable_stalled_on(stalled_on, &mut stalled_on_empty_opaques) - { - all_pending_trackable = false; - } + all_goals_known_to_be_stalled &= Self::goal_is_known_to_be_stalled(stalled_on); return true; } @@ -325,23 +295,14 @@ where // running until a fixpoint. *opt_stalled_on = stalled_on; - if let Some(stalled_on) = opt_stalled_on { - if !Self::record_trackable_stalled_on( - stalled_on, - &mut stalled_on_empty_opaques, - ) { - all_pending_trackable = false; - } - } else { - all_pending_trackable = false; - } + all_goals_known_to_be_stalled &= + opt_stalled_on.as_ref().is_some_and(Self::goal_is_known_to_be_stalled); true } } }); if !any_changed { - self.all_pending_trackable = all_pending_trackable; - self.stalled_on_empty_opaques = stalled_on_empty_opaques; + self.all_goals_known_to_be_stalled = all_goals_known_to_be_stalled; self.last_stalled_goal_generation = pass_generation; break; } From 460e1e66a7fa1993c4ab04d45b5ca917dd3b0e03 Mon Sep 17 00:00:00 2001 From: Amirhossein Akhlaghpour Date: Thu, 1 Oct 2026 19:38:20 +0330 Subject: [PATCH 7/7] restrict opaque type mutation access Signed-off-by: Amirhossein Akhlaghpour --- compiler/rustc_infer/src/infer/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 9eb04b5086d11..808cdb507b54b 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -219,7 +219,12 @@ impl<'tcx> InferCtxtInner<'tcx> { } #[inline] - pub fn opaque_types(&mut self) -> opaque_types::OpaqueTypeTable<'_, 'tcx> { + pub fn opaque_types(&self) -> &OpaqueTypeStorage<'tcx> { + &self.opaque_type_storage + } + + #[inline] + fn opaque_types_mut(&mut self) -> opaque_types::OpaqueTypeTable<'_, 'tcx> { self.opaque_type_storage.with_log(&mut self.undo_log) } @@ -230,7 +235,7 @@ impl<'tcx> InferCtxtInner<'tcx> { hidden_type: ProvisionalHiddenType<'tcx>, ) -> Option> { self.bump_stalled_goal_generation(); - self.opaque_types().register(key, hidden_type) + self.opaque_types_mut().register(key, hidden_type) } #[inline] @@ -240,7 +245,7 @@ impl<'tcx> InferCtxtInner<'tcx> { hidden_type: ProvisionalHiddenType<'tcx>, ) { self.bump_stalled_goal_generation(); - self.opaque_types().add_duplicate(key, hidden_type); + self.opaque_types_mut().add_duplicate(key, hidden_type); } #[inline]