diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index b7135de08ba84..8634c536d30da 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -1,9 +1,15 @@ -// Finds items that are externally reachable, to determine which items -// need to have their metadata (and possibly their AST) serialized. -// All items that can be referred to through an exported name are -// reachable, and when a reachable thing is inline or generic, it -// makes all other generics or inline functions that it references -// reachable as well. +//! Finds local items that are externally reachable, to determine which items +//! need to have their metadata (and possibly their AST) serialized. +//! +//! This set is *not* transitively closed, i.e., in general the set only contains definitions that +//! can be reached *directly* via an exported name, not private functions that can only be reached +//! transitively. +//! +//! However, there's a catch: if an item is generic or cross-crate inlinable, then it will have its +//! code generated by some downstream crate. Now if that item calls private monomorphic +//! non-cross-crate-inlinable items, then those can be reached by the code generated by the +//! downstream create! Therefore, when a reachable thing is cross-crate inlinable or generic, it +//! makes all other functions that it references reachable as well. use hir::def_id::LocalDefIdSet; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -56,10 +62,16 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> { hir::ExprKind::Path(ref qpath) => { Some(self.typeck_results().qpath_res(qpath, expr.hir_id)) } - hir::ExprKind::MethodCall(..) => self + hir::ExprKind::MethodCall(..) => { + // If this is a method call on a generic type, we might not be able to find the + // callee. That's why `reachable_set` also adds all potential callees for such + // calls, i.e. all trait impl items, to the reachable set. So here we only worry + // about the calls we can identify. + self .typeck_results() .type_dependent_def(expr.hir_id) - .map(|(kind, def_id)| Res::Def(kind, def_id)), + .map(|(kind, def_id)| Res::Def(kind, def_id)) + }, hir::ExprKind::Closure(&hir::Closure { def_id, .. }) => { self.reachable_symbols.insert(def_id); None @@ -394,6 +406,7 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { || codegen_attrs.flags.contains(CodegenFnAttrFlags::USED_LINKER) } +/// See module-level doc comment above. fn reachable_set(tcx: TyCtxt<'_>, (): ()) -> LocalDefIdSet { let effective_visibilities = &tcx.effective_visibilities(()); @@ -427,10 +440,10 @@ fn reachable_set(tcx: TyCtxt<'_>, (): ()) -> LocalDefIdSet { } } { - // Some methods from non-exported (completely private) trait impls still have to be - // reachable if they are called from inlinable code. Generally, it's not known until - // monomorphization if a specific trait impl item can be reachable or not. So, we - // conservatively mark all of them as reachable. + // As explained above, we have to mark all functions called from reachable + // `item_might_be_inlined` items as reachable. The issue is, when those functions are + // generic and call a trait method, we have no idea where that call goes! So, we + // conservatively mark all trait impl items as reachable. // FIXME: One possible strategy for pruning the reachable set is to avoid marking impl // items of non-exported traits (or maybe all local traits?) unless their respective // trait items are used from inlinable code through method call syntax or UFCS, or their