Skip to content

Commit

Permalink
extend doc comment for reachability set computation
Browse files Browse the repository at this point in the history
also extend the const fn reachability test
  • Loading branch information
RalfJung committed Mar 24, 2024
1 parent b7dcabe commit 3909adb
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 24 deletions.
72 changes: 49 additions & 23 deletions compiler/rustc_passes/src/reachable.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
// 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, which means that other crates need access to
//! their compiled machine code or their MIR.
//!
//! An item is "externally reachable" if it is relevant for other crates. This obviously includes
//! all public items. However, some of these items cannot be compiled to machine code (because they
//! are generic), and for some the machine code is not sufficient (because we want to cross-crate
//! inline them). These items "need cross-crate MIR". When a reachable function `f` needs
//! cross-crate MIR, then all the functions it calls also become reachable, as they will be
//! necessary to use the MIR of `f` from another crate. Furthermore, an item can become "externally
//! reachable" by having a `const`/`const fn` return a pointer to that item, so we also need to
//! recurse into reachable `const`/`const fn`.

use hir::def_id::LocalDefIdSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
Expand All @@ -21,7 +26,9 @@ use rustc_privacy::DefIdVisitor;
use rustc_session::config::CrateType;
use rustc_target::spec::abi::Abi;

fn item_might_be_inlined(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
/// Determines whether this item is recursive for reachability. See `is_recursively_reachable_local`
/// below for details.
fn recursively_reachable(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
tcx.generics_of(def_id).requires_monomorphization(tcx)
|| tcx.cross_crate_inlinable(def_id)
|| tcx.is_const_fn(def_id)
Expand Down Expand Up @@ -54,12 +61,20 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> {
fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) {
let res = match expr.kind {
hir::ExprKind::Path(ref qpath) => {
// This covers fn ptr casts but also "non-method" calls.
Some(self.typeck_results().qpath_res(qpath, expr.hir_id))
}
hir::ExprKind::MethodCall(..) => self
.typeck_results()
.type_dependent_def(expr.hir_id)
.map(|(kind, def_id)| Res::Def(kind, def_id)),
hir::ExprKind::MethodCall(..) => {
// Method calls don't involve a full "path", so we need to determine the callee
// based on the receiver type.
// 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))
}
hir::ExprKind::Closure(&hir::Closure { def_id, .. }) => {
self.reachable_symbols.insert(def_id);
None
Expand Down Expand Up @@ -96,16 +111,24 @@ impl<'tcx> ReachableContext<'tcx> {
.expect("`ReachableContext::typeck_results` called outside of body")
}

// Returns true if the given def ID represents a local item that is
// eligible for inlining and false otherwise.
fn def_id_represents_local_inlined_item(&self, def_id: DefId) -> bool {
/// Returns true if the given def ID represents a local item that is recursive for reachability,
/// i.e. whether everything mentioned in here also needs to be considered reachable.
///
/// There are two reasons why an item may be recursively reachable:
/// - It needs cross-crate MIR (see the module-level doc comment above).
/// - It is a `const` or `const fn`. This is *not* because we need the MIR to interpret them
/// (MIR for const-eval and MIR for codegen is separate, and MIR for const-eval is always
/// encoded). Instead, it is because `const fn` can create `fn()` pointers to other items
/// which end up in the evaluated result of the constant and can then be called from other
/// crates. Those items must be considered reachable.
fn is_recursively_reachable_local(&self, def_id: DefId) -> bool {
let Some(def_id) = def_id.as_local() else {
return false;
};

match self.tcx.hir_node_by_def_id(def_id) {
Node::Item(item) => match item.kind {
hir::ItemKind::Fn(..) => item_might_be_inlined(self.tcx, def_id.into()),
hir::ItemKind::Fn(..) => recursively_reachable(self.tcx, def_id.into()),
_ => false,
},
Node::TraitItem(trait_method) => match trait_method.kind {
Expand All @@ -117,7 +140,7 @@ impl<'tcx> ReachableContext<'tcx> {
Node::ImplItem(impl_item) => match impl_item.kind {
hir::ImplItemKind::Const(..) => true,
hir::ImplItemKind::Fn(..) => {
item_might_be_inlined(self.tcx, impl_item.hir_id().owner.to_def_id())
recursively_reachable(self.tcx, impl_item.hir_id().owner.to_def_id())
}
hir::ImplItemKind::Type(_) => false,
},
Expand Down Expand Up @@ -174,7 +197,7 @@ impl<'tcx> ReachableContext<'tcx> {
Node::Item(item) => {
match item.kind {
hir::ItemKind::Fn(.., body) => {
if item_might_be_inlined(self.tcx, item.owner_id.into()) {
if recursively_reachable(self.tcx, item.owner_id.into()) {
self.visit_nested_body(body);
}
}
Expand Down Expand Up @@ -228,7 +251,7 @@ impl<'tcx> ReachableContext<'tcx> {
self.visit_nested_body(body);
}
hir::ImplItemKind::Fn(_, body) => {
if item_might_be_inlined(self.tcx, impl_item.hir_id().owner.to_def_id()) {
if recursively_reachable(self.tcx, impl_item.hir_id().owner.to_def_id()) {
self.visit_nested_body(body)
}
}
Expand Down Expand Up @@ -316,7 +339,7 @@ impl<'tcx> ReachableContext<'tcx> {
self.worklist.push(def_id);
}
_ => {
if self.def_id_represents_local_inlined_item(def_id.to_def_id()) {
if self.is_recursively_reachable_local(def_id.to_def_id()) {
self.worklist.push(def_id);
} else {
self.reachable_symbols.insert(def_id);
Expand Down Expand Up @@ -394,6 +417,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(());

Expand Down Expand Up @@ -427,14 +451,16 @@ 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
// trait is a lang item.
// (But if you implement this, don't forget to take into account that vtables and also
// make trait methods reachable!)
let crate_items = tcx.hir_crate_items(());

for id in crate_items.items() {
Expand Down
13 changes: 12 additions & 1 deletion tests/ui/consts/auxiliary/issue-63226.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,24 @@ pub struct VTable{
state:extern "C" fn(),
}

impl VTable{
impl VTable {
pub const fn vtable()->&'static VTable{
Self::VTABLE
}

const VTABLE: &'static VTable =
&VTable{state};

pub const VTABLE2: &'static VTable =
&VTable{state: state2};
}

pub const VTABLE3: &'static VTable =
&VTable{state: state3};

// Only referenced via a `pub const fn`, and yet reachable.
extern "C" fn state() {}
// Only referenced via a associated `pub const`, and yet reachable.
extern "C" fn state2() {}
// Only referenced via a free `pub const`, and yet reachable.
extern "C" fn state3() {}
2 changes: 2 additions & 0 deletions tests/ui/consts/issue-63226.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@
use issue_63226::VTable;

static ICE_ICE:&'static VTable=VTable::vtable();
static MORE_ICE:&'static VTable=VTable::VTABLE2;
static MORE_ICE3:&'static VTable=issue_63226::VTABLE3;

fn main() {}

0 comments on commit 3909adb

Please sign in to comment.