Auto merge of #122371 - oli-obk:visit_nested_body, r=tmiasko

Stop walking the bodies of statics for reachability, and evaluate them instead

cc `@saethlin` `@RalfJung`

cc #119214

This reuses the `DefIdVisitor` from `rustc_privacy`, because they basically try to do the same thing.

This PR's changes can probably be extended to constants, too, but let's tackle that separately, it's likely more involved.
This commit is contained in:
bors 2024-03-16 04:35:02 +00:00
commit c563f2ee79
7 changed files with 118 additions and 41 deletions

View File

@ -4400,6 +4400,7 @@ dependencies = [
"rustc_lexer", "rustc_lexer",
"rustc_macros", "rustc_macros",
"rustc_middle", "rustc_middle",
"rustc_privacy",
"rustc_session", "rustc_session",
"rustc_span", "rustc_span",
"rustc_target", "rustc_target",

View File

@ -18,6 +18,7 @@ rustc_index = { path = "../rustc_index" }
rustc_lexer = { path = "../rustc_lexer" } rustc_lexer = { path = "../rustc_lexer" }
rustc_macros = { path = "../rustc_macros" } rustc_macros = { path = "../rustc_macros" }
rustc_middle = { path = "../rustc_middle" } rustc_middle = { path = "../rustc_middle" }
rustc_privacy = { path = "../rustc_privacy" }
rustc_session = { path = "../rustc_session" } rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" } rustc_span = { path = "../rustc_span" }
rustc_target = { path = "../rustc_target" } rustc_target = { path = "../rustc_target" }

View File

@ -6,6 +6,7 @@
// reachable as well. // reachable as well.
use hir::def_id::LocalDefIdSet; use hir::def_id::LocalDefIdSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res}; use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::{DefId, LocalDefId};
@ -15,7 +16,8 @@ use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}
use rustc_middle::middle::privacy::{self, Level}; use rustc_middle::middle::privacy::{self, Level};
use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc}; use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc};
use rustc_middle::query::Providers; use rustc_middle::query::Providers;
use rustc_middle::ty::{self, TyCtxt}; use rustc_middle::ty::{self, ExistentialTraitRef, TyCtxt};
use rustc_privacy::DefIdVisitor;
use rustc_session::config::CrateType; use rustc_session::config::CrateType;
use rustc_target::spec::abi::Abi; use rustc_target::spec::abi::Abi;
@ -65,23 +67,8 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> {
_ => None, _ => None,
}; };
if let Some(res) = res if let Some(res) = res {
&& let Some(def_id) = res.opt_def_id().and_then(|el| el.as_local()) self.propagate_item(res);
{
if self.def_id_represents_local_inlined_item(def_id.to_def_id()) {
self.worklist.push(def_id);
} else {
match res {
// Reachable constants and reachable statics can have their contents inlined
// into other crates. Mark them as reachable and recurse into their body.
Res::Def(DefKind::Const | DefKind::AssocConst | DefKind::Static { .. }, _) => {
self.worklist.push(def_id);
}
_ => {
self.reachable_symbols.insert(def_id);
}
}
}
} }
intravisit::walk_expr(self, expr) intravisit::walk_expr(self, expr)
@ -198,17 +185,9 @@ impl<'tcx> ReachableContext<'tcx> {
hir::ItemKind::Const(_, _, init) => { hir::ItemKind::Const(_, _, init) => {
self.visit_nested_body(init); self.visit_nested_body(init);
} }
hir::ItemKind::Static(..) => {
// Reachable statics are inlined if read from another constant or static
// in other crates. Additionally anonymous nested statics may be created
// when evaluating a static, so preserve those, too.
hir::ItemKind::Static(_, _, init) => {
// FIXME(oli-obk): remove this body walking and instead walk the evaluated initializer
// to find nested items that end up in the final value instead of also marking symbols
// as reachable that are only needed for evaluation.
self.visit_nested_body(init);
if let Ok(alloc) = self.tcx.eval_static_initializer(item.owner_id.def_id) { if let Ok(alloc) = self.tcx.eval_static_initializer(item.owner_id.def_id) {
self.propagate_statics_from_alloc(item.owner_id.def_id, alloc); self.propagate_from_alloc(alloc);
} }
} }
@ -279,28 +258,89 @@ impl<'tcx> ReachableContext<'tcx> {
} }
} }
/// Finds anonymous nested statics created for nested allocations and adds them to `reachable_symbols`. /// Finds things to add to `reachable_symbols` within allocations.
fn propagate_statics_from_alloc(&mut self, root: LocalDefId, alloc: ConstAllocation<'tcx>) { /// In contrast to visit_nested_body this ignores things that were only needed to evaluate
/// the allocation.
fn propagate_from_alloc(&mut self, alloc: ConstAllocation<'tcx>) {
if !self.any_library { if !self.any_library {
return; return;
} }
for (_, prov) in alloc.0.provenance().ptrs().iter() { for (_, prov) in alloc.0.provenance().ptrs().iter() {
match self.tcx.global_alloc(prov.alloc_id()) { match self.tcx.global_alloc(prov.alloc_id()) {
GlobalAlloc::Static(def_id) => { GlobalAlloc::Static(def_id) => {
if let Some(def_id) = def_id.as_local() self.propagate_item(Res::Def(self.tcx.def_kind(def_id), def_id))
&& self.tcx.local_parent(def_id) == root }
// This is the main purpose of this function: add the def_id we find GlobalAlloc::Function(instance) => {
// to `reachable_symbols`. // Manually visit to actually see the instance's `DefId`. Type visitors won't see it
&& self.reachable_symbols.insert(def_id) self.propagate_item(Res::Def(
&& let Ok(alloc) = self.tcx.eval_static_initializer(def_id) self.tcx.def_kind(instance.def_id()),
{ instance.def_id(),
self.propagate_statics_from_alloc(root, alloc); ));
self.visit(instance.args);
}
GlobalAlloc::VTable(ty, trait_ref) => {
self.visit(ty);
// Manually visit to actually see the trait's `DefId`. Type visitors won't see it
if let Some(trait_ref) = trait_ref {
let ExistentialTraitRef { def_id, args } = trait_ref.skip_binder();
self.visit_def_id(def_id, "", &"");
self.visit(args);
} }
} }
GlobalAlloc::Function(_) | GlobalAlloc::VTable(_, _) | GlobalAlloc::Memory(_) => {} GlobalAlloc::Memory(alloc) => self.propagate_from_alloc(alloc),
} }
} }
} }
fn propagate_item(&mut self, res: Res) {
let Res::Def(kind, def_id) = res else { return };
let Some(def_id) = def_id.as_local() else { return };
match kind {
DefKind::Static { nested: true, .. } => {
// This is the main purpose of this function: add the def_id we find
// to `reachable_symbols`.
if self.reachable_symbols.insert(def_id) {
if let Ok(alloc) = self.tcx.eval_static_initializer(def_id) {
// This cannot cause infinite recursion, because we abort by inserting into the
// work list once we hit a normal static. Nested statics, even if they somehow
// become recursive, are also not infinitely recursing, because of the
// `reachable_symbols` check above.
// We still need to protect against stack overflow due to deeply nested statics.
ensure_sufficient_stack(|| self.propagate_from_alloc(alloc));
}
}
}
// Reachable constants and reachable statics can have their contents inlined
// into other crates. Mark them as reachable and recurse into their body.
DefKind::Const | DefKind::AssocConst | DefKind::Static { .. } => {
self.worklist.push(def_id);
}
_ => {
if self.def_id_represents_local_inlined_item(def_id.to_def_id()) {
self.worklist.push(def_id);
} else {
self.reachable_symbols.insert(def_id);
}
}
}
}
}
impl<'tcx> DefIdVisitor<'tcx> for ReachableContext<'tcx> {
type Result = ();
fn tcx(&self) -> TyCtxt<'tcx> {
self.tcx
}
fn visit_def_id(
&mut self,
def_id: DefId,
_kind: &str,
_descr: &dyn std::fmt::Display,
) -> Self::Result {
self.propagate_item(Res::Def(self.tcx.def_kind(def_id), def_id))
}
} }
fn check_item<'tcx>( fn check_item<'tcx>(

View File

@ -67,7 +67,7 @@ impl<'tcx> fmt::Display for LazyDefPathStr<'tcx> {
/// First, it doesn't have overridable `fn visit_trait_ref`, so we have to catch trait `DefId`s /// First, it doesn't have overridable `fn visit_trait_ref`, so we have to catch trait `DefId`s
/// manually. Second, it doesn't visit some type components like signatures of fn types, or traits /// manually. Second, it doesn't visit some type components like signatures of fn types, or traits
/// in `impl Trait`, see individual comments in `DefIdVisitorSkeleton::visit_ty`. /// in `impl Trait`, see individual comments in `DefIdVisitorSkeleton::visit_ty`.
trait DefIdVisitor<'tcx> { pub trait DefIdVisitor<'tcx> {
type Result: VisitorResult = (); type Result: VisitorResult = ();
const SHALLOW: bool = false; const SHALLOW: bool = false;
const SKIP_ASSOC_TYS: bool = false; const SKIP_ASSOC_TYS: bool = false;
@ -98,7 +98,7 @@ trait DefIdVisitor<'tcx> {
} }
} }
struct DefIdVisitorSkeleton<'v, 'tcx, V: ?Sized> { pub struct DefIdVisitorSkeleton<'v, 'tcx, V: ?Sized> {
def_id_visitor: &'v mut V, def_id_visitor: &'v mut V,
visited_opaque_tys: FxHashSet<DefId>, visited_opaque_tys: FxHashSet<DefId>,
dummy: PhantomData<TyCtxt<'tcx>>, dummy: PhantomData<TyCtxt<'tcx>>,

View File

@ -0,0 +1,10 @@
//! This test checks that we do not monomorphize functions that are only
//! used to evaluate static items, but never used in runtime code.
//@compile-flags: --crate-type=lib -Copt-level=0
const fn foo() {}
pub static FOO: () = foo();
// CHECK-NOT: define{{.*}}foo{{.*}}

View File

@ -1,6 +1,8 @@
pub static V: &u32 = &X; pub static V: &u32 = &X;
pub static F: fn() = f; pub static F: fn() = f;
pub static G: fn() = G0; pub static G: fn() = G0;
pub static H: &(dyn Fn() + Sync) = &h;
pub static I: fn() = Helper(j).mk();
static X: u32 = 42; static X: u32 = 42;
static G0: fn() = g; static G0: fn() = g;
@ -12,3 +14,22 @@ pub fn v() -> *const u32 {
fn f() {} fn f() {}
fn g() {} fn g() {}
fn h() {}
#[derive(Copy, Clone)]
struct Helper<T: Copy>(T);
impl<T: Copy + FnOnce()> Helper<T> {
const fn mk(self) -> fn() {
i::<T>
}
}
fn i<T: FnOnce()>() {
assert_eq!(std::mem::size_of::<T>(), 0);
// unsafe to work around the lack of a `Default` impl for function items
unsafe { (std::mem::transmute_copy::<(), T>(&()))() }
}
fn j() {}

View File

@ -6,6 +6,8 @@ extern crate static_init_aux as aux;
static V: &u32 = aux::V; static V: &u32 = aux::V;
static F: fn() = aux::F; static F: fn() = aux::F;
static G: fn() = aux::G; static G: fn() = aux::G;
static H: &(dyn Fn() + Sync) = aux::H;
static I: fn() = aux::I;
fn v() -> *const u32 { fn v() -> *const u32 {
V V
@ -15,4 +17,6 @@ fn main() {
assert_eq!(aux::v(), crate::v()); assert_eq!(aux::v(), crate::v());
F(); F();
G(); G();
H();
I();
} }