Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions compiler/rustc_passes/src/reachable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..)) => {}
Expand Down

This file was deleted.

38 changes: 38 additions & 0 deletions tests/codegen-llvm/private-const-fn-only-used-in-const-eval.rs
Original file line number Diff line number Diff line change
@@ -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<const C: bool>: () = 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<const C: bool>: () = 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<const C: bool>: Option<fn() /* or a TAIT */> =
if C { Some(func4) } else { None };
const fn func4() {}
// CHECK: define{{.*}}func4{{.*}}
2 changes: 1 addition & 1 deletion tests/ui/generic-const-items/def-site-eval.fail.stderr
Original file line number Diff line number Diff line change
@@ -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
Expand Down
29 changes: 23 additions & 6 deletions tests/ui/generic-const-items/def-site-eval.rs
Copy link
Member Author

@fmease fmease Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Going off on a tangent, the whole "don't eval the initializer for non-lifetime-parametric free const items" thing doesn't apply to mGCA's type-level consts which I'm pretty sure is very intentional (IINM) and thus fine, it's also not GCI-specific, e.g., trait T { type const K: usize = const { panic!(); }; } (Self in scope).

I'm only concerned about const items with bodies anyway since the whole idea is to prevent const eval's "bespoke" handling of "too generic" consts "being user observable" / load-bearing for program correctness (the other motivation being consts should behave like fns in this specific scenario) or rephrased "type based" > "value based".

Original file line number Diff line number Diff line change
@@ -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 _<const _N: usize>: () = 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<const _N: usize>: () = loop {};

#[cfg(fail)]
const _<'_a>: () = panic!(); //[fail]~ ERROR evaluation panicked: explicit panic

fn main() {}
Loading