Skip to content

Commit b64656a

Browse files
committed
Only visit types once when walking the type tree
This fixes rust-lang#72408. Nested closures were resulting in exponential compilation time. As a performance optimization this change introduces MiniSet, which is a simple small storage optimized set.
1 parent 255ceeb commit b64656a

File tree

10 files changed

+193
-39
lines changed

10 files changed

+193
-39
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3733,6 +3733,7 @@ dependencies = [
37333733
name = "rustc_middle"
37343734
version = "0.0.0"
37353735
dependencies = [
3736+
"arrayvec",
37363737
"bitflags",
37373738
"chalk-ir",
37383739
"measureme",

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
}

compiler/rustc_middle/src/ty/walk.rs

+72-8
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,50 @@
33
44
use crate::ty;
55
use crate::ty::subst::{GenericArg, GenericArgKind};
6+
use arrayvec::ArrayVec;
7+
use rustc_data_structures::fx::FxHashSet;
68
use smallvec::{self, SmallVec};
9+
use std::hash::Hash;
10+
11+
/// Small-storage-optimized implementation of a set
12+
/// made specifically for walking type tree.
13+
///
14+
/// Stores elements in a small array up to a certain length
15+
/// and switches to `HashSet` when that lenght is exceeded.
16+
pub enum MiniSet<T> {
17+
Array(ArrayVec<[T; 8]>),
18+
Set(FxHashSet<T>),
19+
}
20+
21+
impl<T: Eq + Hash + Copy> MiniSet<T> {
22+
/// Creates an empty `MiniSet`.
23+
pub fn new() -> Self {
24+
MiniSet::Array(ArrayVec::new())
25+
}
26+
27+
/// Adds a value to the set.
28+
///
29+
/// If the set did not have this value present, true is returned.
30+
///
31+
/// If the set did have this value present, false is returned.
32+
pub fn insert(&mut self, elem: T) -> bool {
33+
match self {
34+
MiniSet::Array(array) => {
35+
if array.iter().any(|e| *e == elem) {
36+
false
37+
} else {
38+
if array.try_push(elem).is_err() {
39+
let mut set: FxHashSet<T> = array.iter().copied().collect();
40+
set.insert(elem);
41+
*self = MiniSet::Set(set);
42+
}
43+
true
44+
}
45+
}
46+
MiniSet::Set(set) => set.insert(elem),
47+
}
48+
}
49+
}
750

851
// The TypeWalker's stack is hot enough that it's worth going to some effort to
952
// avoid heap allocations.
@@ -12,11 +55,20 @@ type TypeWalkerStack<'tcx> = SmallVec<[GenericArg<'tcx>; 8]>;
1255
pub struct TypeWalker<'tcx> {
1356
stack: TypeWalkerStack<'tcx>,
1457
last_subtree: usize,
58+
visited: MiniSet<GenericArg<'tcx>>,
1559
}
1660

61+
/// An iterator for walking the type tree.
62+
///
63+
/// It's very easy to produce a deeply
64+
/// nested type tree with a lot of
65+
/// identical subtrees. In order to work efficiently
66+
/// in this situation walker only visits each type once.
67+
/// It maintains a set of visited types and
68+
/// skips any types that are already there.
1769
impl<'tcx> TypeWalker<'tcx> {
18-
pub fn new(root: GenericArg<'tcx>) -> TypeWalker<'tcx> {
19-
TypeWalker { stack: smallvec![root], last_subtree: 1 }
70+
pub fn new(root: GenericArg<'tcx>) -> Self {
71+
Self { stack: smallvec![root], last_subtree: 1, visited: MiniSet::new() }
2072
}
2173

2274
/// Skips the subtree corresponding to the last type
@@ -41,11 +93,15 @@ impl<'tcx> Iterator for TypeWalker<'tcx> {
4193

4294
fn next(&mut self) -> Option<GenericArg<'tcx>> {
4395
debug!("next(): stack={:?}", self.stack);
44-
let next = self.stack.pop()?;
45-
self.last_subtree = self.stack.len();
46-
push_inner(&mut self.stack, next);
47-
debug!("next: stack={:?}", self.stack);
48-
Some(next)
96+
loop {
97+
let next = self.stack.pop()?;
98+
self.last_subtree = self.stack.len();
99+
if self.visited.insert(next) {
100+
push_inner(&mut self.stack, next);
101+
debug!("next: stack={:?}", self.stack);
102+
return Some(next);
103+
}
104+
}
49105
}
50106
}
51107

@@ -67,9 +123,17 @@ impl GenericArg<'tcx> {
67123
/// Iterator that walks the immediate children of `self`. Hence
68124
/// `Foo<Bar<i32>, u32>` yields the sequence `[Bar<i32>, u32]`
69125
/// (but not `i32`, like `walk`).
70-
pub fn walk_shallow(self) -> impl Iterator<Item = GenericArg<'tcx>> {
126+
///
127+
/// Iterator only walks items once.
128+
/// It accepts visited set, updates it with all visited types
129+
/// and skips any types that are already there.
130+
pub fn walk_shallow(
131+
self,
132+
visited: &mut MiniSet<GenericArg<'tcx>>,
133+
) -> impl Iterator<Item = GenericArg<'tcx>> {
71134
let mut stack = SmallVec::new();
72135
push_inner(&mut stack, self);
136+
stack.retain(|a| visited.insert(*a));
73137
stack.into_iter()
74138
}
75139
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// build-pass
2+
3+
// Closures include captured types twice in a type tree.
4+
//
5+
// Wrapping one closure with another leads to doubling
6+
// the amount of types in the type tree.
7+
//
8+
// This test ensures that rust can handle
9+
// deeply nested type trees with a lot
10+
// of duplicated subtrees.
11+
12+
fn dup(f: impl Fn(i32) -> i32) -> impl Fn(i32) -> i32 {
13+
move |a| f(a * 2)
14+
}
15+
16+
fn main() {
17+
let f = |a| a;
18+
19+
let f = dup(f);
20+
let f = dup(f);
21+
let f = dup(f);
22+
let f = dup(f);
23+
let f = dup(f);
24+
25+
let f = dup(f);
26+
let f = dup(f);
27+
let f = dup(f);
28+
let f = dup(f);
29+
let f = dup(f);
30+
31+
let f = dup(f);
32+
let f = dup(f);
33+
let f = dup(f);
34+
let f = dup(f);
35+
let f = dup(f);
36+
37+
let f = dup(f);
38+
let f = dup(f);
39+
let f = dup(f);
40+
let f = dup(f);
41+
let f = dup(f);
42+
43+
// Compiler dies around here if it tries
44+
// to walk the tree exhaustively.
45+
46+
let f = dup(f);
47+
let f = dup(f);
48+
let f = dup(f);
49+
let f = dup(f);
50+
let f = dup(f);
51+
52+
let f = dup(f);
53+
let f = dup(f);
54+
let f = dup(f);
55+
let f = dup(f);
56+
let f = dup(f);
57+
58+
println!("Type size was at least {}", f(1));
59+
}

0 commit comments

Comments
 (0)