Skip to content

Commit fdc3405

Browse files
committed
Auto merge of #72412 - VFLashM:issue-72408-nested-closures-exponential, r=tmandry
Issue 72408 nested closures exponential This fixes #72408. Nested closures were resulting in exponential compilation time. This PR is enhancing asymptotic complexity, but also increasing the constant, so I would love to see perf run results.
2 parents 2c69266 + 3ccb1c3 commit fdc3405

22 files changed

+342
-80
lines changed

Cargo.lock

+2
Original file line numberDiff line numberDiff line change
@@ -3605,6 +3605,7 @@ dependencies = [
36053605
name = "rustc_infer"
36063606
version = "0.0.0"
36073607
dependencies = [
3608+
"arrayvec",
36083609
"rustc_ast",
36093610
"rustc_data_structures",
36103611
"rustc_errors",
@@ -3744,6 +3745,7 @@ dependencies = [
37443745
name = "rustc_middle"
37453746
version = "0.0.0"
37463747
dependencies = [
3748+
"arrayvec",
37473749
"bitflags",
37483750
"chalk-ir",
37493751
"measureme",

compiler/rustc_infer/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ rustc_serialize = { path = "../rustc_serialize" }
2121
rustc_span = { path = "../rustc_span" }
2222
rustc_target = { path = "../rustc_target" }
2323
smallvec = { version = "1.0", features = ["union", "may_dangle"] }
24+
arrayvec = { version = "0.5.1", default-features = false }
2425
rustc_ast = { path = "../rustc_ast" }

compiler/rustc_infer/src/infer/combine.rs

+71-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ use super::unify_key::replace_if_possible;
3131
use super::unify_key::{ConstVarValue, ConstVariableValue};
3232
use super::unify_key::{ConstVariableOrigin, ConstVariableOriginKind};
3333
use super::{InferCtxt, MiscVariable, TypeTrace};
34+
use arrayvec::ArrayVec;
35+
use rustc_data_structures::fx::FxHashMap;
36+
use std::hash::Hash;
3437

3538
use crate::traits::{Obligation, PredicateObligations};
3639

@@ -44,6 +47,63 @@ use rustc_middle::ty::{self, InferConst, ToPredicate, Ty, TyCtxt, TypeFoldable};
4447
use rustc_middle::ty::{IntType, UintType};
4548
use rustc_span::DUMMY_SP;
4649

50+
/// Small-storage-optimized implementation of a map
51+
/// made specifically for caching results.
52+
///
53+
/// Stores elements in a small array up to a certain length
54+
/// and switches to `HashMap` when that length is exceeded.
55+
enum MiniMap<K, V> {
56+
Array(ArrayVec<[(K, V); 8]>),
57+
Map(FxHashMap<K, V>),
58+
}
59+
60+
impl<K: Eq + Hash, V> MiniMap<K, V> {
61+
/// Creates an empty `MiniMap`.
62+
pub fn new() -> Self {
63+
MiniMap::Array(ArrayVec::new())
64+
}
65+
66+
/// Inserts or updates value in the map.
67+
pub fn insert(&mut self, key: K, value: V) {
68+
match self {
69+
MiniMap::Array(array) => {
70+
for pair in array.iter_mut() {
71+
if pair.0 == key {
72+
pair.1 = value;
73+
return;
74+
}
75+
}
76+
if let Err(error) = array.try_push((key, value)) {
77+
let mut map: FxHashMap<K, V> = array.drain(..).collect();
78+
let (key, value) = error.element();
79+
map.insert(key, value);
80+
*self = MiniMap::Map(map);
81+
}
82+
}
83+
MiniMap::Map(map) => {
84+
map.insert(key, value);
85+
}
86+
}
87+
}
88+
89+
/// Return value by key if any.
90+
pub fn get(&self, key: &K) -> Option<&V> {
91+
match self {
92+
MiniMap::Array(array) => {
93+
for pair in array {
94+
if pair.0 == *key {
95+
return Some(&pair.1);
96+
}
97+
}
98+
return None;
99+
}
100+
MiniMap::Map(map) => {
101+
return map.get(key);
102+
}
103+
}
104+
}
105+
}
106+
47107
#[derive(Clone)]
48108
pub struct CombineFields<'infcx, 'tcx> {
49109
pub infcx: &'infcx InferCtxt<'infcx, 'tcx>,
@@ -379,6 +439,7 @@ impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> {
379439
needs_wf: false,
380440
root_ty: ty,
381441
param_env: self.param_env,
442+
cache: MiniMap::new(),
382443
};
383444

384445
let ty = match generalize.relate(ty, ty) {
@@ -438,6 +499,8 @@ struct Generalizer<'cx, 'tcx> {
438499
root_ty: Ty<'tcx>,
439500

440501
param_env: ty::ParamEnv<'tcx>,
502+
503+
cache: MiniMap<Ty<'tcx>, RelateResult<'tcx, Ty<'tcx>>>,
441504
}
442505

443506
/// Result from a generalization operation. This includes
@@ -535,13 +598,16 @@ impl TypeRelation<'tcx> for Generalizer<'_, 'tcx> {
535598
fn tys(&mut self, t: Ty<'tcx>, t2: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> {
536599
assert_eq!(t, t2); // we are abusing TypeRelation here; both LHS and RHS ought to be ==
537600

601+
if let Some(result) = self.cache.get(&t) {
602+
return result.clone();
603+
}
538604
debug!("generalize: t={:?}", t);
539605

540606
// Check to see whether the type we are generalizing references
541607
// any other type variable related to `vid` via
542608
// subtyping. This is basically our "occurs check", preventing
543609
// us from creating infinitely sized types.
544-
match *t.kind() {
610+
let result = match *t.kind() {
545611
ty::Infer(ty::TyVar(vid)) => {
546612
let vid = self.infcx.inner.borrow_mut().type_variables().root_var(vid);
547613
let sub_vid = self.infcx.inner.borrow_mut().type_variables().sub_root_var(vid);
@@ -598,7 +664,10 @@ impl TypeRelation<'tcx> for Generalizer<'_, 'tcx> {
598664
Ok(t)
599665
}
600666
_ => relate::super_relate_tys(self, t, t),
601-
}
667+
};
668+
669+
self.cache.insert(t, result.clone());
670+
return result;
602671
}
603672

604673
fn regions(

compiler/rustc_infer/src/infer/outlives/verify.rs

+28-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::infer::{GenericKind, VerifyBound};
33
use rustc_data_structures::captures::Captures;
44
use rustc_hir::def_id::DefId;
55
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, Subst};
6+
use rustc_middle::ty::walk::MiniSet;
67
use rustc_middle::ty::{self, Ty, TyCtxt};
78

89
/// The `TypeOutlives` struct has the job of "lowering" a `T: 'a`
@@ -31,16 +32,23 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
3132
/// Returns a "verify bound" that encodes what we know about
3233
/// `generic` and the regions it outlives.
3334
pub fn generic_bound(&self, generic: GenericKind<'tcx>) -> VerifyBound<'tcx> {
35+
let mut visited = MiniSet::new();
3436
match generic {
3537
GenericKind::Param(param_ty) => self.param_bound(param_ty),
36-
GenericKind::Projection(projection_ty) => self.projection_bound(projection_ty),
38+
GenericKind::Projection(projection_ty) => {
39+
self.projection_bound(projection_ty, &mut visited)
40+
}
3741
}
3842
}
3943

40-
fn type_bound(&self, ty: Ty<'tcx>) -> VerifyBound<'tcx> {
44+
fn type_bound(
45+
&self,
46+
ty: Ty<'tcx>,
47+
visited: &mut MiniSet<GenericArg<'tcx>>,
48+
) -> VerifyBound<'tcx> {
4149
match *ty.kind() {
4250
ty::Param(p) => self.param_bound(p),
43-
ty::Projection(data) => self.projection_bound(data),
51+
ty::Projection(data) => self.projection_bound(data, visited),
4452
ty::FnDef(_, substs) => {
4553
// HACK(eddyb) ignore lifetimes found shallowly in `substs`.
4654
// This is inconsistent with `ty::Adt` (including all substs),
@@ -50,9 +58,9 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
5058
let mut bounds = substs
5159
.iter()
5260
.filter_map(|child| match child.unpack() {
53-
GenericArgKind::Type(ty) => Some(self.type_bound(ty)),
61+
GenericArgKind::Type(ty) => Some(self.type_bound(ty, visited)),
5462
GenericArgKind::Lifetime(_) => None,
55-
GenericArgKind::Const(_) => Some(self.recursive_bound(child)),
63+
GenericArgKind::Const(_) => Some(self.recursive_bound(child, visited)),
5664
})
5765
.filter(|bound| {
5866
// Remove bounds that must hold, since they are not interesting.
@@ -66,7 +74,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
6674
),
6775
}
6876
}
69-
_ => self.recursive_bound(ty.into()),
77+
_ => self.recursive_bound(ty.into(), visited),
7078
}
7179
}
7280

@@ -137,7 +145,11 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
137145
self.declared_projection_bounds_from_trait(projection_ty)
138146
}
139147

140-
pub fn projection_bound(&self, projection_ty: ty::ProjectionTy<'tcx>) -> VerifyBound<'tcx> {
148+
pub fn projection_bound(
149+
&self,
150+
projection_ty: ty::ProjectionTy<'tcx>,
151+
visited: &mut MiniSet<GenericArg<'tcx>>,
152+
) -> VerifyBound<'tcx> {
141153
debug!("projection_bound(projection_ty={:?})", projection_ty);
142154

143155
let projection_ty_as_ty =
@@ -166,21 +178,25 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
166178

167179
// see the extensive comment in projection_must_outlive
168180
let ty = self.tcx.mk_projection(projection_ty.item_def_id, projection_ty.substs);
169-
let recursive_bound = self.recursive_bound(ty.into());
181+
let recursive_bound = self.recursive_bound(ty.into(), visited);
170182

171183
VerifyBound::AnyBound(env_bounds.chain(trait_bounds).collect()).or(recursive_bound)
172184
}
173185

174-
fn recursive_bound(&self, parent: GenericArg<'tcx>) -> VerifyBound<'tcx> {
186+
fn recursive_bound(
187+
&self,
188+
parent: GenericArg<'tcx>,
189+
visited: &mut MiniSet<GenericArg<'tcx>>,
190+
) -> VerifyBound<'tcx> {
175191
let mut bounds = parent
176-
.walk_shallow()
192+
.walk_shallow(visited)
177193
.filter_map(|child| match child.unpack() {
178-
GenericArgKind::Type(ty) => Some(self.type_bound(ty)),
194+
GenericArgKind::Type(ty) => Some(self.type_bound(ty, visited)),
179195
GenericArgKind::Lifetime(lt) => {
180196
// Ignore late-bound regions.
181197
if !lt.is_late_bound() { Some(VerifyBound::OutlivedBy(lt)) } else { None }
182198
}
183-
GenericArgKind::Const(_) => Some(self.recursive_bound(child)),
199+
GenericArgKind::Const(_) => Some(self.recursive_bound(child, visited)),
184200
})
185201
.filter(|bound| {
186202
// Remove bounds that must hold, since they are not interesting.

compiler/rustc_middle/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ rustc_ast = { path = "../rustc_ast" }
2828
rustc_span = { path = "../rustc_span" }
2929
chalk-ir = "0.21.0"
3030
smallvec = { version = "1.0", features = ["union", "may_dangle"] }
31+
arrayvec = { version = "0.5.1", default-features = false }
3132
measureme = "0.7.1"
3233
rustc_session = { path = "../rustc_session" }

compiler/rustc_middle/src/ty/outlives.rs

+21-12
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// RFC for reference.
44

55
use crate::ty::subst::{GenericArg, GenericArgKind};
6+
use crate::ty::walk::MiniSet;
67
use crate::ty::{self, Ty, TyCtxt, TypeFoldable};
78
use smallvec::SmallVec;
89

@@ -50,12 +51,18 @@ impl<'tcx> TyCtxt<'tcx> {
5051
/// Push onto `out` all the things that must outlive `'a` for the condition
5152
/// `ty0: 'a` to hold. Note that `ty0` must be a **fully resolved type**.
5253
pub fn push_outlives_components(self, ty0: Ty<'tcx>, out: &mut SmallVec<[Component<'tcx>; 4]>) {
53-
compute_components(self, ty0, out);
54+
let mut visited = MiniSet::new();
55+
compute_components(self, ty0, out, &mut visited);
5456
debug!("components({:?}) = {:?}", ty0, out);
5557
}
5658
}
5759

58-
fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Component<'tcx>; 4]>) {
60+
fn compute_components(
61+
tcx: TyCtxt<'tcx>,
62+
ty: Ty<'tcx>,
63+
out: &mut SmallVec<[Component<'tcx>; 4]>,
64+
visited: &mut MiniSet<GenericArg<'tcx>>,
65+
) {
5966
// Descend through the types, looking for the various "base"
6067
// components and collecting them into `out`. This is not written
6168
// with `collect()` because of the need to sometimes skip subtrees
@@ -73,31 +80,31 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
7380
for child in substs {
7481
match child.unpack() {
7582
GenericArgKind::Type(ty) => {
76-
compute_components(tcx, ty, out);
83+
compute_components(tcx, ty, out, visited);
7784
}
7885
GenericArgKind::Lifetime(_) => {}
7986
GenericArgKind::Const(_) => {
80-
compute_components_recursive(tcx, child, out);
87+
compute_components_recursive(tcx, child, out, visited);
8188
}
8289
}
8390
}
8491
}
8592

8693
ty::Array(element, _) => {
8794
// Don't look into the len const as it doesn't affect regions
88-
compute_components(tcx, element, out);
95+
compute_components(tcx, element, out, visited);
8996
}
9097

9198
ty::Closure(_, ref substs) => {
9299
for upvar_ty in substs.as_closure().upvar_tys() {
93-
compute_components(tcx, upvar_ty, out);
100+
compute_components(tcx, upvar_ty, out, visited);
94101
}
95102
}
96103

97104
ty::Generator(_, ref substs, _) => {
98105
// Same as the closure case
99106
for upvar_ty in substs.as_generator().upvar_tys() {
100-
compute_components(tcx, upvar_ty, out);
107+
compute_components(tcx, upvar_ty, out, visited);
101108
}
102109

103110
// We ignore regions in the generator interior as we don't
@@ -135,7 +142,8 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
135142
// OutlivesProjectionComponents. Continue walking
136143
// through and constrain Pi.
137144
let mut subcomponents = smallvec![];
138-
compute_components_recursive(tcx, ty.into(), &mut subcomponents);
145+
let mut subvisited = MiniSet::new();
146+
compute_components_recursive(tcx, ty.into(), &mut subcomponents, &mut subvisited);
139147
out.push(Component::EscapingProjection(subcomponents.into_iter().collect()));
140148
}
141149
}
@@ -177,7 +185,7 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
177185
// the "bound regions list". In our representation, no such
178186
// list is maintained explicitly, because bound regions
179187
// themselves can be readily identified.
180-
compute_components_recursive(tcx, ty.into(), out);
188+
compute_components_recursive(tcx, ty.into(), out, visited);
181189
}
182190
}
183191
}
@@ -186,11 +194,12 @@ fn compute_components_recursive(
186194
tcx: TyCtxt<'tcx>,
187195
parent: GenericArg<'tcx>,
188196
out: &mut SmallVec<[Component<'tcx>; 4]>,
197+
visited: &mut MiniSet<GenericArg<'tcx>>,
189198
) {
190-
for child in parent.walk_shallow() {
199+
for child in parent.walk_shallow(visited) {
191200
match child.unpack() {
192201
GenericArgKind::Type(ty) => {
193-
compute_components(tcx, ty, out);
202+
compute_components(tcx, ty, out, visited);
194203
}
195204
GenericArgKind::Lifetime(lt) => {
196205
// Ignore late-bound regions.
@@ -199,7 +208,7 @@ fn compute_components_recursive(
199208
}
200209
}
201210
GenericArgKind::Const(_) => {
202-
compute_components_recursive(tcx, child, out);
211+
compute_components_recursive(tcx, child, out, visited);
203212
}
204213
}
205214
}

0 commit comments

Comments
 (0)