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`.
7
12
8
13
use hir:: def_id:: LocalDefIdSet ;
9
14
use rustc_data_structures:: stack:: ensure_sufficient_stack;
@@ -21,7 +26,9 @@ use rustc_privacy::DefIdVisitor;
21
26
use rustc_session:: config:: CrateType ;
22
27
use rustc_target:: spec:: abi:: Abi ;
23
28
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 {
25
32
tcx. generics_of ( def_id) . requires_monomorphization ( tcx)
26
33
|| tcx. cross_crate_inlinable ( def_id)
27
34
|| tcx. is_const_fn ( def_id)
@@ -54,12 +61,20 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> {
54
61
fn visit_expr ( & mut self , expr : & ' tcx hir:: Expr < ' tcx > ) {
55
62
let res = match expr. kind {
56
63
hir:: ExprKind :: Path ( ref qpath) => {
64
+ // This covers fn ptr casts but also "non-method" calls.
57
65
Some ( self . typeck_results ( ) . qpath_res ( qpath, expr. hir_id ) )
58
66
}
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
+ }
63
78
hir:: ExprKind :: Closure ( & hir:: Closure { def_id, .. } ) => {
64
79
self . reachable_symbols . insert ( def_id) ;
65
80
None
@@ -96,16 +111,24 @@ impl<'tcx> ReachableContext<'tcx> {
96
111
. expect ( "`ReachableContext::typeck_results` called outside of body" )
97
112
}
98
113
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 {
102
125
let Some ( def_id) = def_id. as_local ( ) else {
103
126
return false ;
104
127
} ;
105
128
106
129
match self . tcx . hir_node_by_def_id ( def_id) {
107
130
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 ( ) ) ,
109
132
_ => false ,
110
133
} ,
111
134
Node :: TraitItem ( trait_method) => match trait_method. kind {
@@ -117,7 +140,7 @@ impl<'tcx> ReachableContext<'tcx> {
117
140
Node :: ImplItem ( impl_item) => match impl_item. kind {
118
141
hir:: ImplItemKind :: Const ( ..) => true ,
119
142
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 ( ) )
121
144
}
122
145
hir:: ImplItemKind :: Type ( _) => false ,
123
146
} ,
@@ -174,7 +197,7 @@ impl<'tcx> ReachableContext<'tcx> {
174
197
Node :: Item ( item) => {
175
198
match item. kind {
176
199
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 ( ) ) {
178
201
self . visit_nested_body ( body) ;
179
202
}
180
203
}
@@ -228,7 +251,7 @@ impl<'tcx> ReachableContext<'tcx> {
228
251
self . visit_nested_body ( body) ;
229
252
}
230
253
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 ( ) ) {
232
255
self . visit_nested_body ( body)
233
256
}
234
257
}
@@ -316,7 +339,7 @@ impl<'tcx> ReachableContext<'tcx> {
316
339
self . worklist . push ( def_id) ;
317
340
}
318
341
_ => {
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 ( ) ) {
320
343
self . worklist . push ( def_id) ;
321
344
} else {
322
345
self . reachable_symbols . insert ( def_id) ;
@@ -394,6 +417,7 @@ fn has_custom_linkage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
394
417
|| codegen_attrs. flags . contains ( CodegenFnAttrFlags :: USED_LINKER )
395
418
}
396
419
420
+ /// See module-level doc comment above.
397
421
fn reachable_set ( tcx : TyCtxt < ' _ > , ( ) : ( ) ) -> LocalDefIdSet {
398
422
let effective_visibilities = & tcx. effective_visibilities ( ( ) ) ;
399
423
@@ -427,14 +451,16 @@ fn reachable_set(tcx: TyCtxt<'_>, (): ()) -> LocalDefIdSet {
427
451
}
428
452
}
429
453
{
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.
434
458
// FIXME: One possible strategy for pruning the reachable set is to avoid marking impl
435
459
// items of non-exported traits (or maybe all local traits?) unless their respective
436
460
// trait items are used from inlinable code through method call syntax or UFCS, or their
437
461
// 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!)
438
464
let crate_items = tcx. hir_crate_items ( ( ) ) ;
439
465
440
466
for id in crate_items. items ( ) {
0 commit comments