Skip to content

Commit 7208a01

Browse files
committed
Turn quadratic time on number of impl blocks into linear time
Previously, if you had a lot of inherent impl blocks on a type like: struct Foo; impl Foo { fn foo_1() {} } ... impl Foo { fn foo_100_000() {} } The compiler would be very slow at processing it, because an internal algorithm would run in O(n^2), where n is the number of impl blocks. Now, we add a new algorithm that allocates but is faster asymptotically. If there is an overlap between multiple impl blocks in terms of identifiers, we still run a O(m^2) algorithm on groups of impl blocks that have overlaps, but that m refers to the size of the connected component, which is hopefully smaller than the n that refers to the sum of all connected components.
1 parent 9709ef1 commit 7208a01

File tree

1 file changed

+147
-5
lines changed

1 file changed

+147
-5
lines changed

compiler/rustc_typeck/src/coherence/inherent_impls_overlap.rs

+147-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
12
use rustc_errors::struct_span_err;
23
use rustc_hir as hir;
34
use rustc_hir::def_id::{CrateNum, DefId, LOCAL_CRATE};
45
use rustc_hir::itemlikevisit::ItemLikeVisitor;
56
use rustc_middle::ty::{self, TyCtxt};
7+
use rustc_span::Symbol;
68
use rustc_trait_selection::traits::{self, SkipLeakCheck};
79
use smallvec::SmallVec;
10+
use std::collections::hash_map::Entry;
811

912
pub fn crate_inherent_impls_overlap_check(tcx: TyCtxt<'_>, crate_num: CrateNum) {
1013
assert_eq!(crate_num, LOCAL_CRATE);
@@ -45,7 +48,7 @@ impl InherentOverlapChecker<'tcx> {
4548
false
4649
}
4750

48-
fn compare_hygienically(&self, item1: &'tcx ty::AssocItem, item2: &'tcx ty::AssocItem) -> bool {
51+
fn compare_hygienically(&self, item1: &ty::AssocItem, item2: &ty::AssocItem) -> bool {
4952
// Symbols and namespace match, compare hygienically.
5053
item1.kind.namespace() == item2.kind.namespace()
5154
&& item1.ident.normalize_to_macros_2_0() == item2.ident.normalize_to_macros_2_0()
@@ -134,10 +137,149 @@ impl ItemLikeVisitor<'v> for InherentOverlapChecker<'tcx> {
134137
.map(|impl_def_id| (impl_def_id, self.tcx.associated_items(*impl_def_id)))
135138
.collect::<SmallVec<[_; 8]>>();
136139

137-
for (i, &(&impl1_def_id, impl_items1)) in impls_items.iter().enumerate() {
138-
for &(&impl2_def_id, impl_items2) in &impls_items[(i + 1)..] {
139-
if self.impls_have_common_items(impl_items1, impl_items2) {
140-
self.check_for_overlapping_inherent_impls(impl1_def_id, impl2_def_id);
140+
// Perform a O(n^2) algorithm for small n,
141+
// otherwise switch to an allocating algorithm with
142+
// faster asymptotic runtime.
143+
if impls.len() < 30 {
144+
for (i, &(&impl1_def_id, impl_items1)) in impls_items.iter().enumerate() {
145+
for &(&impl2_def_id, impl_items2) in &impls_items[(i + 1)..] {
146+
if self.impls_have_common_items(impl_items1, impl_items2) {
147+
self.check_for_overlapping_inherent_impls(
148+
impl1_def_id,
149+
impl2_def_id,
150+
);
151+
}
152+
}
153+
}
154+
} else {
155+
// Build a set of connected regions of impl blocks.
156+
// Two impl blocks are regarded as connected if they share
157+
// an item with the same unhygienic identifier.
158+
// After we have assembled the connected regions,
159+
// run the O(n^2) algorithm on each connected region.
160+
// This is advantageous to running the algorithm over the
161+
// entire graph when there are many connected regions.
162+
163+
struct ConnectedRegion {
164+
idents: SmallVec<[Symbol; 8]>,
165+
impl_blocks: FxHashSet<usize>,
166+
}
167+
// Highest connected region id
168+
let mut highest_region_id = 0;
169+
let mut connected_region_ids = FxHashMap::default();
170+
let mut connected_regions = FxHashMap::default();
171+
172+
for (i, &(&_impl_def_id, impl_items)) in impls_items.iter().enumerate() {
173+
if impl_items.len() == 0 {
174+
continue;
175+
}
176+
// First obtain a list of existing connected region ids
177+
let mut idents_to_add = SmallVec::<[Symbol; 8]>::new();
178+
let ids = impl_items
179+
.in_definition_order()
180+
.filter_map(|item| {
181+
let entry = connected_region_ids.entry(item.ident.name);
182+
if let Entry::Occupied(e) = &entry {
183+
Some(*e.get())
184+
} else {
185+
idents_to_add.push(item.ident.name);
186+
None
187+
}
188+
})
189+
.collect::<FxHashSet<usize>>();
190+
match ids.len() {
191+
0 | 1 => {
192+
let id_to_set = if ids.len() == 0 {
193+
// Create a new connected region
194+
let region = ConnectedRegion {
195+
idents: idents_to_add,
196+
impl_blocks: std::iter::once(i).collect(),
197+
};
198+
connected_regions.insert(highest_region_id, region);
199+
(highest_region_id, highest_region_id += 1).0
200+
} else {
201+
// Take the only id inside the list
202+
let id_to_set = *ids.iter().next().unwrap();
203+
let region = connected_regions.get_mut(&id_to_set).unwrap();
204+
region.impl_blocks.insert(i);
205+
region.idents.extend_from_slice(&idents_to_add);
206+
id_to_set
207+
};
208+
let (_id, region) = connected_regions.iter().next().unwrap();
209+
// Update the connected region ids
210+
for ident in region.idents.iter() {
211+
connected_region_ids.insert(*ident, id_to_set);
212+
}
213+
}
214+
_ => {
215+
// We have multiple connected regions to merge.
216+
// In the worst case this might add impl blocks
217+
// one by one and can thus be O(n^2) in the size
218+
// of the resulting final connected region, but
219+
// this is no issue as the final step to check
220+
// for overlaps runs in O(n^2) as well.
221+
222+
// Take the smallest id from the list
223+
let id_to_set = *ids.iter().min().unwrap();
224+
225+
// Sort the id list so that the algorithm is deterministic
226+
let mut ids = ids.into_iter().collect::<SmallVec<[_; 8]>>();
227+
ids.sort();
228+
229+
let mut region = connected_regions.remove(&id_to_set).unwrap();
230+
region.idents.extend_from_slice(&idents_to_add);
231+
region.impl_blocks.insert(i);
232+
233+
for &id in ids.iter() {
234+
if id == id_to_set {
235+
continue;
236+
}
237+
let r = connected_regions.remove(&id).unwrap();
238+
// Update the connected region ids
239+
for ident in r.idents.iter() {
240+
connected_region_ids.insert(*ident, id_to_set);
241+
}
242+
region.idents.extend_from_slice(&r.idents);
243+
region.impl_blocks.extend(r.impl_blocks);
244+
}
245+
connected_regions.insert(id_to_set, region);
246+
}
247+
}
248+
}
249+
250+
debug!(
251+
"churning through {} components (sum={}, avg={}, var={}, max={})",
252+
connected_regions.len(),
253+
impls.len(),
254+
impls.len() / connected_regions.len(),
255+
{
256+
let avg = impls.len() / connected_regions.len();
257+
let s = connected_regions
258+
.iter()
259+
.map(|r| r.1.impl_blocks.len() as isize - avg as isize)
260+
.map(|v| v.abs() as usize)
261+
.sum::<usize>();
262+
s / connected_regions.len()
263+
},
264+
connected_regions.iter().map(|r| r.1.impl_blocks.len()).max().unwrap()
265+
);
266+
// List of connected regions is built. Now, run the overlap check
267+
// for each pair of impl blocks in the same connected region.
268+
for (_id, region) in connected_regions.into_iter() {
269+
let mut impl_blocks =
270+
region.impl_blocks.into_iter().collect::<SmallVec<[_; 8]>>();
271+
impl_blocks.sort();
272+
for (i, &impl1_items_idx) in impl_blocks.iter().enumerate() {
273+
let &(&impl1_def_id, impl_items1) = &impls_items[impl1_items_idx];
274+
for &impl2_items_idx in impl_blocks[(i + 1)..].iter() {
275+
let &(&impl2_def_id, impl_items2) = &impls_items[impl2_items_idx];
276+
if self.impls_have_common_items(impl_items1, impl_items2) {
277+
self.check_for_overlapping_inherent_impls(
278+
impl1_def_id,
279+
impl2_def_id,
280+
);
281+
}
282+
}
141283
}
142284
}
143285
}

0 commit comments

Comments
 (0)