diff --git a/compiler/rustc_passes/src/reachable.rs b/compiler/rustc_passes/src/reachable.rs index f72899a66cae0..dd5e8b3976eb5 100644 --- a/compiler/rustc_passes/src/reachable.rs +++ b/compiler/rustc_passes/src/reachable.rs @@ -214,19 +214,26 @@ impl<'tcx> ReachableContext<'tcx> { self.visit_const_item_rhs(init); } hir::ItemKind::Const(_, _, _, init) => { - // Only things actually ending up in the final constant value are reachable - // for codegen. Everything else is only needed during const-eval, so even if - // const-eval happens in a downstream crate, all they need is - // `mir_for_ctfe`. + if self.tcx.generics_of(item.owner_id).own_requires_monomorphization() { + // In this case, we don't want to evaluate the const initializer. + // In lieu of that, we have to consider everything mentioned in it + // as reachable, since it *may* end up in the final value. + self.visit_const_item_rhs(init); + return; + } + match self.tcx.const_eval_poly_to_alloc(item.owner_id.def_id.into()) { Ok(alloc) => { + // Only things actually ending up in the final constant value are + // reachable for codegen. Everything else is only needed during + // const-eval, so even if const-eval happens in a downstream crate, + // all they need is `mir_for_ctfe`. let alloc = self.tcx.global_alloc(alloc.alloc_id).unwrap_memory(); self.propagate_from_alloc(alloc); } - // We can't figure out which value the constant will evaluate to. In - // lieu of that, we have to consider everything mentioned in the const - // initializer reachable, since it *may* end up in the final value. - Err(ErrorHandled::TooGeneric(_)) => self.visit_const_item_rhs(init), + // We've checked at the start that there aren't any non-lifetime params + // in scope. The const initializer can't possibly be too generic. + Err(ErrorHandled::TooGeneric(_)) => bug!(), // If there was an error evaluating the const, nothing can be reachable // via it, and anyway compilation will fail. Err(ErrorHandled::Reported(..)) => {} diff --git a/tests/codegen-llvm/dont_codegen_private_const_fn_only_used_in_const_eval.rs b/tests/codegen-llvm/dont_codegen_private_const_fn_only_used_in_const_eval.rs deleted file mode 100644 index df50b4af80984..0000000000000 --- a/tests/codegen-llvm/dont_codegen_private_const_fn_only_used_in_const_eval.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! 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 - -#![feature(generic_const_items)] - -const fn foo() {} - -pub static FOO: () = foo(); - -// CHECK-NOT: define{{.*}}foo{{.*}} - -const fn bar() {} - -pub const BAR: () = bar(); - -// CHECK-NOT: define{{.*}}bar{{.*}} - -const fn baz() {} - -#[rustfmt::skip] -pub const BAZ: () = if C { - baz() -}; - -// CHECK: define{{.*}}baz{{.*}} diff --git a/tests/codegen-llvm/private-const-fn-only-used-in-const-eval.rs b/tests/codegen-llvm/private-const-fn-only-used-in-const-eval.rs new file mode 100644 index 0000000000000..6dd20cb17c431 --- /dev/null +++ b/tests/codegen-llvm/private-const-fn-only-used-in-const-eval.rs @@ -0,0 +1,38 @@ +// Check that we — where possible — don't codegen functions that are only used to evaluate +// static / const items, but never used in runtime code. + +//@ compile-flags: --crate-type=lib -Copt-level=0 + +#![feature(generic_const_items)] // only used in the last few test cases + +pub static STATIC: () = func0(); +const fn func0() {} +// CHECK-NOT: define{{.*}}func0{{.*}} + +pub const CONSTANT: () = func1(); +const fn func1() {} +// CHECK-NOT: define{{.*}}func1{{.*}} + +// We generally don't want to evaluate the initializer of free const items if they have +// non-region params (and even if we did, const eval would fail anyway with "too polymorphic" +// if the initializer actually referenced such a param). +// +// As a result of not being able to look at the final value, during reachability analysis we +// can't tell for sure if for example certain functions end up in the final value or if they're +// only used during const eval. We fall back to a conservative HIR-based approach. + +// `func2` isn't needed at runtime but the compiler can't tell for the reason mentioned above. +pub const POLY_CONST_0: () = func2(); +const fn func2() {} +// CHECK: define{{.*}}func2{{.*}} + +// `func3` isn't needed at runtime but the compiler can't tell for the reason mentioned above. +pub const POLY_CONST_1: () = if C { func3() }; +const fn func3() {} +// CHECK: define{{.*}}func3{{.*}} + +// `func4` *is* needed at runtime (here, the HIR-based approach gets it right). +pub const POLY_CONST_2: Option = + if C { Some(func4) } else { None }; +const fn func4() {} +// CHECK: define{{.*}}func4{{.*}} diff --git a/tests/ui/generic-const-items/def-site-eval.fail.stderr b/tests/ui/generic-const-items/def-site-eval.fail.stderr index e39fbdf7802d3..1fae0790c9fdb 100644 --- a/tests/ui/generic-const-items/def-site-eval.fail.stderr +++ b/tests/ui/generic-const-items/def-site-eval.fail.stderr @@ -1,5 +1,5 @@ error[E0080]: evaluation panicked: explicit panic - --> $DIR/def-site-eval.rs:13:20 + --> $DIR/def-site-eval.rs:32:20 | LL | const _<'_a>: () = panic!(); | ^^^^^^^^ evaluation of `_` failed here diff --git a/tests/ui/generic-const-items/def-site-eval.rs b/tests/ui/generic-const-items/def-site-eval.rs index fa3ef5907b26d..44440450b6008 100644 --- a/tests/ui/generic-const-items/def-site-eval.rs +++ b/tests/ui/generic-const-items/def-site-eval.rs @@ -1,15 +1,32 @@ -//! Test that we only evaluate free const items (their def site to be clear) -//! whose generics don't require monomorphization. +// Test that we don't evaluate the initializer of free const items if they have +// non-region generic parameters (i.e., ones that "require monomorphization"). +// +// To peek behind the curtains for a bit, at the time of writing there are three places where we +// usually evaluate the initializer: "analysis", mono item collection & reachability analysis. +// We must ensure that all of them take the generics into account. +// +//@ revisions: fail pass +//@[pass] check-pass + #![feature(generic_const_items)] #![expect(incomplete_features)] +#![crate_type = "lib"] // (*) -//@ revisions: fail pass -//@[pass] check-pass +// All of these constants are intentionally unused since we want to test the +// behavior at the def site, not at use sites. const _<_T>: () = panic!(); const _: () = panic!(); +// Check *public* const items specifically to exercise reachability analysis which normally +// evaluates const initializers to look for function pointers in the final const value. +// +// (*): While reachability analysis also runs for purely binary crates (to find e.g., extern items) +// setting the crate type to library (1) makes the case below 'more realistic' since +// hypothetical downstream crates that require runtime MIR could actually exist. +// (2) It ensures that we exercise the relevant part of the compiler under test. +pub const K<_T>: () = panic!(); +pub const Q: () = loop {}; + #[cfg(fail)] const _<'_a>: () = panic!(); //[fail]~ ERROR evaluation panicked: explicit panic - -fn main() {}