Skip to content

Commit 590b789

Browse files
committed
Only visit types once when walking the type tree
This fixes #72408. Nested closures were resulting in exponential compilation time.
1 parent 82911b3 commit 590b789

File tree

4 files changed

+118
-32
lines changed

4 files changed

+118
-32
lines changed

src/librustc_infer/infer/outlives/verify.rs

+14-12
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::infer::outlives::env::RegionBoundPairs;
22
use crate::infer::{GenericKind, VerifyBound};
33
use crate::traits;
44
use rustc_data_structures::captures::Captures;
5+
use rustc_data_structures::fx::FxHashSet;
56
use rustc_hir::def_id::DefId;
67
use rustc_middle::ty::subst::{GenericArg, GenericArgKind, InternalSubsts, Subst};
78
use rustc_middle::ty::{self, Ty, TyCtxt};
@@ -32,16 +33,17 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
3233
/// Returns a "verify bound" that encodes what we know about
3334
/// `generic` and the regions it outlives.
3435
pub fn generic_bound(&self, generic: GenericKind<'tcx>) -> VerifyBound<'tcx> {
36+
let mut visited = FxHashSet::default();
3537
match generic {
3638
GenericKind::Param(param_ty) => self.param_bound(param_ty),
37-
GenericKind::Projection(projection_ty) => self.projection_bound(projection_ty),
39+
GenericKind::Projection(projection_ty) => self.projection_bound(projection_ty, &mut visited),
3840
}
3941
}
4042

41-
fn type_bound(&self, ty: Ty<'tcx>) -> VerifyBound<'tcx> {
43+
fn type_bound(&self, ty: Ty<'tcx>, visited: &mut FxHashSet<GenericArg<'tcx>>) -> VerifyBound<'tcx> {
4244
match ty.kind {
4345
ty::Param(p) => self.param_bound(p),
44-
ty::Projection(data) => self.projection_bound(data),
46+
ty::Projection(data) => self.projection_bound(data, visited),
4547
ty::FnDef(_, substs) => {
4648
// HACK(eddyb) ignore lifetimes found shallowly in `substs`.
4749
// This is inconsistent with `ty::Adt` (including all substs),
@@ -51,9 +53,9 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
5153
let mut bounds = substs
5254
.iter()
5355
.filter_map(|&child| match child.unpack() {
54-
GenericArgKind::Type(ty) => Some(self.type_bound(ty)),
56+
GenericArgKind::Type(ty) => Some(self.type_bound(ty, visited)),
5557
GenericArgKind::Lifetime(_) => None,
56-
GenericArgKind::Const(_) => Some(self.recursive_bound(child)),
58+
GenericArgKind::Const(_) => Some(self.recursive_bound(child, visited)),
5759
})
5860
.filter(|bound| {
5961
// Remove bounds that must hold, since they are not interesting.
@@ -67,7 +69,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
6769
),
6870
}
6971
}
70-
_ => self.recursive_bound(ty.into()),
72+
_ => self.recursive_bound(ty.into(), visited),
7173
}
7274
}
7375

@@ -138,7 +140,7 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
138140
self.declared_projection_bounds_from_trait(projection_ty)
139141
}
140142

141-
pub fn projection_bound(&self, projection_ty: ty::ProjectionTy<'tcx>) -> VerifyBound<'tcx> {
143+
pub fn projection_bound(&self, projection_ty: ty::ProjectionTy<'tcx>, visited: &mut FxHashSet<GenericArg<'tcx>>) -> VerifyBound<'tcx> {
142144
debug!("projection_bound(projection_ty={:?})", projection_ty);
143145

144146
let projection_ty_as_ty =
@@ -167,21 +169,21 @@ impl<'cx, 'tcx> VerifyBoundCx<'cx, 'tcx> {
167169

168170
// see the extensive comment in projection_must_outlive
169171
let ty = self.tcx.mk_projection(projection_ty.item_def_id, projection_ty.substs);
170-
let recursive_bound = self.recursive_bound(ty.into());
172+
let recursive_bound = self.recursive_bound(ty.into(), visited);
171173

172174
VerifyBound::AnyBound(env_bounds.chain(trait_bounds).collect()).or(recursive_bound)
173175
}
174176

175-
fn recursive_bound(&self, parent: GenericArg<'tcx>) -> VerifyBound<'tcx> {
177+
fn recursive_bound(&self, parent: GenericArg<'tcx>, visited: &mut FxHashSet<GenericArg<'tcx>>) -> VerifyBound<'tcx> {
176178
let mut bounds = parent
177-
.walk_shallow()
179+
.walk_shallow(visited)
178180
.filter_map(|child| match child.unpack() {
179-
GenericArgKind::Type(ty) => Some(self.type_bound(ty)),
181+
GenericArgKind::Type(ty) => Some(self.type_bound(ty, visited)),
180182
GenericArgKind::Lifetime(lt) => {
181183
// Ignore late-bound regions.
182184
if !lt.is_late_bound() { Some(VerifyBound::OutlivedBy(lt)) } else { None }
183185
}
184-
GenericArgKind::Const(_) => Some(self.recursive_bound(child)),
186+
GenericArgKind::Const(_) => Some(self.recursive_bound(child, visited)),
185187
})
186188
.filter(|bound| {
187189
// Remove bounds that must hold, since they are not interesting.

src/librustc_middle/ty/outlives.rs

+16-12
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use crate::ty::subst::{GenericArg, GenericArgKind};
66
use crate::ty::{self, Ty, TyCtxt, TypeFoldable};
7+
use rustc_data_structures::fx::FxHashSet;
78
use smallvec::SmallVec;
89

910
#[derive(Debug)]
@@ -50,12 +51,13 @@ 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 = FxHashSet::default();
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(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Component<'tcx>; 4]>, visited: &mut FxHashSet<GenericArg<'tcx>>) {
5961
// Descend through the types, looking for the various "base"
6062
// components and collecting them into `out`. This is not written
6163
// with `collect()` because of the need to sometimes skip subtrees
@@ -73,31 +75,31 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
7375
for &child in substs {
7476
match child.unpack() {
7577
GenericArgKind::Type(ty) => {
76-
compute_components(tcx, ty, out);
78+
compute_components(tcx, ty, out, visited);
7779
}
7880
GenericArgKind::Lifetime(_) => {}
7981
GenericArgKind::Const(_) => {
80-
compute_components_recursive(tcx, child, out);
82+
compute_components_recursive(tcx, child, out, visited);
8183
}
8284
}
8385
}
8486
}
8587

8688
ty::Array(element, _) => {
8789
// Don't look into the len const as it doesn't affect regions
88-
compute_components(tcx, element, out);
90+
compute_components(tcx, element, out, visited);
8991
}
9092

9193
ty::Closure(_, ref substs) => {
9294
for upvar_ty in substs.as_closure().upvar_tys() {
93-
compute_components(tcx, upvar_ty, out);
95+
compute_components(tcx, upvar_ty, out, visited);
9496
}
9597
}
9698

9799
ty::Generator(_, ref substs, _) => {
98100
// Same as the closure case
99101
for upvar_ty in substs.as_generator().upvar_tys() {
100-
compute_components(tcx, upvar_ty, out);
102+
compute_components(tcx, upvar_ty, out, visited);
101103
}
102104

103105
// We ignore regions in the generator interior as we don't
@@ -135,7 +137,8 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
135137
// OutlivesProjectionComponents. Continue walking
136138
// through and constrain Pi.
137139
let mut subcomponents = smallvec![];
138-
compute_components_recursive(tcx, ty.into(), &mut subcomponents);
140+
let mut subvisited = FxHashSet::default();
141+
compute_components_recursive(tcx, ty.into(), &mut subcomponents, &mut subvisited);
139142
out.push(Component::EscapingProjection(subcomponents.into_iter().collect()));
140143
}
141144
}
@@ -177,7 +180,7 @@ fn compute_components(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, out: &mut SmallVec<[Compo
177180
// the "bound regions list". In our representation, no such
178181
// list is maintained explicitly, because bound regions
179182
// themselves can be readily identified.
180-
compute_components_recursive(tcx, ty.into(), out);
183+
compute_components_recursive(tcx, ty.into(), out, visited);
181184
}
182185
}
183186
}
@@ -186,11 +189,12 @@ fn compute_components_recursive(
186189
tcx: TyCtxt<'tcx>,
187190
parent: GenericArg<'tcx>,
188191
out: &mut SmallVec<[Component<'tcx>; 4]>,
192+
visited: &mut FxHashSet<GenericArg<'tcx>>,
189193
) {
190-
for child in parent.walk_shallow() {
194+
for child in parent.walk_shallow(visited) {
191195
match child.unpack() {
192196
GenericArgKind::Type(ty) => {
193-
compute_components(tcx, ty, out);
197+
compute_components(tcx, ty, out, visited);
194198
}
195199
GenericArgKind::Lifetime(lt) => {
196200
// Ignore late-bound regions.
@@ -199,7 +203,7 @@ fn compute_components_recursive(
199203
}
200204
}
201205
GenericArgKind::Const(_) => {
202-
compute_components_recursive(tcx, child, out);
206+
compute_components_recursive(tcx, child, out, visited);
203207
}
204208
}
205209
}

src/librustc_middle/ty/walk.rs

+29-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use crate::ty;
55
use crate::ty::subst::{GenericArg, GenericArgKind};
66
use smallvec::{self, SmallVec};
7+
use rustc_data_structures::fx::FxHashSet;
78

89
// The TypeWalker's stack is hot enough that it's worth going to some effort to
910
// avoid heap allocations.
@@ -12,11 +13,20 @@ type TypeWalkerStack<'tcx> = SmallVec<[GenericArg<'tcx>; 8]>;
1213
pub struct TypeWalker<'tcx> {
1314
stack: TypeWalkerStack<'tcx>,
1415
last_subtree: usize,
16+
visited: FxHashSet<GenericArg<'tcx>>,
1517
}
1618

19+
/// An iterator for walking the type tree.
20+
///
21+
/// It's very easy to produce a deeply
22+
/// nested type tree with a lot of
23+
/// identical subtrees. In order to work efficiently
24+
/// in this situation walker only visits each type once.
25+
/// It maintains a set of visited types and
26+
/// skips any types that are already there.
1727
impl<'tcx> TypeWalker<'tcx> {
18-
pub fn new(root: GenericArg<'tcx>) -> TypeWalker<'tcx> {
19-
TypeWalker { stack: smallvec![root], last_subtree: 1 }
28+
pub fn new(root: GenericArg<'tcx>) -> Self {
29+
Self { stack: smallvec![root], last_subtree: 1, visited: FxHashSet::default() }
2030
}
2131

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

4252
fn next(&mut self) -> Option<GenericArg<'tcx>> {
4353
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)
54+
loop {
55+
let next = self.stack.pop()?;
56+
self.last_subtree = self.stack.len();
57+
if self.visited.insert(next) {
58+
push_inner(&mut self.stack, next);
59+
debug!("next: stack={:?}", self.stack);
60+
return Some(next);
61+
}
62+
}
4963
}
5064
}
5165

@@ -67,9 +81,16 @@ impl GenericArg<'tcx> {
6781
/// Iterator that walks the immediate children of `self`. Hence
6882
/// `Foo<Bar<i32>, u32>` yields the sequence `[Bar<i32>, u32]`
6983
/// (but not `i32`, like `walk`).
70-
pub fn walk_shallow(self) -> impl Iterator<Item = GenericArg<'tcx>> {
84+
///
85+
/// Iterator only walks items once.
86+
/// It accepts visited set, updates it with all visited types
87+
/// and skips any types that are already there.
88+
pub fn walk_shallow(self, visited: &mut FxHashSet<GenericArg<'tcx>>) -> impl Iterator<Item = GenericArg<'tcx>> {
7189
let mut stack = SmallVec::new();
7290
push_inner(&mut stack, self);
91+
stack.retain(|a| {
92+
visited.insert(*a)
93+
});
7394
stack.into_iter()
7495
}
7596
}
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)