Skip to content

Commit 568fe20

Browse files
committed
[EXPERIMENT] Don't monomorphize things that are unused due to if CONST
MIR optimizations have long meant that we do this for concrete constants, but with this it also applies for things like `<T as TrustedRandomAccess>::MAY_HAVE_SIDE_EFFECT`.
1 parent f7c4829 commit 568fe20

23 files changed

+572
-171
lines changed

compiler/rustc_middle/src/mir/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1504,6 +1504,7 @@ impl Statement<'_> {
15041504
}
15051505

15061506
/// Changes a statement to a nop and returns the original statement.
1507+
#[must_use = "If you don't need the statement, use `make_nop` instead"]
15071508
pub fn replace_nop(&mut self) -> Self {
15081509
Statement {
15091510
source_info: self.source_info,

compiler/rustc_mir_transform/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ mod shim;
7171
mod simplify;
7272
mod simplify_branches;
7373
mod simplify_comparison_integral;
74+
mod simplify_if_const;
7475
mod simplify_try;
7576
mod uninhabited_enum_branching;
7677
mod unreachable_prop;
@@ -456,6 +457,7 @@ fn run_post_borrowck_cleanup_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'tc
456457

457458
let post_borrowck_cleanup: &[&dyn MirPass<'tcx>] = &[
458459
// Remove all things only needed by analysis
460+
&simplify_if_const::SimplifyIfConst,
459461
&simplify_branches::SimplifyBranches::new("initial"),
460462
&remove_noop_landing_pads::RemoveNoopLandingPads,
461463
&cleanup_post_borrowck::CleanupNonCodegenStatements,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//! A pass that simplifies branches when their condition is known.
2+
3+
use crate::MirPass;
4+
use rustc_middle::mir::*;
5+
use rustc_middle::ty::TyCtxt;
6+
7+
/// The lowering for `if CONST` produces
8+
/// ```
9+
/// _1 = Const(...);
10+
/// switchInt (move _1)
11+
/// ```
12+
/// so this pass replaces that with
13+
/// ```
14+
/// switchInt (Const(...))
15+
/// ```
16+
/// so that further MIR consumers can special-case it more easily.
17+
///
18+
/// Unlike ConstProp, this supports generic constants too, not just concrete ones.
19+
pub struct SimplifyIfConst;
20+
21+
impl<'tcx> MirPass<'tcx> for SimplifyIfConst {
22+
fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) {
23+
for block in body.basic_blocks_mut() {
24+
simplify_assign_move_switch(tcx, block);
25+
}
26+
}
27+
}
28+
29+
fn simplify_assign_move_switch(tcx: TyCtxt<'_>, block: &mut BasicBlockData<'_>) {
30+
let Some(Terminator { kind: TerminatorKind::SwitchInt { discr: switch_desc, ..}, ..}) =
31+
&mut block.terminator
32+
else { return };
33+
34+
let &mut Operand::Move(switch_place) = &mut*switch_desc
35+
else { return };
36+
37+
let Some(switch_local) = switch_place.as_local()
38+
else { return };
39+
40+
let Some(last_statement) = block.statements.last_mut()
41+
else { return };
42+
43+
let StatementKind::Assign(boxed_place_rvalue) = &last_statement.kind
44+
else { return };
45+
46+
let Some(assigned_local) = boxed_place_rvalue.0.as_local()
47+
else { return };
48+
49+
if switch_local != assigned_local {
50+
return;
51+
}
52+
53+
if !matches!(boxed_place_rvalue.1, Rvalue::Use(Operand::Constant(_))) {
54+
return;
55+
}
56+
57+
let should_optimize = tcx.consider_optimizing(|| {
58+
format!(
59+
"SimplifyBranches - Assignment: {:?} SourceInfo: {:?}",
60+
boxed_place_rvalue, last_statement.source_info
61+
)
62+
});
63+
64+
if should_optimize {
65+
let Some(last_statement) = block.statements.pop()
66+
else { bug!("Somehow the statement disappeared?"); };
67+
68+
let StatementKind::Assign(boxed_place_rvalue) = last_statement.kind
69+
else { bug!("Somehow it's not an assignment any more?"); };
70+
71+
let Rvalue::Use(assigned_constant @ Operand::Constant(_)) = boxed_place_rvalue.1
72+
else { bug!("Somehow it's not a use of a constant any more?"); };
73+
74+
*switch_desc = assigned_constant;
75+
}
76+
}

compiler/rustc_monomorphize/src/collector.rs

+98-2
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ use rustc_hir as hir;
185185
use rustc_hir::def_id::{DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
186186
use rustc_hir::itemlikevisit::ItemLikeVisitor;
187187
use rustc_hir::lang_items::LangItem;
188-
use rustc_index::bit_set::GrowableBitSet;
188+
use rustc_index::bit_set::{BitSet, GrowableBitSet};
189189
use rustc_middle::mir::interpret::{AllocId, ConstValue};
190190
use rustc_middle::mir::interpret::{ErrorHandled, GlobalAlloc, Scalar};
191191
use rustc_middle::mir::mono::{InstantiationMode, MonoItem};
@@ -608,6 +608,7 @@ struct MirNeighborCollector<'a, 'tcx> {
608608
body: &'a mir::Body<'tcx>,
609609
output: &'a mut Vec<Spanned<MonoItem<'tcx>>>,
610610
instance: Instance<'tcx>,
611+
reachable_blocks: BitSet<mir::BasicBlock>,
611612
}
612613

613614
impl<'a, 'tcx> MirNeighborCollector<'a, 'tcx> {
@@ -622,9 +623,95 @@ impl<'a, 'tcx> MirNeighborCollector<'a, 'tcx> {
622623
value,
623624
)
624625
}
626+
627+
fn find_reachable_blocks(&mut self, bb: mir::BasicBlock) {
628+
if !self.reachable_blocks.insert(bb) {
629+
return;
630+
}
631+
632+
use mir::TerminatorKind::*;
633+
let data = &self.body.basic_blocks()[bb];
634+
match data.terminator().kind {
635+
Goto { target } => self.find_reachable_blocks(target),
636+
Resume | Abort | Return | Unreachable | GeneratorDrop => (),
637+
Drop { place: _, target, unwind }
638+
| DropAndReplace { place: _, value: _, target, unwind }
639+
| Assert { cond: _, expected: _, msg: _, target, cleanup: unwind }
640+
| Yield { value: _, resume: target, resume_arg: _, drop: unwind } => {
641+
self.find_reachable_blocks(target);
642+
unwind.map(|b| self.find_reachable_blocks(b));
643+
}
644+
Call { func: _, args: _, destination, cleanup, from_hir_call: _, fn_span: _} => {
645+
destination.map(|(_, b)| self.find_reachable_blocks(b));
646+
cleanup.map(|b| self.find_reachable_blocks(b));
647+
}
648+
FalseEdge { .. } | FalseUnwind { .. } => {
649+
bug!("Expected false edges to already be gone when collecting neighbours for {:?}", self.instance);
650+
}
651+
InlineAsm { template: _, operands: _, options: _, line_spans: _, destination} => {
652+
destination.map(|b| self.find_reachable_blocks(b));
653+
}
654+
SwitchInt { ref discr, switch_ty, ref targets } => {
655+
if let mir::Operand::Constant(constant) = discr {
656+
if let Some(raw_value) = self.try_eval_constant_to_bits(constant, switch_ty) {
657+
// We know what this is going to be,
658+
// so we can ignore all the other blocks.
659+
for (test_value, target) in targets.iter() {
660+
if test_value == raw_value {
661+
return self.find_reachable_blocks(target);
662+
}
663+
}
664+
665+
return self.find_reachable_blocks(targets.otherwise());
666+
}
667+
}
668+
669+
// If it's not a constant or we can't evaluate it,
670+
// then we have to consider them all as reachable.
671+
for &b in targets.all_targets() {
672+
self.find_reachable_blocks(b)
673+
}
674+
}
675+
}
676+
}
677+
678+
fn try_eval_constant_to_bits(&self, constant: &mir::Constant<'tcx>, ty: Ty<'tcx>) -> Option<u128> {
679+
let env = ty::ParamEnv::reveal_all();
680+
let ct = self.monomorphize(constant.literal);
681+
let value = match ct {
682+
mir::ConstantKind::Val(value, _) => value,
683+
mir::ConstantKind::Ty(ct) => {
684+
match ct.val {
685+
ty::ConstKind::Unevaluated(ct) => self
686+
.tcx
687+
.const_eval_resolve(env, ct, None).ok()?,
688+
ty::ConstKind::Value(value) => value,
689+
other => span_bug!(
690+
constant.span,
691+
"encountered bad ConstKind after monomorphizing: {:?}",
692+
other
693+
),
694+
}
695+
}
696+
};
697+
value.try_to_bits_for_ty(self.tcx, env, ty)
698+
}
625699
}
626700

627701
impl<'a, 'tcx> MirVisitor<'tcx> for MirNeighborCollector<'a, 'tcx> {
702+
fn visit_body(&mut self, body: &mir::Body<'tcx>) {
703+
self.find_reachable_blocks(mir::START_BLOCK);
704+
self.super_body(body);
705+
}
706+
707+
fn visit_basic_block_data(&mut self, block: mir::BasicBlock, data: &mir::BasicBlockData<'tcx>) {
708+
if self.reachable_blocks.contains(block) {
709+
self.super_basic_block_data(block, data);
710+
} else {
711+
debug!("skipping basic block {:?}", block);
712+
}
713+
}
714+
628715
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
629716
debug!("visiting rvalue {:?}", *rvalue);
630717

@@ -1395,7 +1482,16 @@ fn collect_neighbours<'tcx>(
13951482
debug!("collect_neighbours: {:?}", instance.def_id());
13961483
let body = tcx.instance_mir(instance.def);
13971484

1398-
MirNeighborCollector { tcx, body: &body, output, instance }.visit_body(&body);
1485+
let reachable_blocks =
1486+
if instance.substs.is_noop() {
1487+
// If it's non-generic, then it's already filtered out
1488+
// any blocks that are unreachable, so don't re-do the work.
1489+
BitSet::new_filled(body.basic_blocks().len())
1490+
} else {
1491+
BitSet::new_empty(body.basic_blocks().len())
1492+
};
1493+
let mut collector = MirNeighborCollector { tcx, body: &body, output, instance, reachable_blocks };
1494+
collector.visit_body(&body);
13991495
}
14001496

14011497
fn collect_const_value<'tcx>(

library/core/src/ptr/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,13 @@ pub const unsafe fn swap_nonoverlapping<T>(x: *mut T, y: *mut T, count: usize) {
430430
#[inline]
431431
#[rustc_const_unstable(feature = "const_swap", issue = "83163")]
432432
pub(crate) const unsafe fn swap_nonoverlapping_one<T>(x: *mut T, y: *mut T) {
433+
trait TypeSizeCheck {
434+
const IS_CHUNK_SIZE_OR_LARGER: bool;
435+
}
436+
impl<T> TypeSizeCheck for T {
437+
const IS_CHUNK_SIZE_OR_LARGER: bool = mem::size_of::<T>() >= 32;
438+
}
439+
433440
// NOTE(eddyb) SPIR-V's Logical addressing model doesn't allow for arbitrary
434441
// reinterpretation of values as (chunkable) byte arrays, and the loop in the
435442
// block optimization in `swap_nonoverlapping_bytes` is hard to rewrite back
@@ -442,7 +449,7 @@ pub(crate) const unsafe fn swap_nonoverlapping_one<T>(x: *mut T, y: *mut T) {
442449
{
443450
// Only apply the block optimization in `swap_nonoverlapping_bytes` for types
444451
// at least as large as the block size, to avoid pessimizing codegen.
445-
if mem::size_of::<T>() >= 32 {
452+
if T::IS_CHUNK_SIZE_OR_LARGER {
446453
// SAFETY: the caller must uphold the safety contract for `swap_nonoverlapping`.
447454
unsafe { swap_nonoverlapping(x, y, 1) };
448455
return;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// compile-flags: -O -C no-prepopulate-passes
2+
3+
#![crate_type = "lib"]
4+
5+
#[no_mangle]
6+
pub fn demo_for_i32() {
7+
generic_impl::<i32>();
8+
}
9+
10+
// CHECK-LABEL: ; skip_mono_inside_if_false::generic_impl
11+
// CHECK: start:
12+
// CHECK-NEXT: br i1 false, label %[[THEN_BRANCH:bb[0-9]+]], label %[[ELSE_BRANCH:bb[0-9]+]]
13+
// CHECK: [[ELSE_BRANCH]]:
14+
// CHECK-NEXT: call skip_mono_inside_if_false::small_impl
15+
// CHECK: [[THEN_BRANCH]]:
16+
// CHECK-NEXT: call skip_mono_inside_if_false::big_impl
17+
18+
// Then despite there being calls to both of them, only the ones that's used has a definition.
19+
// The other is only forward-declared, and its use will disappear with LLVM's simplifycfg.
20+
21+
// CHECK: define internal void @_ZN25skip_mono_inside_if_false10small_impl
22+
// CHECK: declare hidden void @_ZN25skip_mono_inside_if_false8big_impl
23+
24+
fn generic_impl<T>() {
25+
trait MagicTrait {
26+
const IS_BIG: bool;
27+
}
28+
impl<T> MagicTrait for T {
29+
const IS_BIG: bool = std::mem::size_of::<T>() > 10;
30+
}
31+
if T::IS_BIG {
32+
big_impl::<T>();
33+
} else {
34+
small_impl::<T>();
35+
}
36+
}
37+
38+
#[inline(never)]
39+
fn small_impl<T>() {}
40+
#[inline(never)]
41+
fn big_impl<T>() {}

src/test/mir-opt/const_prop/control-flow-simplification.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// compile-flags: -Zmir-opt-level=1
22

3-
trait NeedsDrop:Sized{
4-
const NEEDS:bool=std::mem::needs_drop::<Self>();
3+
trait HasSize: Sized {
4+
const BYTES:usize = std::mem::size_of::<Self>();
55
}
66

7-
impl<This> NeedsDrop for This{}
7+
impl<This> HasSize for This{}
88

99
// EMIT_MIR control_flow_simplification.hello.ConstProp.diff
1010
// EMIT_MIR control_flow_simplification.hello.PreCodegen.before.mir
1111
fn hello<T>(){
12-
if <bool>::NEEDS {
12+
if <bool>::BYTES > 10 {
1313
panic!()
1414
}
1515
}

src/test/mir-opt/const_prop/control_flow_simplification.hello.ConstProp.diff

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33

44
fn hello() -> () {
55
let mut _0: (); // return place in scope 0 at $DIR/control-flow-simplification.rs:11:14: 11:14
6-
let mut _1: bool; // in scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:21
6+
let mut _1: bool; // in scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:26
77
let mut _2: !; // in scope 0 at $SRC_DIR/std/src/panic.rs:LL:COL
88

99
bb0: {
10-
StorageLive(_1); // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:21
11-
- _1 = const <bool as NeedsDrop>::NEEDS; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:21
12-
- switchInt(move _1) -> [false: bb2, otherwise: bb1]; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:21
13-
+ _1 = const false; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:21
14-
+ switchInt(const false) -> [false: bb2, otherwise: bb1]; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:21
10+
StorageLive(_1); // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:26
11+
- _1 = Gt(const <bool as HasSize>::BYTES, const 10_usize); // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:26
12+
- switchInt(move _1) -> [false: bb2, otherwise: bb1]; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:26
13+
+ _1 = const false; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:26
14+
+ switchInt(const false) -> [false: bb2, otherwise: bb1]; // scope 0 at $DIR/control-flow-simplification.rs:12:8: 12:26
1515
}
1616

1717
bb1: {

0 commit comments

Comments
 (0)