Skip to content

Commit 302e5b1

Browse files
Rollup merge of rust-lang#153269 - fmease:gci-reach-no-eval, r=BoxyUwU
GCI: During reachability analysis don't try to evaluate the initializer of overly generic free const items We generally don't want the initializer of free const items to get evaluated if they have any non-lifetime generic parameters. However, while I did account for that in HIR analysis & mono item collection (rust-lang#136168 & rust-lang#136429), I didn't account for reachability analysis so far which means that on main we still evaluate such items if they are *public* for example. The closed PR rust-lang#142293 from a year ago did address that as a byproduct but of course it wasn't merged since its primary goal was misguided. This PR extracts & improves upon the relevant parts of that PR which are necessary to fix said issue. Follow up to rust-lang#136168 & rust-lang#136429. Partially supersedes rust-lang#142293. Part of rust-lang#113521. r? @BoxyUwU
2 parents a772c85 + cf4e8c6 commit 302e5b1

11 files changed

Lines changed: 128 additions & 83 deletions

compiler/rustc_passes/src/reachable.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -214,18 +214,26 @@ impl<'tcx> ReachableContext<'tcx> {
214214
self.visit_const_item_rhs(init);
215215
}
216216
hir::ItemKind::Const(_, _, _, init) => {
217-
// Only things actually ending up in the final constant value are reachable
218-
// for codegen. Everything else is only needed during const-eval, so even if
219-
// const-eval happens in a downstream crate, all they need is
220-
// `mir_for_ctfe`.
217+
if self.tcx.generics_of(item.owner_id).own_requires_monomorphization() {
218+
// In this case, we don't want to evaluate the const initializer.
219+
// In lieu of that, we have to consider everything mentioned in it
220+
// as reachable, since it *may* end up in the final value.
221+
self.visit_const_item_rhs(init);
222+
return;
223+
}
224+
221225
match self.tcx.const_eval_poly_to_alloc(item.owner_id.def_id.into()) {
222226
Ok(alloc) => {
227+
// Only things actually ending up in the final constant value are
228+
// reachable for codegen. Everything else is only needed during
229+
// const-eval, so even if const-eval happens in a downstream crate,
230+
// all they need is `mir_for_ctfe`.
223231
let alloc = self.tcx.global_alloc(alloc.alloc_id).unwrap_memory();
224232
self.propagate_from_alloc(alloc);
225233
}
226-
// We can't figure out which value the constant will evaluate to. In
227-
// lieu of that, we have to consider everything mentioned in the const
228-
// initializer reachable, since it *may* end up in the final value.
234+
// Trivially unsatisfiable bounds on the item prevented us from
235+
// normalizing the initializer. Similar to the other case, we have to
236+
// everything mentioned in it as reachable.
229237
Err(ErrorHandled::TooGeneric(_)) => self.visit_const_item_rhs(init),
230238
// If there was an error evaluating the const, nothing can be reachable
231239
// via it, and anyway compilation will fail.

tests/codegen-llvm/dont_codegen_private_const_fn_only_used_in_const_eval.rs

Lines changed: 0 additions & 27 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Check that we — where possible — don't codegen functions that are only used to evaluate
2+
// static / const items, but never used in runtime code.
3+
4+
//@ compile-flags: --crate-type=lib -Copt-level=0
5+
6+
#![feature(generic_const_items)] // only used in the last few test cases
7+
8+
pub static STATIC: () = func0();
9+
const fn func0() {}
10+
// CHECK-NOT: define{{.*}}func0{{.*}}
11+
12+
pub const CONSTANT: () = func1();
13+
const fn func1() {}
14+
// CHECK-NOT: define{{.*}}func1{{.*}}
15+
16+
// We generally don't want to evaluate the initializer of free const items if they have
17+
// non-region params (and even if we did, const eval would fail anyway with "too polymorphic"
18+
// if the initializer actually referenced such a param).
19+
//
20+
// As a result of not being able to look at the final value, during reachability analysis we
21+
// can't tell for sure if for example certain functions end up in the final value or if they're
22+
// only used during const eval. We fall back to a conservative HIR-based approach.
23+
24+
// `func2` isn't needed at runtime but the compiler can't tell for the reason mentioned above.
25+
pub const POLY_CONST_0<const C: bool>: () = func2();
26+
const fn func2() {}
27+
// CHECK: define{{.*}}func2{{.*}}
28+
29+
// `func3` isn't needed at runtime but the compiler can't tell for the reason mentioned above.
30+
pub const POLY_CONST_1<const C: bool>: () = if C { func3() };
31+
const fn func3() {}
32+
// CHECK: define{{.*}}func3{{.*}}
33+
34+
// `func4` *is* needed at runtime (here, the HIR-based approach gets it right).
35+
pub const POLY_CONST_2<const C: bool>: Option<fn() /* or a TAIT */> =
36+
if C { Some(func4) } else { None };
37+
const fn func4() {}
38+
// CHECK: define{{.*}}func4{{.*}}

tests/ui/generic-const-items/def-site-eval.fail.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0080]: evaluation panicked: explicit panic
2-
--> $DIR/def-site-eval.rs:13:20
2+
--> $DIR/def-site-eval.rs:32:20
33
|
44
LL | const _<'_a>: () = panic!();
55
| ^^^^^^^^ evaluation of `_` failed here

tests/ui/generic-const-items/def-site-eval.rs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,32 @@
1-
//! Test that we only evaluate free const items (their def site to be clear)
2-
//! whose generics don't require monomorphization.
1+
// Test that we don't evaluate the initializer of free const items if they have
2+
// non-region generic parameters (i.e., ones that "require monomorphization").
3+
//
4+
// To peek behind the curtains for a bit, at the time of writing there are three places where we
5+
// usually evaluate the initializer: "analysis", mono item collection & reachability analysis.
6+
// We must ensure that all of them take the generics into account.
7+
//
8+
//@ revisions: fail pass
9+
//@[pass] check-pass
10+
311
#![feature(generic_const_items)]
412
#![expect(incomplete_features)]
13+
#![crate_type = "lib"] // (*)
514

6-
//@ revisions: fail pass
7-
//@[pass] check-pass
15+
// All of these constants are intentionally unused since we want to test the
16+
// behavior at the def site, not at use sites.
817

918
const _<_T>: () = panic!();
1019
const _<const _N: usize>: () = panic!();
1120

21+
// Check *public* const items specifically to exercise reachability analysis which normally
22+
// evaluates const initializers to look for function pointers in the final const value.
23+
//
24+
// (*): While reachability analysis also runs for purely binary crates (to find e.g., extern items)
25+
// setting the crate type to library (1) makes the case below 'more realistic' since
26+
// hypothetical downstream crates that require runtime MIR could actually exist.
27+
// (2) It ensures that we exercise the relevant part of the compiler under test.
28+
pub const K<_T>: () = panic!();
29+
pub const Q<const _N: usize>: () = loop {};
30+
1231
#[cfg(fail)]
1332
const _<'_a>: () = panic!(); //[fail]~ ERROR evaluation panicked: explicit panic
14-
15-
fn main() {}

tests/ui/generic-const-items/trivially-unsatisfied-bounds-0.rs

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/ui/generic-const-items/trivially-unsatisfied-bounds-1.rs

Lines changed: 0 additions & 12 deletions
This file was deleted.

tests/ui/generic-const-items/trivially-unsatisfied-bounds-1.stderr

Lines changed: 0 additions & 11 deletions
This file was deleted.

tests/ui/generic-const-items/trivially-unsatisfied-bounds-0.stderr renamed to tests/ui/generic-const-items/trivially-unsatisfied-bounds.mentioned.stderr

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
error[E0080]: entering unreachable code
2-
--> $DIR/trivially-unsatisfied-bounds-0.rs:6:1
2+
--> $DIR/trivially-unsatisfied-bounds.rs:17:1
33
|
44
LL | / const UNUSABLE: () = ()
55
LL | | where
6-
LL | | String: Copy;
7-
| |_________________^ evaluation of `UNUSABLE` failed here
6+
LL | | for<'_delay> String: Copy;
7+
| |______________________________^ evaluation of `UNUSABLE` failed here
88

99
error[E0277]: the trait bound `String: Copy` is not satisfied
10-
--> $DIR/trivially-unsatisfied-bounds-0.rs:11:13
10+
--> $DIR/trivially-unsatisfied-bounds.rs:24:13
1111
|
1212
LL | let _ = UNUSABLE;
1313
| ^^^^^^^^ the trait `Copy` is not implemented for `String`
1414
|
1515
note: required by a bound in `UNUSABLE`
16-
--> $DIR/trivially-unsatisfied-bounds-0.rs:8:13
16+
--> $DIR/trivially-unsatisfied-bounds.rs:19:26
1717
|
1818
LL | const UNUSABLE: () = ()
1919
| -------- required by a bound in this constant
2020
LL | where
21-
LL | String: Copy;
22-
| ^^^^ required by this bound in `UNUSABLE`
21+
LL | for<'_delay> String: Copy;
22+
| ^^^^ required by this bound in `UNUSABLE`
2323

2424
error: aborting due to 2 previous errors
2525

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Exercise trivially unsatisfied bounds on free const items.
2+
// Their interaction with the evaluation of the initializer is interesting.
3+
//
4+
//@ revisions: mentioned unmentioned
5+
6+
#![feature(generic_const_items)]
7+
8+
// FIXME(generic_const_items): Try to get rid of error "entering unreachable error", it's
9+
// unnecessary and actually caused by MIR pass `ImpossiblePredicates` replacing the body with the
10+
// terminator `Unreachable` due to the unsatisfied bound which is subsequently reached.
11+
//
12+
// NOTE(#142293): However, don't think about suppressing the evaluation of the initializer if the
13+
// bounds are "impossible". That'd be a SemVer hazard since it could cause downstream to fail to
14+
// compile if upstream added a new trait impl which is undesirable[^1].
15+
// [^1]: Strictly speaking that's already possible due to the one-impl rule.
16+
17+
const UNUSABLE: () = () //~ ERROR entering unreachable code
18+
where
19+
for<'_delay> String: Copy;
20+
21+
fn scope() {
22+
// Ensure that we successfully reject references of consts with trivially unsatisfied bounds.
23+
#[cfg(mentioned)]
24+
let _ = UNUSABLE; //[mentioned]~ ERROR the trait bound `String: Copy` is not satisfied
25+
}
26+
27+
const _BAD: () = <() as Unimplemented>::CT
28+
where
29+
for<'_delay> (): Unimplemented;
30+
31+
trait Unimplemented { const CT: (); }
32+
33+
fn main() {}

0 commit comments

Comments
 (0)