about summary refs log tree commit diff
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2024-03-16 04:35:02 +0000
committerbors <bors@rust-lang.org>2024-03-16 04:35:02 +0000
commitc563f2ee799b285067124b516ce99f26063f8351 (patch)
tree91895e6faf0242cc634ab7041171ae4b603052fc
parentc03ea3dfd907e7dc6305ebf20c94f3a1f1d9fed7 (diff)
parent746e4eff263527ba8960552ea62db11ab9821644 (diff)
downloadrust-c563f2ee799b285067124b516ce99f26063f8351.tar.gz
rust-c563f2ee799b285067124b516ce99f26063f8351.zip
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.
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_passes/Cargo.toml1
-rw-r--r--compiler/rustc_passes/src/reachable.rs118
-rw-r--r--compiler/rustc_privacy/src/lib.rs4
-rw-r--r--tests/codegen/dont_codegen_private_const_fn_only_used_in_const_eval.rs10
-rw-r--r--tests/ui/cross-crate/auxiliary/static_init_aux.rs21
-rw-r--r--tests/ui/cross-crate/static-init.rs4
7 files changed, 118 insertions, 41 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 16aed3dc49c..94e2570ebaf 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4400,6 +4400,7 @@ dependencies = [
  "rustc_lexer",
  "rustc_macros",
  "rustc_middle",
+ "rustc_privacy",
  "rustc_session",
  "rustc_span",
  "rustc_target",
diff --git a/compiler/rustc_passes/Cargo.toml b/compiler/rustc_passes/Cargo.toml
index 6abfa08c530..b45a8dae277 100644
--- a/compiler/rustc_passes/Cargo.toml
+++ b/compiler/rustc_passes/Cargo.toml
@@ -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" }
diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs
index 9fbae8f84b4..6eb03bc0f5f 100644
--- a/compiler/rustc_passes/src/reachable.rs
+++ b/compiler/rustc_passes/src/reachable.rs
@@ -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};
@@ -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;
 
@@ -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)
@@ -198,17 +185,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);
                         }
                     }
 
@@ -279,28 +258,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>(
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index 9f8cb8fcb41..073982ca5c3 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -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;
@@ -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>>,
diff --git a/tests/codegen/dont_codegen_private_const_fn_only_used_in_const_eval.rs b/tests/codegen/dont_codegen_private_const_fn_only_used_in_const_eval.rs
new file mode 100644
index 00000000000..eeeaebe52dd
--- /dev/null
+++ b/tests/codegen/dont_codegen_private_const_fn_only_used_in_const_eval.rs
@@ -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{{.*}}
diff --git a/tests/ui/cross-crate/auxiliary/static_init_aux.rs b/tests/ui/cross-crate/auxiliary/static_init_aux.rs
index 5e172ef3198..dca708733b9 100644
--- a/tests/ui/cross-crate/auxiliary/static_init_aux.rs
+++ b/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;
@@ -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() {}
diff --git a/tests/ui/cross-crate/static-init.rs b/tests/ui/cross-crate/static-init.rs
index 090ad5f810e..c4697a1d010 100644
--- a/tests/ui/cross-crate/static-init.rs
+++ b/tests/ui/cross-crate/static-init.rs
@@ -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
@@ -15,4 +17,6 @@ fn main() {
     assert_eq!(aux::v(), crate::v());
     F();
     G();
+    H();
+    I();
 }