Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions compiler/rustc_infer/src/infer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u32> {
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(_)`)
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_infer/src/infer/opaque_types/table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
45 changes: 44 additions & 1 deletion compiler/rustc_infer/src/infer/type_variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,6 +19,8 @@ use crate::infer::InferCtxtUndoLogs;
pub(crate) enum UndoLog<'tcx> {
EqRelation(sv::UndoLog<ut::Delegate<TyVidEqKey<'tcx>>>),
SubRelation(sv::UndoLog<ut::Delegate<TyVidSubKey>>),
/// Previous value of [`TypeVariableStorage::min_changed_ty_vid`].
MinChangedTyVid(Option<u32>),
}

/// Convert from a specific kind of undo to the more general UndoLog
Expand Down Expand Up @@ -52,6 +54,7 @@ impl<'tcx> Rollback<UndoLog<'tcx>> 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,
}
}
}
Expand Down Expand Up @@ -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<TyVidSubKey>,
/// 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<u32>,
}

pub(crate) struct TypeVariableTable<'a, 'tcx> {
Expand Down Expand Up @@ -161,6 +170,11 @@ impl<'tcx> TypeVariableStorage<'tcx> {
pub(crate) fn sub_unification_table_ref(&self) -> &ut::UnificationTableStorage<TyVidSubKey> {
&self.sub_unification_table
}

#[inline]
pub(crate) fn min_changed_ty_vid(&self) -> Option<u32> {
self.min_changed_ty_vid
}
}

impl<'tcx> TypeVariableTable<'_, 'tcx> {
Expand All @@ -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);
}
Expand All @@ -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);
}

Expand All @@ -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 });
}

Expand Down
133 changes: 132 additions & 1 deletion compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<u32>,
/// Shared `GoalStalledOnOpaques::Yes` storage count, if any pending goal
/// recorded one. `None` means no pending goal depends on opaques.
stalled_opaque_count: Option<usize>,
_errors: PhantomData<E>,
}

Expand Down Expand Up @@ -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<TyCtxt<'tcx>>>) {
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<TyCtxt<'tcx>>,
max_stalled_ty_vid: &mut Option<u32>,
stalled_opaque_count: &mut Option<usize>,
) -> 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>,
Expand Down Expand Up @@ -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);
}
}
Expand All @@ -193,6 +313,13 @@ where

fn try_evaluate_obligations(&mut self, infcx: &InferCtxt<'tcx>) -> TraitErrors<E> {
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 {
Expand Down Expand Up @@ -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;
}
Expand All @@ -294,6 +423,8 @@ where
}
}

infcx.reset_min_changed_ty_vid();
self.recompute_tracking();
errors
}

Expand Down
35 changes: 35 additions & 0 deletions tests/ui/traits/next-solver/fulfillment-skip-unrelated-infer.rs
Original file line number Diff line number Diff line change
@@ -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();
}
Loading