Skip to content

Commit d3ebd59

Browse files
committed
Auto merge of #69072 - ecstatic-morse:associated-items, r=petrochenkov
O(log n) lookup of associated items by name Resolves #68957, in which compile time is quadratic in the number of associated items. This PR makes name lookup use binary search instead of a linear scan to improve its asymptotic performance. As a result, the pathological case from that issue now runs in 8 seconds on my local machine, as opposed to many minutes on the current stable. Currently, method resolution must do a linear scan through all associated items of a type to find one with a certain name. This PR changes the result of the `associated_items` query to a data structure that preserves the definition order of associated items (which is used, e.g., for the layout of trait object vtables) while adding an index of those items sorted by (unhygienic) name. When doing name lookup, we first find all items with the same `Symbol` using binary search, then run hygienic comparison to find the one we are looking for. Ideally, this would be implemented using an insertion-order preserving, hash-based multi-map, but one is not readily available. Someone who is more familiar with identifier hygiene could probably make this better by auditing the uses of the `AssociatedItems` interface. My goal was to preserve the current behavior exactly, even if it seemed strange (I left at least one FIXME to this effect). For example, some places use comparison with `ident.modern()` and some places use `tcx.hygienic_eq` which requires the `DefId` of the containing `impl`. I don't know whether those approaches are equivalent or which one should be preferred.
2 parents 2c462a2 + a0212ba commit d3ebd59

File tree

35 files changed

+498
-184
lines changed

35 files changed

+498
-184
lines changed

src/librustc/arena.rs

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ macro_rules! arena_types {
4848
[] item_local_set: rustc_hir::ItemLocalSet,
4949
[decode] mir_const_qualif: rustc_index::bit_set::BitSet<rustc::mir::Local>,
5050
[] trait_impls_of: rustc::ty::trait_def::TraitImpls,
51+
[] associated_items: rustc::ty::AssociatedItems,
5152
[] dropck_outlives:
5253
rustc::infer::canonical::Canonical<'tcx,
5354
rustc::infer::canonical::QueryResponse<'tcx,

src/librustc/query/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ rustc_queries! {
333333
query associated_item(_: DefId) -> ty::AssocItem {}
334334

335335
/// Collects the associated items defined on a trait or impl.
336-
query associated_items(key: DefId) -> &'tcx [ty::AssocItem] {
336+
query associated_items(key: DefId) -> &'tcx ty::AssociatedItems {
337337
desc { |tcx| "collecting associated items of {}", tcx.def_path_str(key) }
338338
}
339339

src/librustc/traits/specialization_graph.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ impl<'tcx> Node {
8181
}
8282

8383
/// Iterate over the items defined directly by the given (impl or trait) node.
84-
pub fn items(&self, tcx: TyCtxt<'tcx>) -> &'tcx [ty::AssocItem] {
85-
tcx.associated_items(self.def_id())
84+
pub fn items(&self, tcx: TyCtxt<'tcx>) -> impl 'tcx + Iterator<Item = &'tcx ty::AssocItem> {
85+
tcx.associated_items(self.def_id()).in_definition_order()
8686
}
8787

8888
/// Finds an associated item defined in this node.
@@ -99,7 +99,7 @@ impl<'tcx> Node {
9999
use crate::ty::AssocKind::*;
100100

101101
tcx.associated_items(self.def_id())
102-
.iter()
102+
.filter_by_name_unhygienic(trait_item_name.name)
103103
.find(move |impl_item| {
104104
match (trait_item_kind, impl_item.kind) {
105105
| (Const, Const)

src/librustc/ty/adjustment.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ impl<'tcx> OverloadedDeref<'tcx> {
122122
};
123123
let method_def_id = tcx
124124
.associated_items(trait_def_id.unwrap())
125-
.iter()
125+
.in_definition_order()
126126
.find(|m| m.kind == ty::AssocKind::Method)
127127
.unwrap()
128128
.def_id;

src/librustc/ty/instance.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ impl<'tcx> Instance<'tcx> {
337337
let fn_once = tcx.lang_items().fn_once_trait().unwrap();
338338
let call_once = tcx
339339
.associated_items(fn_once)
340-
.iter()
340+
.in_definition_order()
341341
.find(|it| it.kind == ty::AssocKind::Method)
342342
.unwrap()
343343
.def_id;

src/librustc/ty/mod.rs

+87-4
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ use rustc_attr as attr;
3030
use rustc_data_structures::captures::Captures;
3131
use rustc_data_structures::fx::FxHashMap;
3232
use rustc_data_structures::fx::FxIndexMap;
33+
use rustc_data_structures::sorted_map::SortedIndexMultiMap;
3334
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
3435
use rustc_data_structures::sync::{self, par_iter, Lrc, ParallelIterator};
3536
use rustc_hir as hir;
36-
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
37+
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Namespace, Res};
3738
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, CRATE_DEF_INDEX, LOCAL_CRATE};
3839
use rustc_hir::{Constness, GlobMap, Node, TraitMap};
3940
use rustc_index::vec::{Idx, IndexVec};
@@ -216,6 +217,13 @@ impl AssocKind {
216217
ty::AssocKind::Const => "associated constant",
217218
}
218219
}
220+
221+
pub fn namespace(&self) -> Namespace {
222+
match *self {
223+
ty::AssocKind::OpaqueTy | ty::AssocKind::Type => Namespace::TypeNS,
224+
ty::AssocKind::Const | ty::AssocKind::Method => Namespace::ValueNS,
225+
}
226+
}
219227
}
220228

221229
impl AssocItem {
@@ -257,6 +265,81 @@ impl AssocItem {
257265
}
258266
}
259267

268+
/// A list of `ty::AssocItem`s in definition order that allows for efficient lookup by name.
269+
///
270+
/// When doing lookup by name, we try to postpone hygienic comparison for as long as possible since
271+
/// it is relatively expensive. Instead, items are indexed by `Symbol` and hygienic comparison is
272+
/// done only on items with the same name.
273+
#[derive(Debug, Clone, PartialEq, HashStable)]
274+
pub struct AssociatedItems {
275+
items: SortedIndexMultiMap<u32, Symbol, ty::AssocItem>,
276+
}
277+
278+
impl AssociatedItems {
279+
/// Constructs an `AssociatedItems` map from a series of `ty::AssocItem`s in definition order.
280+
pub fn new(items_in_def_order: impl IntoIterator<Item = ty::AssocItem>) -> Self {
281+
let items = items_in_def_order.into_iter().map(|item| (item.ident.name, item)).collect();
282+
AssociatedItems { items }
283+
}
284+
285+
/// Returns a slice of associated items in the order they were defined.
286+
///
287+
/// New code should avoid relying on definition order. If you need a particular associated item
288+
/// for a known trait, make that trait a lang item instead of indexing this array.
289+
pub fn in_definition_order(&self) -> impl '_ + Iterator<Item = &ty::AssocItem> {
290+
self.items.iter().map(|(_, v)| v)
291+
}
292+
293+
/// Returns an iterator over all associated items with the given name, ignoring hygiene.
294+
pub fn filter_by_name_unhygienic(
295+
&self,
296+
name: Symbol,
297+
) -> impl '_ + Iterator<Item = &ty::AssocItem> {
298+
self.items.get_by_key(&name)
299+
}
300+
301+
/// Returns an iterator over all associated items with the given name.
302+
///
303+
/// Multiple items may have the same name if they are in different `Namespace`s. For example,
304+
/// an associated type can have the same name as a method. Use one of the `find_by_name_and_*`
305+
/// methods below if you know which item you are looking for.
306+
pub fn filter_by_name(
307+
&'a self,
308+
tcx: TyCtxt<'a>,
309+
ident: Ident,
310+
parent_def_id: DefId,
311+
) -> impl 'a + Iterator<Item = &'a ty::AssocItem> {
312+
self.filter_by_name_unhygienic(ident.name)
313+
.filter(move |item| tcx.hygienic_eq(ident, item.ident, parent_def_id))
314+
}
315+
316+
/// Returns the associated item with the given name and `AssocKind`, if one exists.
317+
pub fn find_by_name_and_kind(
318+
&self,
319+
tcx: TyCtxt<'_>,
320+
ident: Ident,
321+
kind: AssocKind,
322+
parent_def_id: DefId,
323+
) -> Option<&ty::AssocItem> {
324+
self.filter_by_name_unhygienic(ident.name)
325+
.filter(|item| item.kind == kind)
326+
.find(|item| tcx.hygienic_eq(ident, item.ident, parent_def_id))
327+
}
328+
329+
/// Returns the associated item with the given name in the given `Namespace`, if one exists.
330+
pub fn find_by_name_and_namespace(
331+
&self,
332+
tcx: TyCtxt<'_>,
333+
ident: Ident,
334+
ns: Namespace,
335+
parent_def_id: DefId,
336+
) -> Option<&ty::AssocItem> {
337+
self.filter_by_name_unhygienic(ident.name)
338+
.filter(|item| item.kind.namespace() == ns)
339+
.find(|item| tcx.hygienic_eq(ident, item.ident, parent_def_id))
340+
}
341+
}
342+
260343
#[derive(Clone, Debug, PartialEq, Eq, Copy, RustcEncodable, RustcDecodable, HashStable)]
261344
pub enum Visibility {
262345
/// Visible everywhere (including in other crates).
@@ -2731,14 +2814,14 @@ impl<'tcx> TyCtxt<'tcx> {
27312814
.for_each(|&body_id| f(self.hir().body_owner_def_id(body_id)));
27322815
}
27332816

2734-
pub fn provided_trait_methods(self, id: DefId) -> impl Iterator<Item = &'tcx AssocItem> {
2817+
pub fn provided_trait_methods(self, id: DefId) -> impl 'tcx + Iterator<Item = &'tcx AssocItem> {
27352818
self.associated_items(id)
2736-
.iter()
2819+
.in_definition_order()
27372820
.filter(|item| item.kind == AssocKind::Method && item.defaultness.has_value())
27382821
}
27392822

27402823
pub fn trait_relevant_for_never(self, did: DefId) -> bool {
2741-
self.associated_items(did).iter().any(|item| item.relevant_for_never())
2824+
self.associated_items(did).in_definition_order().any(|item| item.relevant_for_never())
27422825
}
27432826

27442827
pub fn opt_item_name(self, def_id: DefId) -> Option<Ident> {

src/librustc/ty/sty.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -1066,11 +1066,7 @@ impl<'tcx> ProjectionTy<'tcx> {
10661066
) -> ProjectionTy<'tcx> {
10671067
let item_def_id = tcx
10681068
.associated_items(trait_ref.def_id)
1069-
.iter()
1070-
.find(|item| {
1071-
item.kind == ty::AssocKind::Type
1072-
&& tcx.hygienic_eq(item_name, item.ident, trait_ref.def_id)
1073-
})
1069+
.find_by_name_and_kind(tcx, item_name, ty::AssocKind::Type, trait_ref.def_id)
10741070
.unwrap()
10751071
.def_id;
10761072

src/librustc/ty/util.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ impl<'tcx> TyCtxt<'tcx> {
357357
let mut dtor_did = None;
358358
let ty = self.type_of(adt_did);
359359
self.for_each_relevant_impl(drop_trait, ty, |impl_did| {
360-
if let Some(item) = self.associated_items(impl_did).first() {
360+
if let Some(item) = self.associated_items(impl_did).in_definition_order().nth(0) {
361361
if validate(self, impl_did).is_ok() {
362362
dtor_did = Some(item.def_id);
363363
}

src/librustc_data_structures/sorted_map.rs

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ use std::iter::FromIterator;
44
use std::mem;
55
use std::ops::{Bound, Index, IndexMut, RangeBounds};
66

7+
mod index_map;
8+
9+
pub use index_map::SortedIndexMultiMap;
10+
711
/// `SortedMap` is a data structure with similar characteristics as BTreeMap but
812
/// slightly different trade-offs: lookup, insertion, and removal are O(log(N))
913
/// and elements can be iterated in order cheaply.

0 commit comments

Comments
 (0)