Skip to content

Commit f076f17

Browse files
committed
collector: always consider all monomorphic functions to be 'mentioned'
1 parent cdb683f commit f076f17

12 files changed

+320
-32
lines changed

compiler/rustc_monomorphize/src/collector.rs

+73-31
Original file line numberDiff line numberDiff line change
@@ -1513,16 +1513,26 @@ fn collect_const_value<'tcx>(
15131513
// Find all non-generic items by walking the HIR. These items serve as roots to
15141514
// start monomorphizing from.
15151515
#[instrument(skip(tcx, mode), level = "debug")]
1516-
fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec<MonoItem<'_>> {
1516+
fn collect_roots(
1517+
tcx: TyCtxt<'_>,
1518+
mode: MonoItemCollectionStrategy,
1519+
) -> Vec<(MonoItem<'_>, CollectionMode)> {
15171520
debug!("collecting roots");
1518-
let mut roots = Vec::new();
1521+
let mut used_roots = MonoItems::new();
1522+
let mut mentioned_roots = MonoItems::new();
15191523

15201524
{
15211525
let entry_fn = tcx.entry_fn(());
15221526

15231527
debug!("collect_roots: entry_fn = {:?}", entry_fn);
15241528

1525-
let mut collector = RootCollector { tcx, strategy: mode, entry_fn, output: &mut roots };
1529+
let mut collector = RootCollector {
1530+
tcx,
1531+
strategy: mode,
1532+
entry_fn,
1533+
used_roots: &mut used_roots,
1534+
mentioned_roots: &mut mentioned_roots,
1535+
};
15261536

15271537
let crate_items = tcx.hir_crate_items(());
15281538

@@ -1537,21 +1547,30 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec<MonoI
15371547
collector.push_extra_entry_roots();
15381548
}
15391549

1550+
// Chain the two root lists together. Used items go first, to make it
1551+
// more likely that when we visit a mentioned item, we can stop immediately as it was already used.
15401552
// We can only codegen items that are instantiable - items all of
15411553
// whose predicates hold. Luckily, items that aren't instantiable
15421554
// can't actually be used, so we can just skip codegenning them.
1543-
roots
1555+
used_roots
15441556
.into_iter()
1545-
.filter_map(|Spanned { node: mono_item, .. }| {
1546-
mono_item.is_instantiable(tcx).then_some(mono_item)
1547-
})
1557+
.map(|mono_item| (mono_item.node, CollectionMode::UsedItems))
1558+
.chain(
1559+
mentioned_roots
1560+
.into_iter()
1561+
.map(|mono_item| (mono_item.node, CollectionMode::MentionedItems)),
1562+
)
1563+
.filter(|(mono_item, _mode)| mono_item.is_instantiable(tcx))
15481564
.collect()
15491565
}
15501566

15511567
struct RootCollector<'a, 'tcx> {
15521568
tcx: TyCtxt<'tcx>,
15531569
strategy: MonoItemCollectionStrategy,
1554-
output: &'a mut MonoItems<'tcx>,
1570+
// `MonoItems` includes spans we don't actually want... but this lets us reuse some of the
1571+
// collector's functions.
1572+
used_roots: &'a mut MonoItems<'tcx>,
1573+
mentioned_roots: &'a mut MonoItems<'tcx>,
15551574
entry_fn: Option<(DefId, EntryFnType)>,
15561575
}
15571576

@@ -1565,33 +1584,33 @@ impl<'v> RootCollector<'_, 'v> {
15651584
debug!("RootCollector: ADT drop-glue for `{id:?}`",);
15661585

15671586
let ty = self.tcx.type_of(id.owner_id.to_def_id()).no_bound_vars().unwrap();
1568-
visit_drop_use(self.tcx, ty, true, DUMMY_SP, self.output);
1587+
visit_drop_use(self.tcx, ty, true, DUMMY_SP, self.used_roots);
15691588
}
15701589
}
15711590
DefKind::GlobalAsm => {
15721591
debug!(
15731592
"RootCollector: ItemKind::GlobalAsm({})",
15741593
self.tcx.def_path_str(id.owner_id)
15751594
);
1576-
self.output.push(dummy_spanned(MonoItem::GlobalAsm(id)));
1595+
self.used_roots.push(dummy_spanned(MonoItem::GlobalAsm(id)));
15771596
}
15781597
DefKind::Static { .. } => {
15791598
let def_id = id.owner_id.to_def_id();
15801599
debug!("RootCollector: ItemKind::Static({})", self.tcx.def_path_str(def_id));
1581-
self.output.push(dummy_spanned(MonoItem::Static(def_id)));
1600+
self.used_roots.push(dummy_spanned(MonoItem::Static(def_id)));
15821601
}
15831602
DefKind::Const => {
15841603
// const items only generate mono items if they are
15851604
// actually used somewhere. Just declaring them is insufficient.
15861605

15871606
// but even just declaring them must collect the items they refer to
15881607
if let Ok(val) = self.tcx.const_eval_poly(id.owner_id.to_def_id()) {
1589-
collect_const_value(self.tcx, val, self.output);
1608+
collect_const_value(self.tcx, val, self.used_roots);
15901609
}
15911610
}
15921611
DefKind::Impl { .. } => {
15931612
if self.strategy == MonoItemCollectionStrategy::Eager {
1594-
create_mono_items_for_default_impls(self.tcx, id, self.output);
1613+
create_mono_items_for_default_impls(self.tcx, id, self.used_roots);
15951614
}
15961615
}
15971616
DefKind::Fn => {
@@ -1607,31 +1626,54 @@ impl<'v> RootCollector<'_, 'v> {
16071626
}
16081627
}
16091628

1610-
fn is_root(&self, def_id: LocalDefId) -> bool {
1611-
!self.tcx.generics_of(def_id).requires_monomorphization(self.tcx)
1612-
&& match self.strategy {
1613-
MonoItemCollectionStrategy::Eager => true,
1614-
MonoItemCollectionStrategy::Lazy => {
1615-
self.entry_fn.and_then(|(id, _)| id.as_local()) == Some(def_id)
1616-
|| self.tcx.is_reachable_non_generic(def_id)
1617-
|| self
1618-
.tcx
1619-
.codegen_fn_attrs(def_id)
1620-
.flags
1621-
.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
1622-
}
1629+
/// Determines whether this is an item we start walking, and in which mode. The "real" roots are
1630+
/// walked as "used" items, but that set is optimization-dependent. We add all other non-generic
1631+
/// items as "mentioned" roots. This makes the set of items where `is_root` return `Some`
1632+
/// optimization-independent, which is crucial to ensure that optimized and unoptimized builds
1633+
/// evaluate the same constants.
1634+
fn is_root(&self, def_id: LocalDefId) -> Option<CollectionMode> {
1635+
// Generic things are never roots.
1636+
if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
1637+
return None;
1638+
}
1639+
// We have to skip `must_be_overridden` bodies; asking for their MIR ICEs.
1640+
if self.tcx.intrinsic(def_id).is_some_and(|i| i.must_be_overridden) {
1641+
return None;
1642+
}
1643+
// The rest is definitely a root, but is it used or merely mentioned?
1644+
// Determine whether this item is reachable, which makes it "used".
1645+
let is_used_root = match self.strategy {
1646+
MonoItemCollectionStrategy::Eager => true,
1647+
MonoItemCollectionStrategy::Lazy => {
1648+
self.entry_fn.and_then(|(id, _)| id.as_local()) == Some(def_id)
1649+
|| self.tcx.is_reachable_non_generic(def_id)
1650+
|| self
1651+
.tcx
1652+
.codegen_fn_attrs(def_id)
1653+
.flags
1654+
.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
16231655
}
1656+
};
1657+
if is_used_root {
1658+
Some(CollectionMode::UsedItems)
1659+
} else {
1660+
Some(CollectionMode::MentionedItems)
1661+
}
16241662
}
16251663

16261664
/// If `def_id` represents a root, pushes it onto the list of
16271665
/// outputs. (Note that all roots must be monomorphic.)
16281666
#[instrument(skip(self), level = "debug")]
16291667
fn push_if_root(&mut self, def_id: LocalDefId) {
1630-
if self.is_root(def_id) {
1668+
if let Some(mode) = self.is_root(def_id) {
16311669
debug!("found root");
16321670

16331671
let instance = Instance::mono(self.tcx, def_id.to_def_id());
1634-
self.output.push(create_fn_mono_item(self.tcx, instance, DUMMY_SP));
1672+
let mono_item = create_fn_mono_item(self.tcx, instance, DUMMY_SP);
1673+
match mode {
1674+
CollectionMode::UsedItems => self.used_roots.push(mono_item),
1675+
CollectionMode::MentionedItems => self.mentioned_roots.push(mono_item),
1676+
}
16351677
}
16361678
}
16371679

@@ -1667,7 +1709,7 @@ impl<'v> RootCollector<'_, 'v> {
16671709
self.tcx.mk_args(&[main_ret_ty.into()]),
16681710
);
16691711

1670-
self.output.push(create_fn_mono_item(self.tcx, start_instance, DUMMY_SP));
1712+
self.used_roots.push(create_fn_mono_item(self.tcx, start_instance, DUMMY_SP));
16711713
}
16721714
}
16731715

@@ -1772,15 +1814,15 @@ pub fn collect_crate_mono_items(
17721814
let state: LRef<'_, _> = &mut state;
17731815

17741816
tcx.sess.time("monomorphization_collector_graph_walk", || {
1775-
par_for_each_in(roots, |root| {
1817+
par_for_each_in(roots, |(root, mode)| {
17761818
let mut recursion_depths = DefIdMap::default();
17771819
collect_items_rec(
17781820
tcx,
17791821
dummy_spanned(root),
17801822
state,
17811823
&mut recursion_depths,
17821824
recursion_limit,
1783-
CollectionMode::UsedItems,
1825+
mode,
17841826
);
17851827
});
17861828
});

tests/incremental/struct_remove_field.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Test incremental compilation tracking where we change field names
1+
// Test incremental compilation tracking where we remove a field
22
// in between revisions (hashing should be stable).
33

44
//@ revisions:rpass1 rpass2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/collect-roots-dead-fn1.rs:15:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-dead-fn1.rs:15:19
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
note: erroneous constant encountered
10+
--> $DIR/collect-roots-dead-fn1.rs:31:5
11+
|
12+
LL | Fail::<T>::C;
13+
| ^^^^^^^^^^^^
14+
15+
note: the above error was encountered while instantiating `fn h::<i32>`
16+
--> $DIR/collect-roots-dead-fn1.rs:24:5
17+
|
18+
LL | h::<i32>()
19+
| ^^^^^^^^^^
20+
21+
error: aborting due to 1 previous error
22+
23+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/collect-roots-dead-fn1.rs:15:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-dead-fn1.rs:15:19
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
note: erroneous constant encountered
10+
--> $DIR/collect-roots-dead-fn1.rs:31:5
11+
|
12+
LL | Fail::<T>::C;
13+
| ^^^^^^^^^^^^
14+
15+
note: the above error was encountered while instantiating `fn h::<i32>`
16+
--> $DIR/collect-roots-dead-fn1.rs:24:5
17+
|
18+
LL | h::<i32>()
19+
| ^^^^^^^^^^
20+
21+
error: aborting due to 1 previous error
22+
23+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@revisions: noopt opt
2+
//@ build-fail
3+
//@[noopt] compile-flags: -Copt-level=0
4+
//@[opt] compile-flags: -O
5+
6+
//! This used to fail in optimized builds but pass in unoptimized builds. The reason is that in
7+
//! optimized builds, `f` gets marked as cross-crate-inlineable, so the functions it calls become
8+
//! reachable, and therefore `g` becomes a collection root. But in unoptimized builds, `g` is no
9+
//! root, and the call to `g` disappears in an early `SimplifyCfg` before "mentioned items" are
10+
//! gathered, so we never reach `g`.
11+
#![crate_type = "lib"]
12+
13+
struct Fail<T>(T);
14+
impl<T> Fail<T> {
15+
const C: () = panic!(); //~ERROR: evaluation of `Fail::<i32>::C` failed
16+
}
17+
18+
pub fn f() {
19+
loop {}; g()
20+
}
21+
22+
#[inline(never)]
23+
fn g() {
24+
h::<i32>()
25+
}
26+
27+
// Make sure we only use the faulty const in a generic function, or
28+
// else it gets evaluated by some MIR pass.
29+
#[inline(never)]
30+
fn h<T>() {
31+
Fail::<T>::C;
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@revisions: noopt opt
2+
//@ build-pass
3+
//@[noopt] compile-flags: -Copt-level=0
4+
//@[opt] compile-flags: -O
5+
6+
//! A slight variant of `collect-roots-in-dead-fn` where the dead call is itself generic. Now this
7+
//! *passes* in both optimized and unoptimized builds: the call to `h` always disappears in an early
8+
//! `SimplifyCfg`, and `h` is generic so it can never be a root.
9+
#![crate_type = "lib"]
10+
11+
struct Fail<T>(T);
12+
impl<T> Fail<T> {
13+
const C: () = panic!();
14+
}
15+
16+
pub fn f() {
17+
loop {}; h::<i32>()
18+
}
19+
20+
#[inline(never)]
21+
fn h<T>() {
22+
Fail::<T>::C;
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0080]: evaluation of `fail::<()>::{constant#0}` failed
2+
--> $DIR/collect-roots-dead-fn3.rs:35:13
3+
|
4+
LL | const { panic!(); }
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-dead-fn3.rs:35:13
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
note: erroneous constant encountered
10+
--> $DIR/collect-roots-dead-fn3.rs:35:5
11+
|
12+
LL | const { panic!(); }
13+
| ^^^^^^^^^^^^^^^^^^^
14+
15+
note: the above error was encountered while instantiating `fn fail::<()>`
16+
--> $DIR/collect-roots-dead-fn3.rs:28:5
17+
|
18+
LL | fail::<()>();
19+
| ^^^^^^^^^^^^
20+
21+
error: aborting due to 1 previous error
22+
23+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error[E0080]: evaluation of `fail::<()>::{constant#0}` failed
2+
--> $DIR/collect-roots-dead-fn3.rs:35:13
3+
|
4+
LL | const { panic!(); }
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-dead-fn3.rs:35:13
6+
|
7+
= note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info)
8+
9+
note: erroneous constant encountered
10+
--> $DIR/collect-roots-dead-fn3.rs:35:5
11+
|
12+
LL | const { panic!(); }
13+
| ^^^^^^^^^^^^^^^^^^^
14+
15+
note: the above error was encountered while instantiating `fn fail::<()>`
16+
--> $DIR/collect-roots-dead-fn3.rs:28:5
17+
|
18+
LL | fail::<()>();
19+
| ^^^^^^^^^^^^
20+
21+
error: aborting due to 1 previous error
22+
23+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//@revisions: noopt opt
2+
//@ build-fail
3+
//@[noopt] compile-flags: -Copt-level=0
4+
//@[opt] compile-flags: -O
5+
6+
#![crate_type = "lib"]
7+
#![feature(inline_const)]
8+
9+
// Will be `inline` with optimizations, so then `g::<()>` becomes reachable. At the same time `g` is
10+
// not "mentioned" in `f` since it is only called inside an inline `const` and hence never appears
11+
// in its MIR! This fundamentally is an issue caused by reachability walking the HIR (before inline
12+
// `const` are extracted) and collection being based on MIR.
13+
pub fn f() {
14+
loop {}; const { g::<()>() }
15+
}
16+
17+
// When this comes reachable (for any `T`), so does `hidden_root`.
18+
// And `hidden_root` is monomorphic so it can become a root!
19+
const fn g<T>() {
20+
match std::mem::size_of::<T>() {
21+
0 => (),
22+
_ => hidden_root(),
23+
}
24+
}
25+
26+
#[inline(never)]
27+
const fn hidden_root() {
28+
fail::<()>();
29+
}
30+
31+
#[inline(never)]
32+
const fn fail<T>() {
33+
// Hiding this in a generic fn so that it doesn't get evaluated by
34+
// MIR passes.
35+
const { panic!(); } //~ERROR: evaluation of `fail::<()>::{constant#0}` failed
36+
}

0 commit comments

Comments
 (0)