1- // Finds items that are externally reachable, to determine which items
2- // need to have their metadata (and possibly their AST) serialized.
3- // All items that can be referred to through an exported name are
4- // reachable, and when a reachable thing is inline or generic, it
5- // makes all other generics or inline functions that it references
6- // reachable as well.
1+ //! Finds local items that are externally reachable, which means that other crates need access to
2+ //! their compiled machine code or their MIR.
3+ //!
4+ //! An item is "externally reachable" if it is relevant for other crates. This obviously includes
5+ //! all public items. However, some of these items cannot be compiled to machine code (because they
6+ //! are generic), and for some the machine code is not sufficient (because we want to cross-crate
7+ //! inline them). These items "need cross-crate MIR". When a reachable function `f` needs
8+ //! cross-crate MIR, then all the functions it calls also become reachable, as they will be
9+ //! necessary to use the MIR of `f` from another crate. Furthermore, an item can become "externally
10+ //! reachable" by having a `const`/`const fn` return a pointer to that item, so we also need to
11+ //! recurse into reachable `const`/`const fn`.
712
813use hir:: def_id:: LocalDefIdSet ;
914use rustc_data_structures:: stack:: ensure_sufficient_stack;
@@ -21,7 +26,9 @@ use rustc_privacy::DefIdVisitor;
2126use rustc_session:: config:: CrateType ;
2227use rustc_target:: spec:: abi:: Abi ;
2328
24- fn item_might_be_inlined ( tcx : TyCtxt < ' _ > , def_id : DefId ) -> bool {
29+ /// Determines whether this item is recursive for reachability. See `is_recursively_reachable_local`
30+ /// below for details.
31+ fn recursively_reachable ( tcx : TyCtxt < ' _ > , def_id : DefId ) -> bool {
2532 tcx. generics_of ( def_id) . requires_monomorphization ( tcx)
2633 || tcx. cross_crate_inlinable ( def_id)
2734 || tcx. is_const_fn ( def_id)
@@ -54,12 +61,20 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> {
5461 fn visit_expr ( & mut self , expr : & ' tcx hir:: Expr < ' tcx > ) {
5562 let res = match expr. kind {
5663 hir:: ExprKind :: Path ( ref qpath) => {
64+ // This covers fn ptr casts but also "non-method" calls.
5765 Some ( self . typeck_results ( ) . qpath_res ( qpath, expr. hir_id ) )
5866 }
59- hir:: ExprKind :: MethodCall ( ..) => self
60- . typeck_results ( )
61- . type_dependent_def ( expr. hir_id )
62- . map ( |( kind, def_id) | Res :: Def ( kind, def_id) ) ,
67+ hir:: ExprKind :: MethodCall ( ..) => {
68+ // Method calls don't involve a full "path", so we need to determine the callee
69+ // based on the receiver type.
70+ // If this is a method call on a generic type, we might not be able to find the
71+ // callee. That's why `reachable_set` also adds all potential callees for such
72+ // calls, i.e. all trait impl items, to the reachable set. So here we only worry
73+ // about the calls we can identify.
74+ self . typeck_results ( )
75+ . type_dependent_def ( expr. hir_id )
76+ . map ( |( kind, def_id) | Res :: Def ( kind, def_id) )
77+ }
6378 hir:: ExprKind :: Closure ( & hir:: Closure { def_id, .. } ) => {
6479 self . reachable_symbols . insert ( def_id) ;
6580 None
@@ -96,16 +111,24 @@ impl<'tcx> ReachableContext<'tcx> {
96111 . expect ( "`ReachableContext::typeck_results` called outside of body" )
97112 }
98113
99- // Returns true if the given def ID represents a local item that is
100- // eligible for inlining and false otherwise.
101- fn def_id_represents_local_inlined_item ( & self , def_id : DefId ) -> bool {
114+ /// Returns true if the given def ID represents a local item that is recursive for reachability,
115+ /// i.e. whether everything mentioned in here also needs to be considered reachable.
116+ ///
117+ /// There are two reasons why an item may be recursively reachable:
118+ /// - It needs cross-crate MIR (see the module-level doc comment above).
119+ /// - It is a `const` or `const fn`. This is *not* because we need the MIR to interpret them
120+ /// (MIR for const-eval and MIR for codegen is separate, and MIR for const-eval is always
121+ /// encoded). Instead, it is because `const fn` can create `fn()` pointers to other items
122+ /// which end up in the evaluated result of the constant and can then be called from other
123+ /// crates. Those items must be considered reachable.
124+ fn is_recursively_reachable_local ( & self , def_id : DefId ) -> bool {
102125 let Some ( def_id) = def_id. as_local ( ) else {
103126 return false ;
104127 } ;
105128
106129 match self . tcx . hir_node_by_def_id ( def_id) {
107130 Node :: Item ( item) => match item. kind {
108- hir:: ItemKind :: Fn ( ..) => item_might_be_inlined ( self . tcx , def_id. into ( ) ) ,
131+ hir:: ItemKind :: Fn ( ..) => recursively_reachable ( self . tcx , def_id. into ( ) ) ,
109132 _ => false ,
110133 } ,
111134 Node :: TraitItem ( trait_method) => match trait_method. kind {
@@ -117,7 +140,7 @@ impl<'tcx> ReachableContext<'tcx> {
117140 Node :: ImplItem ( impl_item) => match impl_item. kind {
118141 hir:: ImplItemKind :: Const ( ..) => true ,
119142 hir:: ImplItemKind :: Fn ( ..) => {
120- item_might_be_inlined ( self . tcx , impl_item. hir_id ( ) . owner . to_def_id ( ) )
143+ recursively_reachable ( self . tcx , impl_item. hir_id ( ) . owner . to_def_id ( ) )
121144 }
122145 hir:: ImplItemKind :: Type ( _) => false ,
123146 } ,
@@ -174,7 +197,7 @@ impl<'tcx> ReachableContext<'tcx> {
174197 Node :: Item ( item) => {
175198 match item. kind {
176199 hir:: ItemKind :: Fn ( .., body) => {
177- if item_might_be_inlined ( self . tcx , item. owner_id . into ( ) ) {
200+ if recursively_reachable ( self . tcx , item. owner_id . into ( ) ) {
178201 self . visit_nested_body ( body) ;
179202 }
180203 }
@@ -228,7 +251,7 @@ impl<'tcx> ReachableContext<'tcx> {
228251 self . visit_nested_body ( body) ;
229252 }
230253 hir:: ImplItemKind :: Fn ( _, body) => {
231- if item_might_be_inlined ( self . tcx , impl_item. hir_id ( ) . owner . to_def_id ( ) ) {
254+ if recursively_reachable ( self . tcx , impl_item. hir_id ( ) . owner . to_def_id ( ) ) {
232255 self . visit_nested_body ( body)
233256 }
234257 }
@@ -316,7 +339,7 @@ impl<'tcx> ReachableContext<'tcx> {
316339 self . worklist . push ( def_id) ;
317340 }
318341 _ => {
319- if self . def_id_represents_local_inlined_item ( def_id. to_def_id ( ) ) {
342+ if self . is_recursively_reachable_local ( def_id. to_def_id ( ) ) {
320343 self . worklist . push ( def_id) ;
321344 } else {
322345 self . reachable_symbols . insert ( def_id) ;
@@ -394,6 +417,7 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
394417 || codegen_attrs. flags . contains ( CodegenFnAttrFlags :: USED_LINKER )
395418}
396419
420+ /// See module-level doc comment above.
397421fn reachable_set ( tcx : TyCtxt < ' _ > , ( ) : ( ) ) -> LocalDefIdSet {
398422 let effective_visibilities = & tcx. effective_visibilities ( ( ) ) ;
399423
@@ -427,14 +451,16 @@ fn reachable_set(tcx: TyCtxt<'_>, (): ()) -> LocalDefIdSet {
427451 }
428452 }
429453 {
430- // Some methods from non-exported (completely private) trait impls still have to be
431- // reachable if they are called from inlinable code. Generally, it's not known until
432- // monomorphization if a specific trait impl item can be reachable or not. So, we
433- // conservatively mark all of them as reachable.
454+ // As explained above, we have to mark all functions called from reachable
455+ // `item_might_be_inlined` items as reachable. The issue is, when those functions are
456+ // generic and call a trait method, we have no idea where that call goes! So, we
457+ // conservatively mark all trait impl items as reachable.
434458 // FIXME: One possible strategy for pruning the reachable set is to avoid marking impl
435459 // items of non-exported traits (or maybe all local traits?) unless their respective
436460 // trait items are used from inlinable code through method call syntax or UFCS, or their
437461 // trait is a lang item.
462+ // (But if you implement this, don't forget to take into account that vtables can also
463+ // make trait methods reachable!)
438464 let crate_items = tcx. hir_crate_items ( ( ) ) ;
439465
440466 for id in crate_items. items ( ) {
0 commit comments