Skip to content

Commit 6edfa53

Browse files
committed
Auto merge of rust-lang#122258 - RalfJung:required-fns, r=<try>
Draft: monomorphize things from dead code, too This is another attempt at fixing rust-lang#107503. The previous attempt at rust-lang#112879 seems stuck in figuring out where the [perf regression](https://perf.rust-lang.org/compare.html?start=c55d1ee8d4e3162187214692229a63c2cc5e0f31&end=ec8de1ebe0d698b109beeaaac83e60f4ef8bb7d1&stat=instructions:u) comes from. So here I want to take baby steps to see the impact of each step. r? `@ghost`
2 parents 184c5ab + f2b8866 commit 6edfa53

27 files changed

+389
-73
lines changed

Diff for: compiler/rustc_middle/src/mir/mod.rs

+13
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,14 @@ impl<'tcx> CoroutineInfo<'tcx> {
312312
}
313313
}
314314

315+
/// Some item that needs to monomorphize successfully for a MIR body to be considered well-formed.
316+
#[derive(Copy, Clone, PartialEq, Debug, HashStable, TyEncodable, TyDecodable)]
317+
#[derive(TypeFoldable, TypeVisitable)]
318+
pub enum RequiredItem<'tcx> {
319+
Fn(DefId, GenericArgsRef<'tcx>),
320+
Drop(Ty<'tcx>),
321+
}
322+
315323
/// The lowered representation of a single function.
316324
#[derive(Clone, TyEncodable, TyDecodable, Debug, HashStable, TypeFoldable, TypeVisitable)]
317325
pub struct Body<'tcx> {
@@ -377,6 +385,9 @@ pub struct Body<'tcx> {
377385
/// We hold in this field all the constants we are not able to evaluate yet.
378386
pub required_consts: Vec<ConstOperand<'tcx>>,
379387

388+
/// Further items that need to monomorphize successfully for this MIR to be well-formed.
389+
pub required_items: Vec<RequiredItem<'tcx>>,
390+
380391
/// Does this body use generic parameters. This is used for the `ConstEvaluatable` check.
381392
///
382393
/// Note that this does not actually mean that this body is not computable right now.
@@ -447,6 +458,7 @@ impl<'tcx> Body<'tcx> {
447458
var_debug_info,
448459
span,
449460
required_consts: Vec::new(),
461+
required_items: Vec::new(),
450462
is_polymorphic: false,
451463
injection_phase: None,
452464
tainted_by_errors,
@@ -475,6 +487,7 @@ impl<'tcx> Body<'tcx> {
475487
spread_arg: None,
476488
span: DUMMY_SP,
477489
required_consts: Vec::new(),
490+
required_items: Vec::new(),
478491
var_debug_info: Vec::new(),
479492
is_polymorphic: false,
480493
injection_phase: None,

Diff for: compiler/rustc_mir_build/src/build/custom/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ pub(super) fn build_custom_mir<'tcx>(
5656
var_debug_info: Vec::new(),
5757
span,
5858
required_consts: Vec::new(),
59+
required_items: Vec::new(),
5960
is_polymorphic: false,
6061
tainted_by_errors: None,
6162
injection_phase: None,

Diff for: compiler/rustc_mir_transform/src/inline.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,8 @@ impl<'tcx> Inliner<'tcx> {
565565
mut callee_body: Body<'tcx>,
566566
) {
567567
let terminator = caller_body[callsite.block].terminator.take().unwrap();
568-
let TerminatorKind::Call { args, destination, unwind, target, .. } = terminator.kind else {
568+
let TerminatorKind::Call { func, args, destination, unwind, target, .. } = terminator.kind
569+
else {
569570
bug!("unexpected terminator kind {:?}", terminator.kind);
570571
};
571572

@@ -717,6 +718,18 @@ impl<'tcx> Inliner<'tcx> {
717718
Const::Val(..) | Const::Unevaluated(..) => true,
718719
},
719720
));
721+
// Now that we incorporated the callee's `required_consts`, we can remove the callee from
722+
// `required_items` -- but we have to take their `required_consts` in return.
723+
let callee_item = {
724+
// We need to reconstruct the `required_item` for the callee.
725+
let func_ty = func.ty(caller_body, self.tcx);
726+
match func_ty.kind() {
727+
ty::FnDef(def_id, args) => RequiredItem::Fn(*def_id, args),
728+
_ => bug!(),
729+
}
730+
};
731+
caller_body.required_items.retain(|item| *item != callee_item);
732+
caller_body.required_items.extend(callee_body.required_items);
720733
}
721734

722735
fn make_call_args(

Diff for: compiler/rustc_mir_transform/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -344,11 +344,14 @@ fn mir_promoted(
344344
}
345345

346346
let mut required_consts = Vec::new();
347-
let mut required_consts_visitor = RequiredConstsVisitor::new(&mut required_consts);
347+
let mut required_items = Vec::new();
348+
let mut required_consts_visitor =
349+
RequiredConstsVisitor::new(tcx, &body, &mut required_consts, &mut required_items);
348350
for (bb, bb_data) in traversal::reverse_postorder(&body) {
349351
required_consts_visitor.visit_basic_block_data(bb, bb_data);
350352
}
351353
body.required_consts = required_consts;
354+
body.required_items = required_items;
352355

353356
// What we need to run borrowck etc.
354357
let promote_pass = promote_consts::PromoteTemps::default();

Diff for: compiler/rustc_mir_transform/src/required_consts.rs

+34-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
use rustc_middle::mir::visit::Visitor;
2-
use rustc_middle::mir::{Const, ConstOperand, Location};
3-
use rustc_middle::ty::ConstKind;
2+
use rustc_middle::mir::{self, Const, ConstOperand, Location, RequiredItem};
3+
use rustc_middle::ty::{self, ConstKind, TyCtxt};
44

5+
#[allow(unused)]
56
pub struct RequiredConstsVisitor<'a, 'tcx> {
7+
tcx: TyCtxt<'tcx>,
8+
body: &'a mir::Body<'tcx>,
69
required_consts: &'a mut Vec<ConstOperand<'tcx>>,
10+
required_items: &'a mut Vec<RequiredItem<'tcx>>,
711
}
812

913
impl<'a, 'tcx> RequiredConstsVisitor<'a, 'tcx> {
10-
pub fn new(required_consts: &'a mut Vec<ConstOperand<'tcx>>) -> Self {
11-
RequiredConstsVisitor { required_consts }
14+
pub fn new(
15+
tcx: TyCtxt<'tcx>,
16+
body: &'a mir::Body<'tcx>,
17+
required_consts: &'a mut Vec<ConstOperand<'tcx>>,
18+
required_items: &'a mut Vec<RequiredItem<'tcx>>,
19+
) -> Self {
20+
RequiredConstsVisitor { tcx, body, required_consts, required_items }
1221
}
1322
}
1423

@@ -21,7 +30,27 @@ impl<'tcx> Visitor<'tcx> for RequiredConstsVisitor<'_, 'tcx> {
2130
_ => bug!("only ConstKind::Param/Value should be encountered here, got {:#?}", c),
2231
},
2332
Const::Unevaluated(..) => self.required_consts.push(*constant),
24-
Const::Val(..) => {}
33+
Const::Val(_val, ty) => {
34+
// This is how function items get referenced: via zero-sized constants of `FnDef` type
35+
if let ty::FnDef(def_id, args) = ty.kind() {
36+
debug!("adding to required_items: {def_id:?}");
37+
self.required_items.push(RequiredItem::Fn(*def_id, args));
38+
}
39+
}
2540
}
2641
}
42+
43+
/*fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
44+
self.super_terminator(terminator, location);
45+
46+
match terminator.kind {
47+
// We don't need to handle `Call` as we already handled all function type operands in
48+
// `visit_constant`. But we do need to handle `Drop`.
49+
mir::TerminatorKind::Drop { place, .. } => {
50+
let ty = place.ty(self.body, self.tcx).ty;
51+
self.required_items.push(RequiredItem::Drop(ty));
52+
}
53+
_ => {}
54+
}
55+
}*/
2756
}

Diff for: compiler/rustc_monomorphize/src/collector.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ fn collect_items_rec<'tcx>(
345345
return;
346346
}
347347

348-
let mut used_items = Vec::new();
348+
let mut used_items = MonoItems::new();
349349
let recursion_depth_reset;
350350

351351
// Post-monomorphization errors MVP
@@ -738,6 +738,17 @@ impl<'a, 'tcx> MirUsedCollector<'a, 'tcx> {
738738
}
739739

740740
impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
741+
fn visit_body(&mut self, body: &mir::Body<'tcx>) {
742+
for item in &body.required_items {
743+
// All these also need monomorphization to ensure that if that leads to error, we find
744+
// those errors.
745+
let item = self.monomorphize(*item);
746+
visit_required_item(self.tcx, item, self.output);
747+
}
748+
749+
self.super_body(body);
750+
}
751+
741752
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
742753
debug!("visiting rvalue {:?}", *rvalue);
743754

@@ -919,6 +930,25 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> {
919930
}
920931
}
921932

933+
fn visit_required_item<'tcx>(
934+
tcx: TyCtxt<'tcx>,
935+
item: mir::RequiredItem<'tcx>,
936+
output: &mut MonoItems<'tcx>,
937+
) {
938+
let instance = match item {
939+
mir::RequiredItem::Fn(def_id, args) => {
940+
Instance::expect_resolve(tcx, ty::ParamEnv::reveal_all(), def_id, args)
941+
}
942+
mir::RequiredItem::Drop(ty) => Instance::resolve_drop_in_place(tcx, ty),
943+
};
944+
// We pretend this is a direct call, just to make sure this is visited at all.
945+
// "indirect" would mean we also generate some shims, but we don't care about the
946+
// generated code, just about the side-effect of code generation causing errors, so we
947+
// can skip the shims.
948+
// FIXME: track the span so that we can show it here.
949+
visit_instance_use(tcx, instance, /*is_direct_call*/ true, DUMMY_SP, output);
950+
}
951+
922952
fn visit_drop_use<'tcx>(
923953
tcx: TyCtxt<'tcx>,
924954
ty: Ty<'tcx>,
@@ -957,7 +987,7 @@ fn visit_instance_use<'tcx>(
957987
source: Span,
958988
output: &mut MonoItems<'tcx>,
959989
) {
960-
debug!("visit_item_use({:?}, is_direct_call={:?})", instance, is_direct_call);
990+
debug!("visit_instance_use({:?}, is_direct_call={:?})", instance, is_direct_call);
961991
if !should_codegen_locally(tcx, &instance) {
962992
return;
963993
}

Diff for: tests/ui/consts/const-eval/erroneous-const.stderr

-15
This file was deleted.

Diff for: tests/ui/consts/const-eval/erroneous-const2.stderr

-15
This file was deleted.

Diff for: tests/ui/consts/const-eval/unused-broken-const-late.stderr

-11
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/dead-code-in-called-fn.rs:9:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/dead-code-in-called-fn.rs:9: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: the above error was encountered while instantiating `fn called::<i32>`
10+
11+
error: aborting due to 1 previous error
12+
13+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/dead-code-in-called-fn.rs:9:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/dead-code-in-called-fn.rs:9: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: the above error was encountered while instantiating `fn called::<i32>`
10+
11+
error: aborting due to 1 previous error
12+
13+
For more information about this error, try `rustc --explain E0080`.
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1+
//@revisions: opt no-opt
12
//@ build-fail
2-
//@ compile-flags: -O
3+
//@[opt] compile-flags: -O
34
//! Make sure we detect erroneous constants post-monomorphization even when they are unused. This is
45
//! crucial, people rely on it for soundness. (https://github.com/rust-lang/rust/issues/112090)
56
6-
struct PrintName<T>(T);
7-
impl<T> PrintName<T> {
8-
const VOID: () = panic!(); //~ERROR evaluation of `PrintName::<i32>::VOID` failed
7+
struct Fail<T>(T);
8+
impl<T> Fail<T> {
9+
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
910
}
1011

11-
fn no_codegen<T>() {
12+
#[inline(never)]
13+
fn called<T>() {
1214
// Any function that is called is guaranteed to have all consts that syntactically
1315
// appear in its body evaluated, even if they only appear in dead code.
16+
// This relies on mono-item collection checking `required_consts` in collected functions.
1417
if false {
15-
let _ = PrintName::<T>::VOID;
18+
let _ = Fail::<T>::C;
1619
}
1720
}
21+
1822
pub fn main() {
19-
no_codegen::<i32>();
23+
called::<i32>();
2024
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/dead-code-in-const-called-fn.rs:8:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/dead-code-in-const-called-fn.rs:8: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/dead-code-in-const-called-fn.rs:17:9
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,17 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/dead-code-in-const-called-fn.rs:8:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/dead-code-in-const-called-fn.rs:8: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/dead-code-in-const-called-fn.rs:17:9
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`.

Diff for: tests/ui/consts/const-eval/erroneous-const.rs renamed to tests/ui/consts/required-consts/dead-code-in-const-called-fn.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1+
//@revisions: opt no-opt
2+
//@[opt] compile-flags: -O
13
//! Make sure we error on erroneous consts even if they are unused.
24
#![allow(unconditional_panic)]
35

4-
struct PrintName<T>(T);
5-
impl<T> PrintName<T> {
6-
const VOID: () = [()][2]; //~ERROR evaluation of `PrintName::<i32>::VOID` failed
6+
struct Fail<T>(T);
7+
impl<T> Fail<T> {
8+
const C: () = panic!(); //~ERROR evaluation of `Fail::<i32>::C` failed
79
}
810

11+
#[inline(never)]
912
const fn no_codegen<T>() {
1013
if false {
1114
// This bad constant is only used in dead code in a no-codegen function... and yet we still
1215
// must make sure that the build fails.
13-
PrintName::<T>::VOID; //~ constant
16+
// This relies on const-eval evaluating all `required_consts` of `const fn`.
17+
Fail::<T>::C; //~ constant
1418
}
1519
}
1620

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0080]: evaluation of `Fail::<i32>::C` failed
2+
--> $DIR/dead-code-in-dead-drop.rs:9:19
3+
|
4+
LL | const C: () = panic!();
5+
| ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/dead-code-in-dead-drop.rs:9: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: the above error was encountered while instantiating `fn <Fail<i32> as std::ops::Drop>::drop`
10+
--> $SRC_DIR/core/src/ptr/mod.rs:LL:COL
11+
12+
error: aborting due to 1 previous error
13+
14+
For more information about this error, try `rustc --explain E0080`.

0 commit comments

Comments
 (0)