Skip to content

Commit d94f657

Browse files
committed
extend doc comment for reachability set computation
also extend the const fn reachability test
1 parent b7dcabe commit d94f657

File tree

3 files changed

+63
-24
lines changed

3 files changed

+63
-24
lines changed

compiler/rustc_passes/src/reachable.rs

+49-23
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
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
813
use hir::def_id::LocalDefIdSet;
914
use rustc_data_structures::stack::ensure_sufficient_stack;
@@ -21,7 +26,9 @@ use rustc_privacy::DefIdVisitor;
2126
use rustc_session::config::CrateType;
2227
use 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.
397421
fn 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() {

tests/ui/consts/auxiliary/issue-63226.rs

+12-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,24 @@ pub struct VTable{
22
state:extern "C" fn(),
33
}
44

5-
impl VTable{
5+
impl VTable {
66
pub const fn vtable()->&'static VTable{
77
Self::VTABLE
88
}
99

1010
const VTABLE: &'static VTable =
1111
&VTable{state};
12+
13+
pub const VTABLE2: &'static VTable =
14+
&VTable{state: state2};
1215
}
1316

17+
pub const VTABLE3: &'static VTable =
18+
&VTable{state: state3};
19+
20+
// Only referenced via a `pub const fn`, and yet reachable.
1421
extern "C" fn state() {}
22+
// Only referenced via a associated `pub const`, and yet reachable.
23+
extern "C" fn state2() {}
24+
// Only referenced via a free `pub const`, and yet reachable.
25+
extern "C" fn state3() {}

tests/ui/consts/issue-63226.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@
88
use issue_63226::VTable;
99

1010
static ICE_ICE:&'static VTable=VTable::vtable();
11+
static MORE_ICE:&'static VTable=VTable::VTABLE2;
12+
static MORE_ICE3:&'static VTable=issue_63226::VTABLE3;
1113

1214
fn main() {}

0 commit comments

Comments
 (0)