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
16 changes: 16 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,22 @@ impl<'tcx> InferCtxt<'tcx> {
}
}

/// Whether any type vid was instantiated with a known type since the last
/// [`Self::reset_ty_instantiated`].
#[inline]
pub fn ty_was_instantiated(&self) -> bool {
self.inner.borrow().type_variable_storage.ty_instantiated()
}

pub fn reset_ty_instantiated(&self) {
self.inner.borrow_mut().type_variables().reset_ty_instantiated();
}

#[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
33 changes: 32 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::ty_instantiated`].
TyInstantiated(bool),
}

/// 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::TyInstantiated(prev) => self.ty_instantiated = prev,
}
}
}
Expand Down Expand Up @@ -83,6 +86,14 @@ 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>,
/// Whether any type vid was instantiated with a known type since the last
/// [`TypeVariableTable::reset_ty_instantiated`].
///
/// Equating two unknown vids does not set this. Next-solver fulfillment
/// uses that to skip the pending-queue walk when every pending goal is
/// stalled on at most one type var: unknown-unknown unification cannot
/// make those goals progress (see rustc#159933).
ty_instantiated: bool,
}

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

#[inline]
pub(crate) fn ty_instantiated(&self) -> bool {
self.ty_instantiated
}
}

impl<'tcx> TypeVariableTable<'_, 'tcx> {
Expand All @@ -172,6 +188,20 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> {
self.storage.values[vid].origin
}

fn note_ty_instantiated(&mut self) {
if !self.storage.ty_instantiated {
self.undo_log.push(UndoLog::TyInstantiated(false));
self.storage.ty_instantiated = true;
}
}

pub(crate) fn reset_ty_instantiated(&mut self) {
if self.storage.ty_instantiated {
self.undo_log.push(UndoLog::TyInstantiated(true));
self.storage.ty_instantiated = false;
}
}

/// Records that `a == b`.
///
/// Precondition: neither `a` nor `b` are known.
Expand Down Expand Up @@ -204,6 +234,7 @@ impl<'tcx> TypeVariableTable<'_, 'tcx> {
"instantiating type variable `{vid:?}` twice: new-value = {ty:?}, old-value={:?}",
self.eq_relations().probe_value(vid)
);
self.note_ty_instantiated();
self.eq_relations().union_value(vid, TypeVariableValue::Known { value: ty });
}

Expand Down
129 changes: 128 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,
/// Whether every trackable pending goal is stalled on at most one type var.
/// Goals stalled on two type vars can progress when those vars are equated.
all_pending_single_ty_stall: bool,
/// 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,82 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> {
FulfillmentCtxt {
obligations: Default::default(),
usable_in_snapshot: infcx.num_open_snapshots(),
all_pending_trackable: true,
all_pending_single_ty_stall: true,
stalled_opaque_count: None,
_errors: PhantomData,
}
}

fn reset_tracking(&mut self) {
self.all_pending_trackable = true;
self.all_pending_single_ty_stall = true;
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.stalled_opaque_count,
&mut self.all_pending_single_ty_stall,
) {
self.all_pending_trackable = false;
}
}

fn recompute_tracking(&mut self) {
let mut stalled_opaque_count = None;
let mut all_pending_trackable = true;
let mut all_pending_single_ty_stall = true;
for (_, stalled_on) in &self.obligations.pending {
match stalled_on {
Some(stalled_on) => {
if !record_trackable_stalled_on(
stalled_on,
&mut stalled_opaque_count,
&mut all_pending_single_ty_stall,
) {
all_pending_trackable = false;
break;
}
}
None => {
all_pending_trackable = false;
break;
}
}
}
self.all_pending_trackable = all_pending_trackable;
self.all_pending_single_ty_stall = all_pending_single_ty_stall;
self.stalled_opaque_count = stalled_opaque_count;
}

/// Skip the pending-queue walk when no pending goal can have made progress:
/// every goal is type-var-only with at most one stalled vid, opaque storage
/// is unchanged, and no type vid was instantiated.
fn can_skip_fulfillment(&self, infcx: &InferCtxt<'tcx>) -> bool {
if infcx.disable_trait_solver_fast_paths()
|| !self.all_pending_trackable
|| !self.all_pending_single_ty_stall
|| infcx.ty_was_instantiated()
{
return false;
}
if let Some(n) = self.stalled_opaque_count
&& infcx.opaque_type_count() != n
{
return false;
}
true
}

fn inspect_evaluated_obligation(
infcx: &InferCtxt<'tcx>,
obligation: &PredicateObligation<'tcx>,
Expand All @@ -151,6 +233,38 @@ impl<'tcx, E: 'tcx> FulfillmentCtxt<'tcx, E> {
}
}

/// Returns `false` if this stalled goal cannot participate in the
/// "no type-var instantiate" fulfillment skip.
fn record_trackable_stalled_on<'tcx>(
stalled_on: &GoalStalledOn<TyCtxt<'tcx>>,
stalled_opaque_count: &mut Option<usize>,
all_pending_single_ty_stall: &mut bool,
) -> bool {
let mut ty_stalls = 0usize;
for var in &stalled_on.stalled_vars {
match *var {
TyOrConstInferVar::Ty(_) => ty_stalls += 1,
TyOrConstInferVar::TyInt(_)
| TyOrConstInferVar::TyFloat(_)
| TyOrConstInferVar::Const(_) => return false,
}
}
if ty_stalls > 1 {
*all_pending_single_ty_stall = false;
}
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> TraitEngine<'tcx, E> for FulfillmentCtxt<'tcx, E>
where
E: FromSolverError<'tcx, NextSolverError<'tcx>>,
Expand All @@ -172,10 +286,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 +309,14 @@ 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();
infcx.reset_ty_instantiated();
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 +409,7 @@ where
});
if overflowed {
self.obligations.on_fulfillment_overflow(infcx);
self.all_pending_trackable = false;
// Only return true errors that we have accumulated while processing.
return errors;
}
Expand All @@ -294,6 +419,8 @@ where
}
}

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

Expand Down
23 changes: 23 additions & 0 deletions tests/ui/traits/next-solver/fulfillment-skip-equate-two-var.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//@ check-pass
//
// Unifying two unknown vids can make a two-var goal progress
// (`T: Mirror<T>`). Fulfillment must still walk that goal; it
// must not treat unknown-unknown equate as a skip.

trait Mirror<T> {}
impl<T> Mirror<T> for T {}

fn assert_mirror<T: Mirror<U>, U>(_: T, _: U) {}

fn unify<T>(x: T, y: T) {}

pub fn check() {
let a = None;
let b = None;
assert_mirror(a, b);
unify(a, b);
}

fn main() {
check();
}
36 changes: 36 additions & 0 deletions tests/ui/traits/next-solver/fulfillment-skip-equate-unknowns.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//@ check-pass
//
// Each `push(Default::default())` equates a fresh infer vid with the
// vec element vid. That is unknown-unknown unification, not instantiate.
// Next-solver fulfillment may skip the pending-queue walk for single-var
// stalls in that case (rustc#159933). Instantiating with `u8` must still
// resolve the stalled `Default` goals.

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