Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from Mar 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Expand Up @@ -4400,6 +4400,7 @@ dependencies = [
"rustc_lexer",
"rustc_macros",
"rustc_middle",
"rustc_privacy",
"rustc_session",
"rustc_span",
"rustc_target",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_passes/Cargo.toml
Expand Up @@ -18,6 +18,7 @@ rustc_index = { path = "../rustc_index" }
rustc_lexer = { path = "../rustc_lexer" }
rustc_macros = { path = "../rustc_macros" }
rustc_middle = { path = "../rustc_middle" }
rustc_privacy = { path = "../rustc_privacy" }
rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
rustc_target = { path = "../rustc_target" }
Expand Down
118 changes: 79 additions & 39 deletions compiler/rustc_passes/src/reachable.rs
Expand Up @@ -6,6 +6,7 @@
// reachable as well.

use hir::def_id::LocalDefIdSet;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir as hir;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
Expand All @@ -15,7 +16,8 @@ use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}
use rustc_middle::middle::privacy::{self, Level};
use rustc_middle::mir::interpret::{ConstAllocation, GlobalAlloc};
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_target::spec::abi::Abi;

Expand Down Expand Up @@ -65,23 +67,8 @@ impl<'tcx> Visitor<'tcx> for ReachableContext<'tcx> {
_ => None,
};

if let Some(res) = res
&& let Some(def_id) = res.opt_def_id().and_then(|el| el.as_local())
{
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);
}
}
}
if let Some(res) = res {
self.propagate_item(res);
}

intravisit::walk_expr(self, expr)
Expand Down Expand Up @@ -201,17 +188,9 @@ impl<'tcx> ReachableContext<'tcx> {
hir::ItemKind::Const(_, _, init) => {
self.visit_nested_body(init);
}

// 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);
hir::ItemKind::Static(..) => {
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);
}
}

Expand Down Expand Up @@ -281,28 +260,89 @@ impl<'tcx> ReachableContext<'tcx> {
}
}

/// Finds anonymous nested statics created for nested allocations and adds them to `reachable_symbols`.
fn propagate_statics_from_alloc(&mut self, root: LocalDefId, alloc: ConstAllocation<'tcx>) {
/// Finds things to add to `reachable_symbols` within allocations.
/// 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 {
return;
}
for (_, prov) in alloc.0.provenance().ptrs().iter() {
match self.tcx.global_alloc(prov.alloc_id()) {
GlobalAlloc::Static(def_id) => {
if let Some(def_id) = def_id.as_local()
&& self.tcx.local_parent(def_id) == root
// This is the main purpose of this function: add the def_id we find
// to `reachable_symbols`.
&& self.reachable_symbols.insert(def_id)
&& let Ok(alloc) = self.tcx.eval_static_initializer(def_id)
{
self.propagate_statics_from_alloc(root, alloc);
self.propagate_item(Res::Def(self.tcx.def_kind(def_id), def_id))
}
GlobalAlloc::Function(instance) => {
// Manually visit to actually see the instance's `DefId`. Type visitors won't see it
self.propagate_item(Res::Def(
self.tcx.def_kind(instance.def_id()),
instance.def_id(),
));
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>(
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_privacy/src/lib.rs
Expand Up @@ -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
/// 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`.
trait DefIdVisitor<'tcx> {
pub trait DefIdVisitor<'tcx> {
type Result: VisitorResult = ();
const SHALLOW: bool = false;
const SKIP_ASSOC_TYS: bool = false;
Expand Down Expand Up @@ -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,
visited_opaque_tys: FxHashSet<DefId>,
dummy: PhantomData<TyCtxt<'tcx>>,
Expand Down
@@ -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{{.*}}
21 changes: 21 additions & 0 deletions tests/ui/cross-crate/auxiliary/static_init_aux.rs
@@ -1,6 +1,8 @@
pub static V: &u32 = &X;
pub static F: fn() = f;
pub static G: fn() = G0;
pub static H: &(dyn Fn() + Sync) = &h;
pub static I: fn() = Helper(j).mk();

static X: u32 = 42;
static G0: fn() = g;
Expand All @@ -12,3 +14,22 @@ pub fn v() -> *const u32 {
fn f() {}

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() {}
4 changes: 4 additions & 0 deletions tests/ui/cross-crate/static-init.rs
Expand Up @@ -6,6 +6,8 @@ extern crate static_init_aux as aux;
static V: &u32 = aux::V;
static F: fn() = aux::F;
static G: fn() = aux::G;
static H: &(dyn Fn() + Sync) = aux::H;
static I: fn() = aux::I;

fn v() -> *const u32 {
V
Expand All @@ -15,4 +17,6 @@ fn main() {
assert_eq!(aux::v(), crate::v());
F();
G();
H();
I();
}