From 5a956727170262d8d2a618d72e54a21f9aa891f1 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 20 Sep 2024 13:55:26 +0000 Subject: [PATCH 1/8] very cool, 10/10 --- .../src/canonicalizer.rs | 244 ++++++++++-------- .../src/solve/eval_ctxt/mod.rs | 6 + 2 files changed, 141 insertions(+), 109 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/canonicalizer.rs b/compiler/rustc_next_trait_solver/src/canonicalizer.rs index 196ddeb244302..31e67b3d3a042 100644 --- a/compiler/rustc_next_trait_solver/src/canonicalizer.rs +++ b/compiler/rustc_next_trait_solver/src/canonicalizer.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; +use rustc_type_ir::data_structures::HashMap; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::visit::TypeVisitableExt; @@ -44,8 +45,12 @@ pub struct Canonicalizer<'a, D: SolverDelegate, I: Interner> { canonicalize_mode: CanonicalizeMode, variables: &'a mut Vec, + variable_lookup_table: HashMap, + primitive_var_infos: Vec>, binder_index: ty::DebruijnIndex, + + cache: HashMap<(ty::DebruijnIndex, I::Ty), I::Ty>, } impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { @@ -60,12 +65,14 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { canonicalize_mode, variables, + variable_lookup_table: Default::default(), primitive_var_infos: Vec::new(), binder_index: ty::INNERMOST, + + cache: Default::default(), }; let value = value.fold_with(&mut canonicalizer); - // FIXME: Restore these assertions. Should we uplift type flags? assert!(!value.has_infer(), "unexpected infer in {value:?}"); assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}"); @@ -75,6 +82,37 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { Canonical { defining_opaque_types, max_universe, variables, value } } + fn get_or_insert_bound_var( + &mut self, + arg: impl Into, + canonical_var_info: CanonicalVarInfo, + ) -> ty::BoundVar { + // FIXME: 16 is made up and arbitrary. We should look at some + // perf data here. + let arg = arg.into(); + let idx = if self.variables.len() > 16 { + if self.variable_lookup_table.is_empty() { + self.variable_lookup_table.extend(self.variables.iter().copied().zip(0..)); + } + + *self.variable_lookup_table.entry(arg).or_insert_with(|| { + let var = self.variables.len(); + self.variables.push(arg); + self.primitive_var_infos.push(canonical_var_info); + var + }) + } else { + self.variables.iter().position(|&v| v == arg).unwrap_or_else(|| { + let var = self.variables.len(); + self.variables.push(arg); + self.primitive_var_infos.push(canonical_var_info); + var + }) + }; + + ty::BoundVar::from(idx) + } + fn finalize(self) -> (ty::UniverseIndex, I::CanonicalVars) { let mut var_infos = self.primitive_var_infos; // See the rustc-dev-guide section about how we deal with universes @@ -124,8 +162,8 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { // - var_infos: [E0, U1, E2, U1, E1, E6, U6], curr_compressed_uv: 2, next_orig_uv: 6 // - var_infos: [E0, U1, E1, U1, E1, E3, U3], curr_compressed_uv: 2, next_orig_uv: - // - // This algorithm runs in `O(n²)` where `n` is the number of different universe - // indices in the input. This should be fine as `n` is expected to be small. + // This algorithm runs in `O(mn)` where `n` is the number of different universes and + // `m` the number of variables. This should be fine as both are expected to be small. let mut curr_compressed_uv = ty::UniverseIndex::ROOT; let mut existential_in_new_uv = None; let mut next_orig_uv = Some(ty::UniverseIndex::ROOT); @@ -185,14 +223,16 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { for var in var_infos.iter_mut() { // We simply put all regions from the input into the highest // compressed universe, so we only deal with them at the end. - if !var.is_region() && is_existential == var.is_existential() { - update_uv(var, orig_uv, is_existential) + if !var.is_region() { + if is_existential == var.is_existential() { + update_uv(var, orig_uv, is_existential) + } } } } } - // We uniquify regions and always put them into their own universe + // We put all regions into a separate universe. let mut first_region = true; for var in var_infos.iter_mut() { if var.is_region() { @@ -208,93 +248,8 @@ impl<'a, D: SolverDelegate, I: Interner> Canonicalizer<'a, D, I> { let var_infos = self.delegate.cx().mk_canonical_var_infos(&var_infos); (curr_compressed_uv, var_infos) } -} - -impl, I: Interner> TypeFolder for Canonicalizer<'_, D, I> { - fn cx(&self) -> I { - self.delegate.cx() - } - - fn fold_binder(&mut self, t: ty::Binder) -> ty::Binder - where - T: TypeFoldable, - { - self.binder_index.shift_in(1); - let t = t.super_fold_with(self); - self.binder_index.shift_out(1); - t - } - - fn fold_region(&mut self, r: I::Region) -> I::Region { - let kind = match r.kind() { - ty::ReBound(..) => return r, - - // We may encounter `ReStatic` in item signatures or the hidden type - // of an opaque. `ReErased` should only be encountered in the hidden - // type of an opaque for regions that are ignored for the purposes of - // captures. - // - // FIXME: We should investigate the perf implications of not uniquifying - // `ReErased`. We may be able to short-circuit registering region - // obligations if we encounter a `ReErased` on one side, for example. - ty::ReStatic | ty::ReErased | ty::ReError(_) => match self.canonicalize_mode { - CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), - CanonicalizeMode::Response { .. } => return r, - }, - - ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode { - CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), - CanonicalizeMode::Response { .. } => { - panic!("unexpected region in response: {r:?}") - } - }, - - ty::RePlaceholder(placeholder) => match self.canonicalize_mode { - // We canonicalize placeholder regions as existentials in query inputs. - CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), - CanonicalizeMode::Response { max_input_universe } => { - // If we have a placeholder region inside of a query, it must be from - // a new universe. - if max_input_universe.can_name(placeholder.universe()) { - panic!("new placeholder in universe {max_input_universe:?}: {r:?}"); - } - CanonicalVarKind::PlaceholderRegion(placeholder) - } - }, - - ty::ReVar(vid) => { - assert_eq!( - self.delegate.opportunistic_resolve_lt_var(vid), - r, - "region vid should have been resolved fully before canonicalization" - ); - match self.canonicalize_mode { - CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), - CanonicalizeMode::Response { .. } => { - CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap()) - } - } - } - }; - - let existing_bound_var = match self.canonicalize_mode { - CanonicalizeMode::Input => None, - CanonicalizeMode::Response { .. } => { - self.variables.iter().position(|&v| v == r.into()).map(ty::BoundVar::from) - } - }; - - let var = existing_bound_var.unwrap_or_else(|| { - let var = ty::BoundVar::from(self.variables.len()); - self.variables.push(r.into()); - self.primitive_var_infos.push(CanonicalVarInfo { kind }); - var - }); - Region::new_anon_bound(self.cx(), self.binder_index, var) - } - - fn fold_ty(&mut self, t: I::Ty) -> I::Ty { + fn cached_fold_ty(&mut self, t: I::Ty) -> I::Ty { let kind = match t.kind() { ty::Infer(i) => match i { ty::TyVar(vid) => { @@ -368,20 +323,98 @@ impl, I: Interner> TypeFolder for Canonicaliz | ty::Tuple(_) | ty::Alias(_, _) | ty::Bound(_, _) - | ty::Error(_) => return t.super_fold_with(self), + | ty::Error(_) => { + return t.super_fold_with(self); + } }; - let var = ty::BoundVar::from( - self.variables.iter().position(|&v| v == t.into()).unwrap_or_else(|| { - let var = self.variables.len(); - self.variables.push(t.into()); - self.primitive_var_infos.push(CanonicalVarInfo { kind }); - var - }), - ); + let var = self.get_or_insert_bound_var(t, CanonicalVarInfo { kind }); Ty::new_anon_bound(self.cx(), self.binder_index, var) } +} + +impl, I: Interner> TypeFolder for Canonicalizer<'_, D, I> { + fn cx(&self) -> I { + self.delegate.cx() + } + + fn fold_binder(&mut self, t: ty::Binder) -> ty::Binder + where + T: TypeFoldable, + { + self.binder_index.shift_in(1); + let t = t.super_fold_with(self); + self.binder_index.shift_out(1); + t + } + + fn fold_region(&mut self, r: I::Region) -> I::Region { + let kind = match r.kind() { + ty::ReBound(..) => return r, + + // We may encounter `ReStatic` in item signatures or the hidden type + // of an opaque. `ReErased` should only be encountered in the hidden + // type of an opaque for regions that are ignored for the purposes of + // captures. + // + // FIXME: We should investigate the perf implications of not uniquifying + // `ReErased`. We may be able to short-circuit registering region + // obligations if we encounter a `ReErased` on one side, for example. + ty::ReStatic | ty::ReErased | ty::ReError(_) => match self.canonicalize_mode { + CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Response { .. } => return r, + }, + + ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode { + CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Response { .. } => { + panic!("unexpected region in response: {r:?}") + } + }, + + ty::RePlaceholder(placeholder) => match self.canonicalize_mode { + // We canonicalize placeholder regions as existentials in query inputs. + CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Response { max_input_universe } => { + // If we have a placeholder region inside of a query, it must be from + // a new universe. + if max_input_universe.can_name(placeholder.universe()) { + panic!("new placeholder in universe {max_input_universe:?}: {r:?}"); + } + CanonicalVarKind::PlaceholderRegion(placeholder) + } + }, + + ty::ReVar(vid) => { + assert_eq!( + self.delegate.opportunistic_resolve_lt_var(vid), + r, + "region vid should have been resolved fully before canonicalization" + ); + match self.canonicalize_mode { + CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT), + CanonicalizeMode::Response { .. } => { + CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap()) + } + } + } + }; + + let var = self.get_or_insert_bound_var(r, CanonicalVarInfo { kind }); + + Region::new_anon_bound(self.cx(), self.binder_index, var) + } + + fn fold_ty(&mut self, t: I::Ty) -> I::Ty { + if let Some(&ty) = self.cache.get(&(self.binder_index, t)) { + ty + } else { + let res = self.cached_fold_ty(t); + assert!(self.cache.insert((self.binder_index, t), res).is_none()); + res + } + } fn fold_const(&mut self, c: I::Const) -> I::Const { let kind = match c.kind() { @@ -419,14 +452,7 @@ impl, I: Interner> TypeFolder for Canonicaliz | ty::ConstKind::Expr(_) => return c.super_fold_with(self), }; - let var = ty::BoundVar::from( - self.variables.iter().position(|&v| v == c.into()).unwrap_or_else(|| { - let var = self.variables.len(); - self.variables.push(c.into()); - self.primitive_var_infos.push(CanonicalVarInfo { kind }); - var - }), - ); + let var = self.get_or_insert_bound_var(c, CanonicalVarInfo { kind }); Const::new_anon_bound(self.cx(), self.binder_index, var) } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 12ad0724b5cb1..d792862427a93 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -362,6 +362,12 @@ where goal: Goal, ) -> Result<(NestedNormalizationGoals, bool, Certainty), NoSolution> { let (orig_values, canonical_goal) = self.canonicalize_goal(goal); + + let num_orig_values = orig_values.iter().filter(|v| v.as_region().is_none()).count(); + if num_orig_values >= 128 { + println!("bail due to too many orig_values: {num_orig_values}"); + return Ok((Default::default(), false, Certainty::overflow(false))); + } let mut goal_evaluation = self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind); let canonical_response = EvalCtxt::evaluate_canonical_goal( From a9ea89248c41fecf97cb077218266c1f31c02e55 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 25 Sep 2024 11:13:23 +0200 Subject: [PATCH 2/8] cache the world --- .../src/infer/relate/type_relating.rs | 15 +++++- compiler/rustc_infer/src/infer/resolve.rs | 15 ++++-- compiler/rustc_middle/src/ty/fold.rs | 17 +++++-- .../rustc_next_trait_solver/src/resolve.rs | 15 ++++-- .../src/solve/eval_ctxt/mod.rs | 51 ++++++++++++++----- .../overflow}/coherence-alias-hang.rs | 11 +++- 6 files changed, 97 insertions(+), 27 deletions(-) rename tests/ui/traits/{ => next-solver/overflow}/coherence-alias-hang.rs (57%) diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index a402a8009ff8d..6167beccd8a55 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -1,3 +1,4 @@ +use rustc_data_structures::fx::FxHashSet; use rustc_middle::traits::solve::Goal; use rustc_middle::ty::relate::{ Relate, RelateResult, TypeRelation, relate_args_invariantly, relate_args_with_variances, @@ -16,6 +17,7 @@ pub struct TypeRelating<'combine, 'a, 'tcx> { fields: &'combine mut CombineFields<'a, 'tcx>, structurally_relate_aliases: StructurallyRelateAliases, ambient_variance: ty::Variance, + cache: FxHashSet<(ty::Variance, Ty<'tcx>, Ty<'tcx>)>, } impl<'combine, 'infcx, 'tcx> TypeRelating<'combine, 'infcx, 'tcx> { @@ -24,7 +26,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(), + } } } @@ -78,6 +85,10 @@ impl<'tcx> TypeRelation> 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 { @@ -160,6 +171,8 @@ impl<'tcx> TypeRelation> for TypeRelating<'_, '_, 'tcx> { } } + assert!(self.cache.insert((self.ambient_variance, a, b))); + Ok(a) } diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs index 34625ffb77879..1a162c4dbcf93 100644 --- a/compiler/rustc_infer/src/infer/resolve.rs +++ b/compiler/rustc_infer/src/infer/resolve.rs @@ -1,3 +1,4 @@ +use rustc_data_structures::fx::FxHashMap; use rustc_middle::bug; use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::visit::TypeVisitableExt; @@ -15,12 +16,13 @@ use super::{FixupError, FixupResult, InferCtxt}; /// points for correctness. pub struct OpportunisticVarResolver<'a, 'tcx> { infcx: &'a InferCtxt<'tcx>, + cache: FxHashMap, Ty<'tcx>>, } impl<'a, 'tcx> OpportunisticVarResolver<'a, 'tcx> { #[inline] pub fn new(infcx: &'a InferCtxt<'tcx>) -> Self { - OpportunisticVarResolver { infcx } + OpportunisticVarResolver { infcx, cache: Default::default() } } } @@ -31,12 +33,19 @@ impl<'a, 'tcx> TypeFolder> for OpportunisticVarResolver<'a, 'tcx> { #[inline] fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - if !t.has_non_region_infer() { + if let Some(&ty) = self.cache.get(&t) { + return ty; + } + + let res = if !t.has_non_region_infer() { t // micro-optimize -- if there is nothing in this type that this fold affects... } else { let t = self.infcx.shallow_resolve(t); t.super_fold_with(self) - } + }; + + assert!(self.cache.insert(t, res).is_none()); + res } fn fold_const(&mut self, ct: Const<'tcx>) -> Const<'tcx> { diff --git a/compiler/rustc_middle/src/ty/fold.rs b/compiler/rustc_middle/src/ty/fold.rs index 2ee7497497aa9..c98e5c26cd71b 100644 --- a/compiler/rustc_middle/src/ty/fold.rs +++ b/compiler/rustc_middle/src/ty/fold.rs @@ -1,4 +1,4 @@ -use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_hir::def_id::DefId; pub use rustc_type_ir::fold::{ FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable, shift_region, shift_vars, @@ -164,11 +164,13 @@ struct BoundVarReplacer<'tcx, D> { current_index: ty::DebruijnIndex, delegate: D, + + cache: FxHashMap<(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() } } } @@ -191,7 +193,11 @@ where } fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - match *t.kind() { + if let Some(&ty) = self.cache.get(&(self.current_index, t)) { + return ty; + } + + let res = match *t.kind() { ty::Bound(debruijn, bound_ty) if debruijn == self.current_index => { let ty = self.delegate.replace_ty(bound_ty); debug_assert!(!ty.has_vars_bound_above(ty::INNERMOST)); @@ -199,7 +205,10 @@ where } _ if t.has_vars_bound_at_or_above(self.current_index) => t.super_fold_with(self), _ => t, - } + }; + + assert!(self.cache.insert((self.current_index, t), res).is_none()); + res } fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { diff --git a/compiler/rustc_next_trait_solver/src/resolve.rs b/compiler/rustc_next_trait_solver/src/resolve.rs index 132b7400300c5..3b97028dbabfd 100644 --- a/compiler/rustc_next_trait_solver/src/resolve.rs +++ b/compiler/rustc_next_trait_solver/src/resolve.rs @@ -1,3 +1,4 @@ +use rustc_type_ir::data_structures::HashMap; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::visit::TypeVisitableExt; @@ -15,11 +16,12 @@ where I: Interner, { delegate: &'a D, + cache: HashMap, } impl<'a, D: SolverDelegate> EagerResolver<'a, D> { pub fn new(delegate: &'a D) -> Self { - EagerResolver { delegate } + EagerResolver { delegate, cache: Default::default() } } } @@ -29,7 +31,11 @@ impl, I: Interner> TypeFolder for EagerResolv } fn fold_ty(&mut self, t: I::Ty) -> I::Ty { - match t.kind() { + if let Some(&ty) = self.cache.get(&t) { + return ty; + } + + let res = match t.kind() { ty::Infer(ty::TyVar(vid)) => { let resolved = self.delegate.opportunistic_resolve_ty_var(vid); if t != resolved && resolved.has_infer() { @@ -47,7 +53,10 @@ impl, I: Interner> TypeFolder for EagerResolv t } } - } + }; + + assert!(self.cache.insert(t, res).is_none()); + res } fn fold_region(&mut self, r: I::Region) -> I::Region { diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index d792862427a93..c6f37d7ea4149 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -3,7 +3,7 @@ use std::ops::ControlFlow; use derive_where::derive_where; #[cfg(feature = "nightly")] use rustc_macros::{HashStable_NoContext, TyDecodable, TyEncodable}; -use rustc_type_ir::data_structures::ensure_sufficient_stack; +use rustc_type_ir::data_structures::{HashMap, HashSet, ensure_sufficient_stack}; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::relate::Relate; @@ -585,18 +585,16 @@ where #[instrument(level = "trace", skip(self))] pub(super) fn add_normalizes_to_goal(&mut self, mut goal: Goal>) { - goal.predicate = goal - .predicate - .fold_with(&mut ReplaceAliasWithInfer { ecx: self, param_env: goal.param_env }); + goal.predicate = + goal.predicate.fold_with(&mut ReplaceAliasWithInfer::new(self, goal.param_env)); self.inspect.add_normalizes_to_goal(self.delegate, self.max_input_universe, goal); self.nested_goals.normalizes_to_goals.push(goal); } #[instrument(level = "debug", skip(self))] pub(super) fn add_goal(&mut self, source: GoalSource, mut goal: Goal) { - goal.predicate = goal - .predicate - .fold_with(&mut ReplaceAliasWithInfer { ecx: self, param_env: goal.param_env }); + goal.predicate = + goal.predicate.fold_with(&mut ReplaceAliasWithInfer::new(self, goal.param_env)); self.inspect.add_goal(self.delegate, self.max_input_universe, source, goal); self.nested_goals.goals.push((source, goal)); } @@ -660,6 +658,7 @@ where term: I::Term, universe_of_term: ty::UniverseIndex, delegate: &'a D, + cache: HashSet, } impl, I: Interner> ContainsTermOrNotNameable<'_, D, I> { @@ -677,6 +676,10 @@ where { type Result = ControlFlow<()>; fn visit_ty(&mut self, t: I::Ty) -> Self::Result { + if self.cache.contains(&t) { + return ControlFlow::Continue(()); + } + match t.kind() { ty::Infer(ty::TyVar(vid)) => { if let ty::TermKind::Ty(term) = self.term.kind() { @@ -689,17 +692,18 @@ where } } - self.check_nameable(self.delegate.universe_of_ty(vid).unwrap()) + self.check_nameable(self.delegate.universe_of_ty(vid).unwrap())?; } - ty::Placeholder(p) => self.check_nameable(p.universe()), + ty::Placeholder(p) => self.check_nameable(p.universe())?, _ => { if t.has_non_region_infer() || t.has_placeholders() { - t.super_visit_with(self) - } else { - ControlFlow::Continue(()) + t.super_visit_with(self)? } } } + + assert!(self.cache.insert(t)); + ControlFlow::Continue(()) } fn visit_const(&mut self, c: I::Const) -> Self::Result { @@ -734,6 +738,7 @@ where delegate: self.delegate, universe_of_term, term: goal.predicate.term, + cache: Default::default(), }; goal.predicate.alias.visit_with(&mut visitor).is_continue() && goal.param_env.visit_with(&mut visitor).is_continue() @@ -1021,6 +1026,17 @@ where { ecx: &'me mut EvalCtxt<'a, D>, param_env: I::ParamEnv, + cache: HashMap, +} + +impl<'me, 'a, D, I> ReplaceAliasWithInfer<'me, 'a, D, I> +where + D: SolverDelegate, + I: Interner, +{ + fn new(ecx: &'me mut EvalCtxt<'a, D>, param_env: I::ParamEnv) -> Self { + ReplaceAliasWithInfer { ecx, param_env, cache: Default::default() } + } } impl TypeFolder for ReplaceAliasWithInfer<'_, '_, D, I> @@ -1033,7 +1049,11 @@ where } fn fold_ty(&mut self, ty: I::Ty) -> I::Ty { - match ty.kind() { + if let Some(&entry) = self.cache.get(&ty) { + return entry; + } + + let res = match ty.kind() { ty::Alias(..) if !ty.has_escaping_bound_vars() => { let infer_ty = self.ecx.next_ty_infer(); let normalizes_to = ty::PredicateKind::AliasRelate( @@ -1048,7 +1068,10 @@ where infer_ty } _ => ty.super_fold_with(self), - } + }; + + assert!(self.cache.insert(ty, res).is_none()); + res } fn fold_const(&mut self, ct: I::Const) -> I::Const { diff --git a/tests/ui/traits/coherence-alias-hang.rs b/tests/ui/traits/next-solver/overflow/coherence-alias-hang.rs similarity index 57% rename from tests/ui/traits/coherence-alias-hang.rs rename to tests/ui/traits/next-solver/overflow/coherence-alias-hang.rs index c2b4d2e42d214..0d5f42231e4ad 100644 --- a/tests/ui/traits/coherence-alias-hang.rs +++ b/tests/ui/traits/next-solver/overflow/coherence-alias-hang.rs @@ -1,6 +1,8 @@ //@ check-pass -//@ revisions: current next -//[next]@ compile-flags: -Znext-solver +//@ revisions: ai_current ai_next ia_current ia_next ii_current ii_next +//@[ai_next] compile-flags: -Znext-solver +//@[ia_next] compile-flags: -Znext-solver +//@[ii_next] compile-flags: -Znext-solver // Regression test for nalgebra hang . @@ -15,7 +17,12 @@ trait Trait { type Assoc: ?Sized; } impl Trait for W { + #[cfg(any(ai_current, ai_next))] type Assoc = W>; + #[cfg(any(ia_current, ia_next))] + type Assoc = W, T::Assoc>; + #[cfg(any(ii_current, ii_next))] + type Assoc = W, Id>; } trait Overlap {} From a9dd08cbf0cde2bb9c6ad6c4a9ed99abca1fc6d5 Mon Sep 17 00:00:00 2001 From: lcnr Date: Wed, 25 Sep 2024 14:58:42 +0200 Subject: [PATCH 3/8] SsoHashMap --- .../src/infer/relate/type_relating.rs | 4 ++-- compiler/rustc_infer/src/infer/resolve.rs | 23 ++++++++----------- compiler/rustc_middle/src/ty/fold.rs | 5 ++-- .../rustc_next_trait_solver/src/resolve.rs | 4 ++-- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index 6167beccd8a55..d4ad9c383db36 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -1,4 +1,4 @@ -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::sso::SsoHashSet; use rustc_middle::traits::solve::Goal; use rustc_middle::ty::relate::{ Relate, RelateResult, TypeRelation, relate_args_invariantly, relate_args_with_variances, @@ -17,7 +17,7 @@ pub struct TypeRelating<'combine, 'a, 'tcx> { fields: &'combine mut CombineFields<'a, 'tcx>, structurally_relate_aliases: StructurallyRelateAliases, ambient_variance: ty::Variance, - cache: FxHashSet<(ty::Variance, Ty<'tcx>, Ty<'tcx>)>, + cache: SsoHashSet<(ty::Variance, Ty<'tcx>, Ty<'tcx>)>, } impl<'combine, 'infcx, 'tcx> TypeRelating<'combine, 'infcx, 'tcx> { diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs index 1a162c4dbcf93..b80406d197fa5 100644 --- a/compiler/rustc_infer/src/infer/resolve.rs +++ b/compiler/rustc_infer/src/infer/resolve.rs @@ -1,4 +1,4 @@ -use rustc_data_structures::fx::FxHashMap; +use rustc_data_structures::sso::SsoHashMap; use rustc_middle::bug; use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::visit::TypeVisitableExt; @@ -16,7 +16,7 @@ use super::{FixupError, FixupResult, InferCtxt}; /// points for correctness. pub struct OpportunisticVarResolver<'a, 'tcx> { infcx: &'a InferCtxt<'tcx>, - cache: FxHashMap, Ty<'tcx>>, + cache: SsoHashMap, Ty<'tcx>>, } impl<'a, 'tcx> OpportunisticVarResolver<'a, 'tcx> { @@ -33,19 +33,16 @@ impl<'a, 'tcx> TypeFolder> for OpportunisticVarResolver<'a, 'tcx> { #[inline] fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - if let Some(&ty) = self.cache.get(&t) { - return ty; - } - - let res = if !t.has_non_region_infer() { + 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) - }; - - assert!(self.cache.insert(t, res).is_none()); - res + let shallow = self.infcx.shallow_resolve(t); + let res = shallow.super_fold_with(self); + assert!(self.cache.insert(t, res).is_none()); + res + } } fn fold_const(&mut self, ct: Const<'tcx>) -> Const<'tcx> { diff --git a/compiler/rustc_middle/src/ty/fold.rs b/compiler/rustc_middle/src/ty/fold.rs index c98e5c26cd71b..6884366e8fb6a 100644 --- a/compiler/rustc_middle/src/ty/fold.rs +++ b/compiler/rustc_middle/src/ty/fold.rs @@ -1,4 +1,5 @@ -use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; +use rustc_data_structures::fx::FxIndexMap; +use rustc_data_structures::sso::SsoHashMap; use rustc_hir::def_id::DefId; pub use rustc_type_ir::fold::{ FallibleTypeFolder, TypeFoldable, TypeFolder, TypeSuperFoldable, shift_region, shift_vars, @@ -165,7 +166,7 @@ struct BoundVarReplacer<'tcx, D> { delegate: D, - cache: FxHashMap<(ty::DebruijnIndex, Ty<'tcx>), Ty<'tcx>>, + cache: SsoHashMap<(ty::DebruijnIndex, Ty<'tcx>), Ty<'tcx>>, } impl<'tcx, D: BoundVarReplacerDelegate<'tcx>> BoundVarReplacer<'tcx, D> { diff --git a/compiler/rustc_next_trait_solver/src/resolve.rs b/compiler/rustc_next_trait_solver/src/resolve.rs index 3b97028dbabfd..f0fd79f6e4810 100644 --- a/compiler/rustc_next_trait_solver/src/resolve.rs +++ b/compiler/rustc_next_trait_solver/src/resolve.rs @@ -1,4 +1,4 @@ -use rustc_type_ir::data_structures::HashMap; +use rustc_data_structures::sso::SsoHashMap; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::visit::TypeVisitableExt; @@ -16,7 +16,7 @@ where I: Interner, { delegate: &'a D, - cache: HashMap, + cache: SsoHashMap, } impl<'a, D: SolverDelegate> EagerResolver<'a, D> { From f666aac74b1de4ece7df352920dc38ceff666633 Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 26 Sep 2024 09:58:48 +0200 Subject: [PATCH 4/8] add regression tests --- .../coherence-alias-hang-with-region.rs | 30 ++++++++++++++++ .../next-solver/overflow/nalgebra-hang.rs | 35 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 tests/ui/traits/next-solver/overflow/coherence-alias-hang-with-region.rs create mode 100644 tests/ui/traits/next-solver/overflow/nalgebra-hang.rs diff --git a/tests/ui/traits/next-solver/overflow/coherence-alias-hang-with-region.rs b/tests/ui/traits/next-solver/overflow/coherence-alias-hang-with-region.rs new file mode 100644 index 0000000000000..4ade8a13ca914 --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/coherence-alias-hang-with-region.rs @@ -0,0 +1,30 @@ +//@ check-pass +//@ revisions: ai ia ii +//@ compile-flags: -Znext-solver=coherence + +// Regression test for nalgebra hang . + +#![feature(lazy_type_alias)] +#![allow(incomplete_features)] + +type Id = T; +trait NotImplemented {} + +struct W<'a, T: ?Sized, U: ?Sized>(&'a (), *const T, *const U); +trait Trait { + type Assoc: ?Sized; +} +impl<'a, T: ?Sized + Trait> Trait for W<'a, T, T> { + #[cfg(ai)] + type Assoc = W<'a, T::Assoc, Id>; + #[cfg(ia)] + type Assoc = W<'a, Id, T::Assoc>; + #[cfg(ii)] + type Assoc = W<'a, Id, Id>; +} + +trait Overlap {} +impl<'a, T: ?Sized> Overlap for W<'a, T, T> {} +impl Overlap for T {} + +fn main() {} diff --git a/tests/ui/traits/next-solver/overflow/nalgebra-hang.rs b/tests/ui/traits/next-solver/overflow/nalgebra-hang.rs new file mode 100644 index 0000000000000..4bc6039c57dfd --- /dev/null +++ b/tests/ui/traits/next-solver/overflow/nalgebra-hang.rs @@ -0,0 +1,35 @@ +//@ check-pass +//@ revisions: current next +//@[next] compile-flags: -Znext-solver + +// Regression test for nalgebra hang from +// https://github.com/rust-lang/rust/pull/130654#issuecomment-2365465354 +trait HasAlias {} + +struct Dummy; +trait DummyTrait { + type DummyType; +} +impl DummyTrait for Dummy { + type DummyType = T; +} +type AliasOf = ::DummyType; + +struct Matrix(T, S); +type OMatrix = Matrix>; + +impl HasAlias for OMatrix {} + +trait SimdValue { + type Element; +} +impl> SimdValue for OMatrix { + type Element = OMatrix; +} + +trait Unimplemented {} +pub trait MyFrom {} +impl MyFrom for T {} +impl> MyFrom for OMatrix {} + +fn main() {} From 95b9dc1beebab3e2ac2b7375be6f9b7f5b81a52f Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 26 Sep 2024 09:59:14 +0200 Subject: [PATCH 5/8] improve caching --- .../src/infer/relate/type_relating.rs | 6 +++-- .../rustc_next_trait_solver/src/resolve.rs | 22 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index d4ad9c383db36..aafa9cef09f73 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -85,7 +85,7 @@ impl<'tcx> TypeRelation> for TypeRelating<'_, '_, 'tcx> { let a = infcx.shallow_resolve(a); let b = infcx.shallow_resolve(b); - if self.cache.contains(&(self.ambient_variance, a, b)) { + if infcx.next_trait_solver() && self.cache.contains(&(self.ambient_variance, a, b)) { return Ok(a); } @@ -171,7 +171,9 @@ impl<'tcx> TypeRelation> for TypeRelating<'_, '_, 'tcx> { } } - assert!(self.cache.insert((self.ambient_variance, a, b))); + if infcx.next_trait_solver() { + assert!(self.cache.insert((self.ambient_variance, a, b))); + } Ok(a) } diff --git a/compiler/rustc_next_trait_solver/src/resolve.rs b/compiler/rustc_next_trait_solver/src/resolve.rs index f0fd79f6e4810..08baf66971bee 100644 --- a/compiler/rustc_next_trait_solver/src/resolve.rs +++ b/compiler/rustc_next_trait_solver/src/resolve.rs @@ -1,4 +1,4 @@ -use rustc_data_structures::sso::SsoHashMap; +use rustc_type_ir::data_structures::HashMap; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::visit::TypeVisitableExt; @@ -16,7 +16,7 @@ where I: Interner, { delegate: &'a D, - cache: SsoHashMap, + cache: HashMap, } impl<'a, D: SolverDelegate> EagerResolver<'a, D> { @@ -31,11 +31,7 @@ impl, I: Interner> TypeFolder for EagerResolv } fn fold_ty(&mut self, t: I::Ty) -> I::Ty { - if let Some(&ty) = self.cache.get(&t) { - return ty; - } - - let res = match t.kind() { + match t.kind() { ty::Infer(ty::TyVar(vid)) => { let resolved = self.delegate.opportunistic_resolve_ty_var(vid); if t != resolved && resolved.has_infer() { @@ -48,15 +44,17 @@ impl, I: Interner> TypeFolder for EagerResolv ty::Infer(ty::FloatVar(vid)) => self.delegate.opportunistic_resolve_float_var(vid), _ => { if t.has_infer() { - t.super_fold_with(self) + if let Some(&ty) = self.cache.get(&t) { + return ty; + } + let res = t.super_fold_with(self); + assert!(self.cache.insert(t, res).is_none()); + res } else { t } } - }; - - assert!(self.cache.insert(t, res).is_none()); - res + } } fn fold_region(&mut self, r: I::Region) -> I::Region { From 7bf456d5e30265c30b1a896681d2898604756125 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 27 Sep 2024 09:49:45 +0200 Subject: [PATCH 6/8] caching high hopes --- .../src/infer/relate/type_relating.rs | 10 ++- compiler/rustc_infer/src/infer/resolve.rs | 5 +- compiler/rustc_middle/src/ty/fold.rs | 25 +++---- .../rustc_next_trait_solver/src/resolve.rs | 6 +- .../src/solve/eval_ctxt/mod.rs | 26 +++---- .../src/data_structures/delayed_map.rs | 68 +++++++++++++++++++ .../mod.rs} | 3 + 7 files changed, 105 insertions(+), 38 deletions(-) create mode 100644 compiler/rustc_type_ir/src/data_structures/delayed_map.rs rename compiler/rustc_type_ir/src/{data_structures.rs => data_structures/mod.rs} (92%) diff --git a/compiler/rustc_infer/src/infer/relate/type_relating.rs b/compiler/rustc_infer/src/infer/relate/type_relating.rs index aafa9cef09f73..9b31f897e5645 100644 --- a/compiler/rustc_infer/src/infer/relate/type_relating.rs +++ b/compiler/rustc_infer/src/infer/relate/type_relating.rs @@ -1,10 +1,10 @@ -use rustc_data_structures::sso::SsoHashSet; use rustc_middle::traits::solve::Goal; use rustc_middle::ty::relate::{ Relate, RelateResult, TypeRelation, relate_args_invariantly, relate_args_with_variances, }; 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; @@ -17,7 +17,7 @@ pub struct TypeRelating<'combine, 'a, 'tcx> { fields: &'combine mut CombineFields<'a, 'tcx>, structurally_relate_aliases: StructurallyRelateAliases, ambient_variance: ty::Variance, - cache: SsoHashSet<(ty::Variance, Ty<'tcx>, Ty<'tcx>)>, + cache: DelayedSet<(ty::Variance, Ty<'tcx>, Ty<'tcx>)>, } impl<'combine, 'infcx, 'tcx> TypeRelating<'combine, 'infcx, 'tcx> { @@ -85,7 +85,7 @@ impl<'tcx> TypeRelation> for TypeRelating<'_, '_, 'tcx> { let a = infcx.shallow_resolve(a); let b = infcx.shallow_resolve(b); - if infcx.next_trait_solver() && self.cache.contains(&(self.ambient_variance, a, b)) { + if self.cache.contains(&(self.ambient_variance, a, b)) { return Ok(a); } @@ -171,9 +171,7 @@ impl<'tcx> TypeRelation> for TypeRelating<'_, '_, 'tcx> { } } - if infcx.next_trait_solver() { - assert!(self.cache.insert((self.ambient_variance, a, b))); - } + assert!(self.cache.insert((self.ambient_variance, a, b))); Ok(a) } diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs index b80406d197fa5..0698b3616f2e7 100644 --- a/compiler/rustc_infer/src/infer/resolve.rs +++ b/compiler/rustc_infer/src/infer/resolve.rs @@ -3,6 +3,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}; @@ -16,7 +17,7 @@ use super::{FixupError, FixupResult, InferCtxt}; /// points for correctness. pub struct OpportunisticVarResolver<'a, 'tcx> { infcx: &'a InferCtxt<'tcx>, - cache: SsoHashMap, Ty<'tcx>>, + cache: DelayedMap, Ty<'tcx>>, } impl<'a, 'tcx> OpportunisticVarResolver<'a, 'tcx> { @@ -40,7 +41,7 @@ impl<'a, 'tcx> TypeFolder> for OpportunisticVarResolver<'a, 'tcx> { } else { let shallow = self.infcx.shallow_resolve(t); let res = shallow.super_fold_with(self); - assert!(self.cache.insert(t, res).is_none()); + assert!(self.cache.insert(t, res)); res } } diff --git a/compiler/rustc_middle/src/ty/fold.rs b/compiler/rustc_middle/src/ty/fold.rs index 6884366e8fb6a..940366e0681e7 100644 --- a/compiler/rustc_middle/src/ty/fold.rs +++ b/compiler/rustc_middle/src/ty/fold.rs @@ -1,6 +1,6 @@ use rustc_data_structures::fx::FxIndexMap; -use rustc_data_structures::sso::SsoHashMap; 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, }; @@ -166,7 +166,7 @@ struct BoundVarReplacer<'tcx, D> { delegate: D, - cache: SsoHashMap<(ty::DebruijnIndex, Ty<'tcx>), Ty<'tcx>>, + cache: DelayedMap<(ty::DebruijnIndex, Ty<'tcx>), Ty<'tcx>>, } impl<'tcx, D: BoundVarReplacerDelegate<'tcx>> BoundVarReplacer<'tcx, D> { @@ -194,22 +194,23 @@ where } fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> { - if let Some(&ty) = self.cache.get(&(self.current_index, t)) { - return ty; - } - - let res = match *t.kind() { + match *t.kind() { ty::Bound(debruijn, bound_ty) if debruijn == self.current_index => { let ty = self.delegate.replace_ty(bound_ty); 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), - _ => t, - }; + _ if t.has_vars_bound_at_or_above(self.current_index) => { + if let Some(&ty) = self.cache.get(&(self.current_index, t)) { + return ty; + } - assert!(self.cache.insert((self.current_index, t), res).is_none()); - res + let res = t.super_fold_with(self); + assert!(self.cache.insert((self.current_index, t), res)); + res + } + _ => t, + } } fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> { diff --git a/compiler/rustc_next_trait_solver/src/resolve.rs b/compiler/rustc_next_trait_solver/src/resolve.rs index 08baf66971bee..a37066cec669f 100644 --- a/compiler/rustc_next_trait_solver/src/resolve.rs +++ b/compiler/rustc_next_trait_solver/src/resolve.rs @@ -1,4 +1,4 @@ -use rustc_type_ir::data_structures::HashMap; +use rustc_type_ir::data_structures::DelayedMap; use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable}; use rustc_type_ir::inherent::*; use rustc_type_ir::visit::TypeVisitableExt; @@ -16,7 +16,7 @@ where I: Interner, { delegate: &'a D, - cache: HashMap, + cache: DelayedMap, } impl<'a, D: SolverDelegate> EagerResolver<'a, D> { @@ -48,7 +48,7 @@ impl, I: Interner> TypeFolder for EagerResolv return ty; } let res = t.super_fold_with(self); - assert!(self.cache.insert(t, res).is_none()); + assert!(self.cache.insert(t, res)); res } else { t diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index c6f37d7ea4149..12b4b3cb3a9d8 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -362,12 +362,6 @@ where goal: Goal, ) -> Result<(NestedNormalizationGoals, bool, Certainty), NoSolution> { let (orig_values, canonical_goal) = self.canonicalize_goal(goal); - - let num_orig_values = orig_values.iter().filter(|v| v.as_region().is_none()).count(); - if num_orig_values >= 128 { - println!("bail due to too many orig_values: {num_orig_values}"); - return Ok((Default::default(), false, Certainty::overflow(false))); - } let mut goal_evaluation = self.inspect.new_goal_evaluation(goal, &orig_values, goal_evaluation_kind); let canonical_response = EvalCtxt::evaluate_canonical_goal( @@ -1049,11 +1043,7 @@ where } fn fold_ty(&mut self, ty: I::Ty) -> I::Ty { - if let Some(&entry) = self.cache.get(&ty) { - return entry; - } - - let res = match ty.kind() { + match ty.kind() { ty::Alias(..) if !ty.has_escaping_bound_vars() => { let infer_ty = self.ecx.next_ty_infer(); let normalizes_to = ty::PredicateKind::AliasRelate( @@ -1067,11 +1057,17 @@ where ); infer_ty } - _ => ty.super_fold_with(self), - }; + _ if ty.has_aliases() => { + if let Some(&entry) = self.cache.get(&ty) { + return entry; + } - assert!(self.cache.insert(ty, res).is_none()); - res + let res = ty.super_fold_with(self); + assert!(self.cache.insert(ty, res).is_none()); + res + } + _ => ty, + } } fn fold_const(&mut self, ct: I::Const) -> I::Const { diff --git a/compiler/rustc_type_ir/src/data_structures/delayed_map.rs b/compiler/rustc_type_ir/src/data_structures/delayed_map.rs new file mode 100644 index 0000000000000..fafbaf50ca938 --- /dev/null +++ b/compiler/rustc_type_ir/src/data_structures/delayed_map.rs @@ -0,0 +1,68 @@ +use std::hash::Hash; + +use crate::data_structures::{HashMap, HashSet}; + +const CACHE_CUTOFF: u32 = 16; + +/// A hashmap which only starts hashing after ignoring the first few inputs. +/// +/// This is used in type folders asin nearly all cases caching is not worth it +/// as nearly all folded types are tiny. However, there are very rare incredibly +/// large types for which caching is necessary to avoid hangs. +#[derive(Debug)] +pub struct DelayedMap { + cache: HashMap, + count: u32, +} + +impl Default for DelayedMap { + fn default() -> Self { + DelayedMap { cache: Default::default(), count: 0 } + } +} + +impl DelayedMap { + #[inline] + pub fn insert(&mut self, key: K, value: V) -> bool { + if self.count >= CACHE_CUTOFF { + self.cache.insert(key, value).is_none() + } else { + self.count += 1; + true + } + } + + #[inline] + pub fn get(&self, key: &K) -> Option<&V> { + self.cache.get(key) + } +} + +#[derive(Debug)] +pub struct DelayedSet { + cache: HashSet, + count: u32, +} + +impl Default for DelayedSet { + fn default() -> Self { + DelayedSet { cache: Default::default(), count: 0 } + } +} + +impl DelayedSet { + #[inline] + pub fn insert(&mut self, value: T) -> bool { + if self.count >= CACHE_CUTOFF { + self.cache.insert(value) + } else { + self.count += 1; + true + } + } + + #[inline] + pub fn contains(&self, value: &T) -> bool { + self.cache.contains(value) + } +} diff --git a/compiler/rustc_type_ir/src/data_structures.rs b/compiler/rustc_type_ir/src/data_structures/mod.rs similarity index 92% rename from compiler/rustc_type_ir/src/data_structures.rs rename to compiler/rustc_type_ir/src/data_structures/mod.rs index 96036e53b0abb..d9766d91845cb 100644 --- a/compiler/rustc_type_ir/src/data_structures.rs +++ b/compiler/rustc_type_ir/src/data_structures/mod.rs @@ -6,6 +6,8 @@ pub use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet}; pub type IndexMap = indexmap::IndexMap>; pub type IndexSet = indexmap::IndexSet>; +mod delayed_map; + #[cfg(feature = "nightly")] mod impl_ { pub use rustc_data_structures::sso::{SsoHashMap, SsoHashSet}; @@ -24,4 +26,5 @@ mod impl_ { } } +pub use delayed_map::{DelayedMap, DelayedSet}; pub use impl_::*; From f550e0f74e101823a618b97e478792837fecf5a8 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 27 Sep 2024 10:05:06 +0200 Subject: [PATCH 7/8] yikes --- compiler/rustc_infer/src/infer/resolve.rs | 1 - compiler/rustc_type_ir/src/data_structures/delayed_map.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_infer/src/infer/resolve.rs b/compiler/rustc_infer/src/infer/resolve.rs index 0698b3616f2e7..e55dc92babe9f 100644 --- a/compiler/rustc_infer/src/infer/resolve.rs +++ b/compiler/rustc_infer/src/infer/resolve.rs @@ -1,4 +1,3 @@ -use rustc_data_structures::sso::SsoHashMap; use rustc_middle::bug; use rustc_middle::ty::fold::{FallibleTypeFolder, TypeFolder, TypeSuperFoldable}; use rustc_middle::ty::visit::TypeVisitableExt; diff --git a/compiler/rustc_type_ir/src/data_structures/delayed_map.rs b/compiler/rustc_type_ir/src/data_structures/delayed_map.rs index fafbaf50ca938..757a3849baf97 100644 --- a/compiler/rustc_type_ir/src/data_structures/delayed_map.rs +++ b/compiler/rustc_type_ir/src/data_structures/delayed_map.rs @@ -6,7 +6,7 @@ const CACHE_CUTOFF: u32 = 16; /// A hashmap which only starts hashing after ignoring the first few inputs. /// -/// This is used in type folders asin nearly all cases caching is not worth it +/// This is used in type folders as in nearly all cases caching is not worth it /// as nearly all folded types are tiny. However, there are very rare incredibly /// large types for which caching is necessary to avoid hangs. #[derive(Debug)] From 999add5f36a309b953b0b8e3cb705b5c535fee93 Mon Sep 17 00:00:00 2001 From: lcnr Date: Fri, 27 Sep 2024 19:28:09 +0200 Subject: [PATCH 8/8] completely outline `DelayedMap` cache accesses also increase the cutoff --- .../src/data_structures/delayed_map.rs | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/compiler/rustc_type_ir/src/data_structures/delayed_map.rs b/compiler/rustc_type_ir/src/data_structures/delayed_map.rs index 757a3849baf97..bccd50d3a0b84 100644 --- a/compiler/rustc_type_ir/src/data_structures/delayed_map.rs +++ b/compiler/rustc_type_ir/src/data_structures/delayed_map.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use crate::data_structures::{HashMap, HashSet}; -const CACHE_CUTOFF: u32 = 16; +const CACHE_CUTOFF: u32 = 32; /// A hashmap which only starts hashing after ignoring the first few inputs. /// @@ -22,18 +22,35 @@ impl Default for DelayedMap { } impl DelayedMap { - #[inline] + #[inline(always)] pub fn insert(&mut self, key: K, value: V) -> bool { if self.count >= CACHE_CUTOFF { - self.cache.insert(key, value).is_none() + self.cold_insert(key, value) } else { self.count += 1; true } } - #[inline] + #[cold] + #[inline(never)] + fn cold_insert(&mut self, key: K, value: V) -> bool { + self.cache.insert(key, value).is_none() + } + + + #[inline(always)] pub fn get(&self, key: &K) -> Option<&V> { + if self.cache.is_empty() { + None + } else { + self.cold_get(key) + } + } + + #[cold] + #[inline(never)] + fn cold_get(&self, key: &K) -> Option<&V> { self.cache.get(key) } } @@ -51,18 +68,30 @@ impl Default for DelayedSet { } impl DelayedSet { - #[inline] + #[inline(always)] pub fn insert(&mut self, value: T) -> bool { if self.count >= CACHE_CUTOFF { - self.cache.insert(value) + self.cold_insert(value) } else { self.count += 1; true } } + #[cold] + #[inline(never)] + fn cold_insert(&mut self, value: T) -> bool { + self.cache.insert(value) + } + #[inline] pub fn contains(&self, value: &T) -> bool { + !self.cache.is_empty() && self.cold_contains(value) + } + + #[cold] + #[inline(never)] + fn cold_contains(&self, value: &T) -> bool { self.cache.contains(value) } }