diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 583fb1d7db21a..62fb3370aa9b8 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -1613,6 +1613,23 @@ impl<'tcx> InferCtxt<'tcx> { } } + /// Smallest type vid equated, sub-unified, or instantiated since the last + /// [`Self::reset_min_changed_ty_vid`]. Used by next-solver fulfillment to + /// skip walking pending goals stalled only on older vids. + #[inline] + pub fn min_changed_ty_vid(&self) -> Option { + self.inner.borrow().type_variable_storage.min_changed_ty_vid() + } + + pub fn reset_min_changed_ty_vid(&self) { + self.inner.borrow_mut().type_variables().reset_min_changed_ty_vid(); + } + + #[inline] + pub fn opaque_type_count(&self) -> usize { + self.inner.borrow().opaque_type_storage.num_entries().num_opaque_types() + } + /// `ty_or_const_infer_var_changed` is equivalent to one of these two: /// * `shallow_resolve(ty) != ty` (where `ty.kind = ty::Infer(_)`) /// * `shallow_resolve(ct) != ct` (where `ct.kind = ty::ConstKind::Infer(_)`) diff --git a/compiler/rustc_infer/src/infer/opaque_types/table.rs b/compiler/rustc_infer/src/infer/opaque_types/table.rs index 066d12be320a2..ff2835baa9d20 100644 --- a/compiler/rustc_infer/src/infer/opaque_types/table.rs +++ b/compiler/rustc_infer/src/infer/opaque_types/table.rs @@ -30,6 +30,13 @@ impl rustc_type_ir::inherent::OpaqueTypeStorageEntries for OpaqueTypeStorageEntr } } +impl OpaqueTypeStorageEntries { + #[inline] + pub fn num_opaque_types(self) -> usize { + self.opaque_types + } +} + impl<'tcx> OpaqueTypeStorage<'tcx> { #[instrument(level = "debug")] pub(crate) fn remove( diff --git a/compiler/rustc_infer/src/infer/type_variable.rs b/compiler/rustc_infer/src/infer/type_variable.rs index be0ac72d85675..c00bff1451845 100644 --- a/compiler/rustc_infer/src/infer/type_variable.rs +++ b/compiler/rustc_infer/src/infer/type_variable.rs @@ -2,7 +2,7 @@ use std::cmp; use std::marker::PhantomData; use std::ops::Range; -use rustc_data_structures::undo_log::Rollback; +use rustc_data_structures::undo_log::{Rollback, UndoLogs}; use rustc_data_structures::{snapshot_vec as sv, unify as ut}; use rustc_hir::HirId; use rustc_hir::def_id::DefId; @@ -19,6 +19,8 @@ use crate::infer::InferCtxtUndoLogs; pub(crate) enum UndoLog<'tcx> { EqRelation(sv::UndoLog>>), SubRelation(sv::UndoLog>), + /// Previous value of [`TypeVariableStorage::min_changed_ty_vid`]. + MinChangedTyVid(Option), } /// Convert from a specific kind of undo to the more general UndoLog @@ -52,6 +54,7 @@ impl<'tcx> Rollback> for TypeVariableStorage<'tcx> { match undo { UndoLog::EqRelation(undo) => self.eq_relations.reverse(undo), UndoLog::SubRelation(undo) => self.sub_unification_table.reverse(undo), + UndoLog::MinChangedTyVid(prev) => self.min_changed_ty_vid = prev, } } } @@ -83,6 +86,12 @@ pub(crate) struct TypeVariableStorage<'tcx> { /// type of `x` is only a supertype of the argument of `returns_arg`. We /// still want to suggest specifying the type of the argument. sub_unification_table: ut::UnificationTableStorage, + /// Smallest type-inference vid that was equated, sub-unified, or + /// instantiated since the last [`TypeVariableTable::reset_min_changed_ty_vid`]. + /// + /// Next-solver fulfillment uses this to skip walking pending goals that + /// can only become unstalled by changes to older vids (see rustc#159933). + min_changed_ty_vid: Option, } pub(crate) struct TypeVariableTable<'a, 'tcx> { @@ -161,6 +170,11 @@ impl<'tcx> TypeVariableStorage<'tcx> { pub(crate) fn sub_unification_table_ref(&self) -> &ut::UnificationTableStorage { &self.sub_unification_table } + + #[inline] + pub(crate) fn min_changed_ty_vid(&self) -> Option { + self.min_changed_ty_vid + } } impl<'tcx> TypeVariableTable<'_, 'tcx> { @@ -172,12 +186,37 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { self.storage.values[vid].origin } + fn note_ty_infer_change(&mut self, vid: ty::TyVid) { + let idx = vid.as_u32(); + let old = self.storage.min_changed_ty_vid; + let new = Some(old.map_or(idx, |m| m.min(idx))); + if old != new { + self.undo_log.push(UndoLog::MinChangedTyVid(old)); + self.storage.min_changed_ty_vid = new; + } + } + + pub(crate) fn reset_min_changed_ty_vid(&mut self) { + let old = self.storage.min_changed_ty_vid; + if old.is_some() { + self.undo_log.push(UndoLog::MinChangedTyVid(old)); + self.storage.min_changed_ty_vid = None; + } + } + /// Records that `a == b`. /// /// Precondition: neither `a` nor `b` are known. 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()); + let ra = self.root_var(a); + let rb = self.root_var(b); + let sa = self.sub_unification_table_root_var(a); + let sb = self.sub_unification_table_root_var(b); + self.note_ty_infer_change(ty::TyVid::from_u32( + ra.as_u32().min(rb.as_u32()).min(sa.as_u32()).min(sb.as_u32()), + )); self.eq_relations().union(a, b); self.sub_unification_table().union(a, b); } @@ -189,6 +228,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()); + let sa = self.sub_unification_table_root_var(a); + let sb = self.sub_unification_table_root_var(b); + self.note_ty_infer_change(if sa.as_u32() < sb.as_u32() { sa } else { sb }); self.sub_unification_table().union(a, b); } @@ -204,6 +246,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> { "instantiating type variable `{vid:?}` twice: new-value = {ty:?}, old-value={:?}", self.eq_relations().probe_value(vid) ); + self.note_ty_infer_change(vid); 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 549dcc24cfa9a..c52214d251443 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, TyOrConstInferVar, }; use thin_vec::ThinVec; use tracing::instrument; @@ -44,6 +45,15 @@ 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, + /// Whether every pending goal has precise, type-var-only stall info. + /// Int/float/const stalls and opaque-count mismatches force a full scan. + all_pending_trackable: bool, + /// Maximum type vid among pending `stalled_vars` / `sub_roots`. + /// Changes to strictly newer vids cannot make these goals unstalled. + max_stalled_ty_vid: Option, + /// Shared `GoalStalledOnOpaques::Yes` storage count, if any pending goal + /// recorded one. `None` means no pending goal depends on opaques. + stalled_opaque_count: Option, _errors: PhantomData, } @@ -132,10 +142,118 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { FulfillmentCtxt { obligations: Default::default(), usable_in_snapshot: infcx.num_open_snapshots(), + all_pending_trackable: true, + max_stalled_ty_vid: None, + stalled_opaque_count: None, _errors: PhantomData, } } + fn reset_tracking(&mut self) { + self.all_pending_trackable = true; + self.max_stalled_ty_vid = None; + self.stalled_opaque_count = None; + } + + fn note_registered_stalled_on(&mut self, stalled_on: Option<&GoalStalledOn>>) { + if !self.all_pending_trackable { + return; + } + let Some(stalled_on) = stalled_on else { + self.all_pending_trackable = false; + return; + }; + if !record_trackable_stalled_on( + stalled_on, + &mut self.max_stalled_ty_vid, + &mut self.stalled_opaque_count, + ) { + self.all_pending_trackable = false; + } + } + + fn recompute_tracking(&mut self) { + let mut max_stalled_ty_vid = None; + let mut stalled_opaque_count = None; + let mut all_pending_trackable = true; + for (_, stalled_on) in &self.obligations.pending { + match stalled_on { + Some(stalled_on) => { + if !record_trackable_stalled_on( + stalled_on, + &mut max_stalled_ty_vid, + &mut stalled_opaque_count, + ) { + all_pending_trackable = false; + break; + } + } + None => { + all_pending_trackable = false; + break; + } + } + } + self.all_pending_trackable = all_pending_trackable; + self.max_stalled_ty_vid = max_stalled_ty_vid; + self.stalled_opaque_count = stalled_opaque_count; + } + + /// Skip the pending-queue walk when no pending goal can have been + /// unstalled: every goal is trackable, opaque storage is unchanged, + /// and every type-infer change is to a newer vid than any stalled vid. + fn can_skip_fulfillment(&self, infcx: &InferCtxt<'tcx>) -> bool { + if infcx.disable_trait_solver_fast_paths() || !self.all_pending_trackable { + return false; + } + if let Some(n) = self.stalled_opaque_count + && infcx.opaque_type_count() != n + { + return false; + } + match (infcx.min_changed_ty_vid(), self.max_stalled_ty_vid) { + (None, _) | (Some(_), None) => true, + (Some(changed), Some(stalled)) => changed > stalled, + } + } +} + +/// Returns `false` if this stalled goal cannot participate in the +/// "no relevant type-infer change" fulfillment skip. +fn record_trackable_stalled_on<'tcx>( + stalled_on: &GoalStalledOn>, + max_stalled_ty_vid: &mut Option, + stalled_opaque_count: &mut Option, +) -> bool { + for var in &stalled_on.stalled_vars { + match *var { + TyOrConstInferVar::Ty(vid) => { + let idx = vid.as_u32(); + *max_stalled_ty_vid = Some(max_stalled_ty_vid.map_or(idx, |m| m.max(idx))); + } + TyOrConstInferVar::TyInt(_) + | TyOrConstInferVar::TyFloat(_) + | TyOrConstInferVar::Const(_) => return false, + } + } + for &vid in &stalled_on.sub_roots { + let idx = vid.as_u32(); + *max_stalled_ty_vid = Some(max_stalled_ty_vid.map_or(idx, |m| m.max(idx))); + } + match stalled_on.opaques { + GoalStalledOnOpaques::No => true, + GoalStalledOnOpaques::Yes { num_opaques_in_storage, .. } => match stalled_opaque_count { + None => { + *stalled_opaque_count = Some(num_opaques_in_storage); + true + } + Some(n) if *n == num_opaques_in_storage => true, + Some(_) => false, + }, + } +} + +impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> { fn inspect_evaluated_obligation( infcx: &InferCtxt<'tcx>, obligation: &PredicateObligation<'tcx>, @@ -172,10 +290,12 @@ where match certainty { Certainty::Yes => {} Certainty::Maybe(_) => { + self.note_registered_stalled_on(stalled_on.as_ref()); self.obligations.register(obligation, stalled_on); } } } else { + self.note_registered_stalled_on(None); self.obligations.register(obligation, None); } } @@ -193,6 +313,13 @@ where fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors { assert_eq!(self.usable_in_snapshot, infcx.num_open_snapshots()); + if self.obligations.pending.is_empty() { + self.reset_tracking(); + return TraitErrors::NoErrors; + } + if self.can_skip_fulfillment(infcx) { + return TraitErrors::NoErrors; + } let mut errors = TraitErrors::NoErrors; let delegate = <&SolverDelegate<'tcx>>::from(infcx); loop { @@ -285,6 +412,8 @@ where }); if overflowed { self.obligations.on_fulfillment_overflow(infcx); + // Remaining pending goals may be a mix; do not take the skip path. + self.all_pending_trackable = false; // Only return true errors that we have accumulated while processing. return errors; } @@ -294,6 +423,8 @@ where } } + infcx.reset_min_changed_ty_vid(); + self.recompute_tracking(); errors } diff --git a/tests/ui/traits/next-solver/fulfillment-skip-unrelated-infer.rs b/tests/ui/traits/next-solver/fulfillment-skip-unrelated-infer.rs new file mode 100644 index 0000000000000..50387bc8e6327 --- /dev/null +++ b/tests/ui/traits/next-solver/fulfillment-skip-unrelated-infer.rs @@ -0,0 +1,35 @@ +//@ check-pass +// +// Typeck of a long chain of stalled `Default` obligations, then a +// constraint that resolves them. Next-solver fulfillment may skip +// walking the pending queue when only newer, unrelated infer vids +// changed (rustc#159933). This must still notice the final `u8`. + +pub fn big() { + let mut v = Vec::new(); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(Default::default()); + v.push(0u8); +} + +fn main() { + big(); +}