Skip to content

Commit a303df0

Browse files
committed
collector: always consider all monomorphic functions to be 'mentioned'
1 parent 0cb1065 commit a303df0

9 files changed

+232
-33
lines changed

compiler/rustc_monomorphize/src/collector.rs

+76-32
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,9 @@ fn collect_items_rec<'tcx>(
429429
}
430430
MonoItem::Fn(instance) => {
431431
// Sanity check whether this ended up being collected accidentally
432-
debug_assert!(should_codegen_locally(tcx, &instance));
432+
if mode == CollectionMode::UsedItems {
433+
debug_assert!(should_codegen_locally(tcx, &instance));
434+
}
433435

434436
// Keep track of the monomorphization recursion depth
435437
recursion_depth_reset = Some(check_recursion_limit(
@@ -1518,16 +1520,26 @@ fn collect_const_value<'tcx>(
15181520
// Find all non-generic items by walking the HIR. These items serve as roots to
15191521
// start monomorphizing from.
15201522
#[instrument(skip(tcx, mode), level = "debug")]
1521-
fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec<MonoItem<'_>> {
1523+
fn collect_roots(
1524+
tcx: TyCtxt<'_>,
1525+
mode: MonoItemCollectionStrategy,
1526+
) -> Vec<(MonoItem<'_>, CollectionMode)> {
15221527
debug!("collecting roots");
1523-
let mut roots = Vec::new();
1528+
let mut used_roots = MonoItems::new();
1529+
let mut mentioned_roots = MonoItems::new();
15241530

15251531
{
15261532
let entry_fn = tcx.entry_fn(());
15271533

15281534
debug!("collect_roots: entry_fn = {:?}", entry_fn);
15291535

1530-
let mut collector = RootCollector { tcx, strategy: mode, entry_fn, output: &mut roots };
1536+
let mut collector = RootCollector {
1537+
tcx,
1538+
strategy: mode,
1539+
entry_fn,
1540+
used_roots: &mut used_roots,
1541+
mentioned_roots: &mut mentioned_roots,
1542+
};
15311543

15321544
let crate_items = tcx.hir_crate_items(());
15331545

@@ -1542,21 +1554,30 @@ fn collect_roots(tcx: TyCtxt<'_>, mode: MonoItemCollectionStrategy) -> Vec<MonoI
15421554
collector.push_extra_entry_roots();
15431555
}
15441556

1557+
// Chain the two root lists together. Used items go first, to make it
1558+
// more likely that when we visit a mentioned item, we can stop immediately as it was already used.
15451559
// We can only codegen items that are instantiable - items all of
15461560
// whose predicates hold. Luckily, items that aren't instantiable
15471561
// can't actually be used, so we can just skip codegenning them.
1548-
roots
1562+
used_roots
15491563
.into_iter()
1550-
.filter_map(|Spanned { node: mono_item, .. }| {
1551-
mono_item.is_instantiable(tcx).then_some(mono_item)
1552-
})
1564+
.map(|mono_item| (mono_item.node, CollectionMode::UsedItems))
1565+
.chain(
1566+
mentioned_roots
1567+
.into_iter()
1568+
.map(|mono_item| (mono_item.node, CollectionMode::MentionedItems)),
1569+
)
1570+
.filter(|(mono_item, _mode)| mono_item.is_instantiable(tcx))
15531571
.collect()
15541572
}
15551573

15561574
struct RootCollector<'a, 'tcx> {
15571575
tcx: TyCtxt<'tcx>,
15581576
strategy: MonoItemCollectionStrategy,
1559-
output: &'a mut MonoItems<'tcx>,
1577+
// `MonoItems` includes spans we don't actually want... but this lets us reuse some of the
1578+
// collector's functions.
1579+
used_roots: &'a mut MonoItems<'tcx>,
1580+
mentioned_roots: &'a mut MonoItems<'tcx>,
15601581
entry_fn: Option<(DefId, EntryFnType)>,
15611582
}
15621583

@@ -1570,33 +1591,33 @@ impl<'v> RootCollector<'_, 'v> {
15701591
debug!("RootCollector: ADT drop-glue for `{id:?}`",);
15711592

15721593
let ty = self.tcx.type_of(id.owner_id.to_def_id()).no_bound_vars().unwrap();
1573-
visit_drop_use(self.tcx, ty, true, DUMMY_SP, self.output);
1594+
visit_drop_use(self.tcx, ty, true, DUMMY_SP, self.used_roots);
15741595
}
15751596
}
15761597
DefKind::GlobalAsm => {
15771598
debug!(
15781599
"RootCollector: ItemKind::GlobalAsm({})",
15791600
self.tcx.def_path_str(id.owner_id)
15801601
);
1581-
self.output.push(dummy_spanned(MonoItem::GlobalAsm(id)));
1602+
self.used_roots.push(dummy_spanned(MonoItem::GlobalAsm(id)));
15821603
}
15831604
DefKind::Static { .. } => {
15841605
let def_id = id.owner_id.to_def_id();
15851606
debug!("RootCollector: ItemKind::Static({})", self.tcx.def_path_str(def_id));
1586-
self.output.push(dummy_spanned(MonoItem::Static(def_id)));
1607+
self.used_roots.push(dummy_spanned(MonoItem::Static(def_id)));
15871608
}
15881609
DefKind::Const => {
15891610
// const items only generate mono items if they are
15901611
// actually used somewhere. Just declaring them is insufficient.
15911612

15921613
// but even just declaring them must collect the items they refer to
15931614
if let Ok(val) = self.tcx.const_eval_poly(id.owner_id.to_def_id()) {
1594-
collect_const_value(self.tcx, val, self.output);
1615+
collect_const_value(self.tcx, val, self.used_roots);
15951616
}
15961617
}
15971618
DefKind::Impl { .. } => {
15981619
if self.strategy == MonoItemCollectionStrategy::Eager {
1599-
create_mono_items_for_default_impls(self.tcx, id, self.output);
1620+
create_mono_items_for_default_impls(self.tcx, id, self.used_roots);
16001621
}
16011622
}
16021623
DefKind::Fn => {
@@ -1612,31 +1633,54 @@ impl<'v> RootCollector<'_, 'v> {
16121633
}
16131634
}
16141635

1615-
fn is_root(&self, def_id: LocalDefId) -> bool {
1616-
!self.tcx.generics_of(def_id).requires_monomorphization(self.tcx)
1617-
&& match self.strategy {
1618-
MonoItemCollectionStrategy::Eager => true,
1619-
MonoItemCollectionStrategy::Lazy => {
1620-
self.entry_fn.and_then(|(id, _)| id.as_local()) == Some(def_id)
1621-
|| self.tcx.is_reachable_non_generic(def_id)
1622-
|| self
1623-
.tcx
1624-
.codegen_fn_attrs(def_id)
1625-
.flags
1626-
.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
1627-
}
1636+
/// Determines whether this is an item we start walking, and in which mode. The "real" roots are
1637+
/// walked as "used" items, but that set is optimization-dependent. We add all other non-generic
1638+
/// items as "mentioned" roots. This makes the set of items where `is_root` return `Some`
1639+
/// optimization-independent, which is crucial to ensure that optimized and unoptimized builds
1640+
/// evaluate the same constants.
1641+
fn is_root(&self, def_id: LocalDefId) -> Option<CollectionMode> {
1642+
// Generic things are never roots.
1643+
if self.tcx.generics_of(def_id).requires_monomorphization(self.tcx) {
1644+
return None;
1645+
}
1646+
// Determine whether this item is reachable, which makes it "used".
1647+
let is_used_root = match self.strategy {
1648+
MonoItemCollectionStrategy::Eager => true,
1649+
MonoItemCollectionStrategy::Lazy => {
1650+
self.entry_fn.and_then(|(id, _)| id.as_local()) == Some(def_id)
1651+
|| self.tcx.is_reachable_non_generic(def_id)
1652+
|| self
1653+
.tcx
1654+
.codegen_fn_attrs(def_id)
1655+
.flags
1656+
.contains(CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL)
16281657
}
1658+
};
1659+
if is_used_root {
1660+
return Some(CollectionMode::UsedItems);
1661+
}
1662+
// We have to skip `must_be_overridden` bodies; asking for their "mentioned" items
1663+
// leads to ICEs.
1664+
if self.tcx.intrinsic(def_id).is_some_and(|i| i.must_be_overridden) {
1665+
return None;
1666+
}
1667+
// Everything else is considered "mentioned"
1668+
Some(CollectionMode::MentionedItems)
16291669
}
16301670

16311671
/// If `def_id` represents a root, pushes it onto the list of
16321672
/// outputs. (Note that all roots must be monomorphic.)
16331673
#[instrument(skip(self), level = "debug")]
16341674
fn push_if_root(&mut self, def_id: LocalDefId) {
1635-
if self.is_root(def_id) {
1675+
if let Some(mode) = self.is_root(def_id) {
16361676
debug!("found root");
16371677

16381678
let instance = Instance::mono(self.tcx, def_id.to_def_id());
1639-
self.output.push(create_fn_mono_item(self.tcx, instance, DUMMY_SP));
1679+
let mono_item = create_fn_mono_item(self.tcx, instance, DUMMY_SP);
1680+
match mode {
1681+
CollectionMode::UsedItems => self.used_roots.push(mono_item),
1682+
CollectionMode::MentionedItems => self.mentioned_roots.push(mono_item),
1683+
}
16401684
}
16411685
}
16421686

@@ -1672,7 +1716,7 @@ impl<'v> RootCollector<'_, 'v> {
16721716
self.tcx.mk_args(&[main_ret_ty.into()]),
16731717
);
16741718

1675-
self.output.push(create_fn_mono_item(self.tcx, start_instance, DUMMY_SP));
1719+
self.used_roots.push(create_fn_mono_item(self.tcx, start_instance, DUMMY_SP));
16761720
}
16771721
}
16781722

@@ -1777,15 +1821,15 @@ pub fn collect_crate_mono_items(
17771821
let state: LRef<'_, _> = &mut state;
17781822

17791823
tcx.sess.time("monomorphization_collector_graph_walk", || {
1780-
par_for_each_in(roots, |root| {
1824+
par_for_each_in(roots, |(root, mode)| {
17811825
let mut recursion_depths = DefIdMap::default();
17821826
collect_items_rec(
17831827
tcx,
17841828
dummy_spanned(root),
17851829
state,
17861830
&mut recursion_depths,
17871831
recursion_limit,
1788-
CollectionMode::UsedItems,
1832+
mode,
17891833
);
17901834
});
17911835
});

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-fn.rs:12:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-dead-fn.rs:12: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-fn.rs:25: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-fn.rs:21: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,17 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/collect-roots-dead-fn.rs:12:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-dead-fn.rs:12: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-fn.rs:25:5
11+
|
12+
LL | Fail::<T>::C;
13+
| ^^^^^^^^^^^^
14+
15+
error: aborting due to 1 previous error
16+
17+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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 "mentoned 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+
fn h<T>() {
28+
Fail::<T>::C;
29+
}
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 `Zst::<u32>::ASSERT` failed
2+
--> $DIR/collect-roots-inline-fn.rs:13:9
3+
|
4+
LL | panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-inline-fn.rs:13:9
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-inline-fn.rs:18:5
11+
|
12+
LL | Zst::<T>::ASSERT;
13+
| ^^^^^^^^^^^^^^^^
14+
15+
note: the above error was encountered while instantiating `fn f::<u32>`
16+
--> $DIR/collect-roots-inline-fn.rs:22:5
17+
|
18+
LL | f::<u32>()
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,17 @@
1+
error[E0080]: evaluation of `Zst::<u32>::ASSERT` failed
2+
--> $DIR/collect-roots-inline-fn.rs:13:9
3+
|
4+
LL | panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/collect-roots-inline-fn.rs:13:9
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-inline-fn.rs:18:5
11+
|
12+
LL | Zst::<T>::ASSERT;
13+
| ^^^^^^^^^^^^^^^^
14+
15+
error: aborting due to 1 previous error
16+
17+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//@revisions: noopt opt
2+
//@ build-fail
3+
//@[noopt] compile-flags: -Copt-level=0
4+
//@[opt] compile-flags: -O
5+
6+
//! In optimized builds, the functions in this crate are all marked "inline" so none of them become
7+
//! collector roots. Ensure that we still evaluate the constants.
8+
#![crate_type = "lib"]
9+
10+
struct Zst<T>(T);
11+
impl<T> Zst<T> {
12+
const ASSERT: () = if std::mem::size_of::<T>() != 0 {
13+
panic!(); //~ERROR: evaluation of `Zst::<u32>::ASSERT` failed
14+
};
15+
}
16+
17+
fn f<T>() {
18+
Zst::<T>::ASSERT;
19+
}
20+
21+
pub fn g() {
22+
f::<u32>()
23+
}

0 commit comments

Comments
 (0)