Skip to content

Commit

Permalink
Auto merge of rust-lang#130821 - lcnr:nalgebra-hang-2, r=<try>
Browse files Browse the repository at this point in the history
add caching to most type folders

Fixes the new minimization of the hang in nalgebra and nalgebra itself :3

this is a bit iffy, especially the cache in `TypeRelating`. I believe all the caches are correct, but it definitely adds some non-local complexity in places. The first commit removes region uniquification, reintroducing the ICE from rust-lang/trait-system-refactor-initiative#27. This does not affect coherence and I would like to fix this by introducing OR-region constraints

r? `@compiler-errors`
  • Loading branch information
bors committed Sep 30, 2024
2 parents e9df22f + 908178a commit dbc40a5
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 133 deletions.
7 changes: 6 additions & 1 deletion compiler/rustc_infer/src/infer/relate/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,15 @@ use crate::traits::{Obligation, PredicateObligation};
#[derive(Clone)]
pub struct CombineFields<'infcx, 'tcx> {
pub infcx: &'infcx InferCtxt<'tcx>,
// Immutable fields
pub trace: TypeTrace<'tcx>,
pub param_env: ty::ParamEnv<'tcx>,
pub goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
pub define_opaque_types: DefineOpaqueTypes,
// Mutable fields
//
// Adding any additional field likely requires
// changes to the cache of `TypeRelating`.
pub goals: Vec<Goal<'tcx, ty::Predicate<'tcx>>>,
}

impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> {
Expand Down
41 changes: 40 additions & 1 deletion compiler/rustc_infer/src/infer/relate/type_relating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use rustc_middle::ty::relate::{
};
use rustc_middle::ty::{self, Ty, TyCtxt, TyVar};
use rustc_span::Span;
use rustc_type_ir::data_structures::DelayedSet;
use tracing::{debug, instrument};

use super::combine::CombineFields;
Expand All @@ -13,9 +14,36 @@ use crate::infer::{DefineOpaqueTypes, InferCtxt, SubregionOrigin};

/// Enforce that `a` is equal to or a subtype of `b`.
pub struct TypeRelating<'combine, 'a, 'tcx> {
// Partially mutable.
fields: &'combine mut CombineFields<'a, 'tcx>,

// Immutable fields.
structurally_relate_aliases: StructurallyRelateAliases,
ambient_variance: ty::Variance,

/// The cache has only tracks the `ambient_variance` as its the
/// only field which is mutable and which meaningfully changes
/// the result when relating types.
///
/// The cache does not track whether the state of the
/// `InferCtxt` has been changed or whether we've added any
/// obligations to `self.fields.goals`. Whether a goal is added
/// once or multiple times is not really meaningful.
///
/// Changes in the inference state may delay some type inference to
/// the next fulfillment loop. Given that this look is already
/// necessary, this is also not a meaningful change, consider
/// the following three relations:
/// ```text
/// Vec<?0> sub Vec<?1>
/// ?0 eq u32
/// Vec<?0> sub Vec<?1>
/// ```
/// Without a cache, `Vec<?0> sub Vec<?1>` would eagerly constrain
/// `?1` to `u32`. When using the cache entry from the first time
/// we've related these types, this only happens when later proving
/// the `Subtype(?0, ?1)` goal from the first relation.
cache: DelayedSet<(ty::Variance, Ty<'tcx>, Ty<'tcx>)>,
}

impl<'combine, 'infcx, 'tcx> TypeRelating<'combine, 'infcx, 'tcx> {
Expand All @@ -24,7 +52,12 @@ impl<'combine, 'infcx, 'tcx> TypeRelating<'combine, 'infcx, 'tcx> {
structurally_relate_aliases: StructurallyRelateAliases,
ambient_variance: ty::Variance,
) -> TypeRelating<'combine, 'infcx, 'tcx> {
TypeRelating { fields: f, structurally_relate_aliases, ambient_variance }
TypeRelating {
fields: f,
structurally_relate_aliases,
ambient_variance,
cache: Default::default(),
}
}
}

Expand Down Expand Up @@ -78,6 +111,10 @@ impl<'tcx> TypeRelation<TyCtxt<'tcx>> for TypeRelating<'_, '_, 'tcx> {
let a = infcx.shallow_resolve(a);
let b = infcx.shallow_resolve(b);

if self.cache.contains(&(self.ambient_variance, a, b)) {
return Ok(a);
}

match (a.kind(), b.kind()) {
(&ty::Infer(TyVar(a_id)), &ty::Infer(TyVar(b_id))) => {
match self.ambient_variance {
Expand Down Expand Up @@ -160,6 +197,8 @@ impl<'tcx> TypeRelation<TyCtxt<'tcx>> for TypeRelating<'_, '_, 'tcx> {
}
}

assert!(self.cache.insert((self.ambient_variance, a, b)));

Ok(a)
}

Expand Down
14 changes: 11 additions & 3 deletions compiler/rustc_infer/src/infer/resolve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use rustc_middle::bug;
use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFolder, TypeSuperFoldable};
use rustc_middle::ty::visit::TypeVisitableExt;
use rustc_middle::ty::{self, Const, InferConst, Ty, TyCtxt, TypeFoldable};
use rustc_type_ir::data_structures::DelayedMap;

use super::{FixupError, FixupResult, InferCtxt};

Expand All @@ -15,12 +16,15 @@ use super::{FixupError, FixupResult, InferCtxt};
/// points for correctness.
pub struct OpportunisticVarResolver<'a, 'tcx> {
infcx: &'a InferCtxt<'tcx>,
/// We're able to use a cache here as the folder does
/// not have any mutable state.
cache: DelayedMap<Ty<'tcx>, Ty<'tcx>>,
}

impl<'a, 'tcx> OpportunisticVarResolver<'a, 'tcx> {
#[inline]
pub fn new(infcx: &'a InferCtxt<'tcx>) -> Self {
OpportunisticVarResolver { infcx }
OpportunisticVarResolver { infcx, cache: Default::default() }
}
}

Expand All @@ -33,9 +37,13 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for OpportunisticVarResolver<'a, 'tcx> {
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
if !t.has_non_region_infer() {
t // micro-optimize -- if there is nothing in this type that this fold affects...
} else if let Some(&ty) = self.cache.get(&t) {
return ty;
} else {
let t = self.infcx.shallow_resolve(t);
t.super_fold_with(self)
let shallow = self.infcx.shallow_resolve(t);
let res = shallow.super_fold_with(self);
assert!(self.cache.insert(t, res));
res
}
}

Expand Down
24 changes: 22 additions & 2 deletions compiler/rustc_middle/src/ty/fold.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use rustc_data_structures::fx::FxIndexMap;
use rustc_hir::def_id::DefId;
use rustc_type_ir::data_structures::DelayedMap;
pub use rustc_type_ir::fold::{
FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable, shift_region, shift_vars,
};
Expand Down Expand Up @@ -131,12 +132,19 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for RegionFolder<'a, 'tcx> {
///////////////////////////////////////////////////////////////////////////
// Bound vars replacer

/// A delegate used when instantiating bound vars.
///
/// Any implementation must make sure that each bound variable always
/// gets mapped to the same result. `BoundVarReplacer` caches by using
/// a `DelayedMap` which does not cache the first few types it encounters.
pub trait BoundVarReplacerDelegate<'tcx> {
fn replace_region(&mut self, br: ty::BoundRegion) -> ty::Region<'tcx>;
fn replace_ty(&mut self, bt: ty::BoundTy) -> Ty<'tcx>;
fn replace_const(&mut self, bv: ty::BoundVar) -> ty::Const<'tcx>;
}

/// A simple delegate taking 3 mutable functions. It must not be observable
/// whether a function is used once or multiple times.
pub struct FnMutDelegate<'a, 'tcx> {
pub regions: &'a mut (dyn FnMut(ty::BoundRegion) -> ty::Region<'tcx> + 'a),
pub types: &'a mut (dyn FnMut(ty::BoundTy) -> Ty<'tcx> + 'a),
Expand Down Expand Up @@ -164,11 +172,15 @@ struct BoundVarReplacer<'tcx, D> {
current_index: ty::DebruijnIndex,

delegate: D,

/// This cache only tracks the `DebruijnIndex` and assumes that it does not matter
/// for the delegate whether its methods get used a single time or multiple times.
cache: DelayedMap<(ty::DebruijnIndex, Ty<'tcx>), Ty<'tcx>>,
}

impl<'tcx, D: BoundVarReplacerDelegate<'tcx>> BoundVarReplacer<'tcx, D> {
fn new(tcx: TyCtxt<'tcx>, delegate: D) -> Self {
BoundVarReplacer { tcx, current_index: ty::INNERMOST, delegate }
BoundVarReplacer { tcx, current_index: ty::INNERMOST, delegate, cache: Default::default() }
}
}

Expand Down Expand Up @@ -197,7 +209,15 @@ where
debug_assert!(!ty.has_vars_bound_above(ty::INNERMOST));
ty::fold::shift_vars(self.tcx, ty, self.current_index.as_u32())
}
_ if t.has_vars_bound_at_or_above(self.current_index) => t.super_fold_with(self),
_ if t.has_vars_bound_at_or_above(self.current_index) => {
if let Some(&ty) = self.cache.get(&(self.current_index, t)) {
return ty;
}

let res = t.super_fold_with(self);
assert!(self.cache.insert((self.current_index, t), res));
res
}
_ => t,
}
}
Expand Down
Loading

0 comments on commit dbc40a5

Please sign in to comment.