Skip to content

Commit ea28252

Browse files
committed
Auto merge of #147654 - dianqk:simplify-const-condition, r=cjgillot
Simplify trivial constants in SimplifyConstCondition After `InstSimplify-after-simplifycfg` with `-Zub_checks=false`, there are many of the following patterns. ``` _13 = const false; assume(copy _13); _12 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable]; ``` Simplifying them to unreachable early should make CFG simpler.
2 parents a41214f + 7af5775 commit ea28252

25 files changed

+696
-336
lines changed

compiler/rustc_middle/src/mir/terminator.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,28 @@ impl<'tcx> TerminatorKind<'tcx> {
696696
_ => None,
697697
}
698698
}
699+
700+
/// Returns true if the terminator can write to memory.
701+
pub fn can_write_to_memory(&self) -> bool {
702+
match self {
703+
TerminatorKind::Goto { .. }
704+
| TerminatorKind::SwitchInt { .. }
705+
| TerminatorKind::UnwindResume
706+
| TerminatorKind::UnwindTerminate(_)
707+
| TerminatorKind::Return
708+
| TerminatorKind::Assert { .. }
709+
| TerminatorKind::CoroutineDrop
710+
| TerminatorKind::FalseEdge { .. }
711+
| TerminatorKind::FalseUnwind { .. }
712+
| TerminatorKind::Unreachable => false,
713+
TerminatorKind::Call { .. }
714+
| TerminatorKind::Drop { .. }
715+
| TerminatorKind::TailCall { .. }
716+
// Yield writes to the resume_arg place.
717+
| TerminatorKind::Yield { .. }
718+
| TerminatorKind::InlineAsm { .. } => true,
719+
}
720+
}
699721
}
700722

701723
#[derive(Copy, Clone, Debug)]

compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,13 +1926,8 @@ impl<'tcx> MutVisitor<'tcx> for VnState<'_, '_, 'tcx> {
19261926
self.assign(local, opaque);
19271927
}
19281928
}
1929-
// Function calls and ASM may invalidate (nested) derefs. We must handle them carefully.
1930-
// Currently, only preserving derefs for trivial terminators like SwitchInt and Goto.
1931-
let safe_to_preserve_derefs = matches!(
1932-
terminator.kind,
1933-
TerminatorKind::SwitchInt { .. } | TerminatorKind::Goto { .. }
1934-
);
1935-
if !safe_to_preserve_derefs {
1929+
// Terminators that can write to memory may invalidate (nested) derefs.
1930+
if terminator.kind.can_write_to_memory() {
19361931
self.invalidate_derefs();
19371932
}
19381933
self.super_terminator(terminator, location);

compiler/rustc_mir_transform/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ declare_passes! {
189189
Final
190190
};
191191
mod simplify_branches : SimplifyConstCondition {
192+
AfterInstSimplify,
192193
AfterConstProp,
193194
Final
194195
};
@@ -708,6 +709,15 @@ pub(crate) fn run_optimization_passes<'tcx>(tcx: TyCtxt<'tcx>, body: &mut Body<'
708709
// optimizations. This invalidates CFG caches, so avoid putting between
709710
// `ReferencePropagation` and `GVN` which both use the dominator tree.
710711
&instsimplify::InstSimplify::AfterSimplifyCfg,
712+
// After `InstSimplify-after-simplifycfg` with `-Zub_checks=false`, simplify
713+
// ```
714+
// _13 = const false;
715+
// assume(copy _13);
716+
// Call(precondition_check);
717+
// ```
718+
// to unreachable to eliminate the call to help later passes.
719+
// This invalidates CFG caches also.
720+
&o1(simplify_branches::SimplifyConstCondition::AfterInstSimplify),
711721
&ref_prop::ReferencePropagation,
712722
&sroa::ScalarReplacementOfAggregates,
713723
&simplify::SimplifyLocals::BeforeConstProp,

compiler/rustc_mir_transform/src/simplify_branches.rs

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use tracing::trace;
55
use crate::patch::MirPatch;
66

77
pub(super) enum SimplifyConstCondition {
8+
AfterInstSimplify,
89
AfterConstProp,
910
Final,
1011
}
@@ -13,6 +14,9 @@ pub(super) enum SimplifyConstCondition {
1314
impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition {
1415
fn name(&self) -> &'static str {
1516
match self {
17+
SimplifyConstCondition::AfterInstSimplify => {
18+
"SimplifyConstCondition-after-inst-simplify"
19+
}
1620
SimplifyConstCondition::AfterConstProp => "SimplifyConstCondition-after-const-prop",
1721
SimplifyConstCondition::Final => "SimplifyConstCondition-final",
1822
}
@@ -23,12 +27,33 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition {
2327
let typing_env = body.typing_env(tcx);
2428
let mut patch = MirPatch::new(body);
2529

30+
fn try_get_const<'tcx, 'a>(
31+
operand: &'a Operand<'tcx>,
32+
has_place_const: Option<(Place<'tcx>, &'a ConstOperand<'tcx>)>,
33+
) -> Option<&'a ConstOperand<'tcx>> {
34+
match operand {
35+
Operand::Constant(const_operand) => Some(const_operand),
36+
// `has_place_const` must be the LHS of the previous statement.
37+
// Soundness: There is nothing can modify the place, as there are no statements between the two statements.
38+
Operand::Copy(place) | Operand::Move(place)
39+
if let Some((place_const, const_operand)) = has_place_const
40+
&& place_const == *place =>
41+
{
42+
Some(const_operand)
43+
}
44+
Operand::Copy(_) | Operand::Move(_) => None,
45+
}
46+
}
47+
2648
'blocks: for (bb, block) in body.basic_blocks.iter_enumerated() {
49+
let mut pre_place_const: Option<(Place<'tcx>, &ConstOperand<'tcx>)> = None;
50+
2751
for (statement_index, stmt) in block.statements.iter().enumerate() {
52+
let has_place_const = pre_place_const.take();
2853
// Simplify `assume` of a known value: either a NOP or unreachable.
2954
if let StatementKind::Intrinsic(box ref intrinsic) = stmt.kind
3055
&& let NonDivergingIntrinsic::Assume(discr) = intrinsic
31-
&& let Operand::Constant(c) = discr
56+
&& let Some(c) = try_get_const(discr, has_place_const)
3257
&& let Some(constant) = c.const_.try_eval_bool(tcx, typing_env)
3358
{
3459
if constant {
@@ -37,28 +62,29 @@ impl<'tcx> crate::MirPass<'tcx> for SimplifyConstCondition {
3762
patch.patch_terminator(bb, TerminatorKind::Unreachable);
3863
continue 'blocks;
3964
}
65+
} else if let StatementKind::Assign(box (lhs, ref rvalue)) = stmt.kind
66+
&& let Rvalue::Use(Operand::Constant(c)) = rvalue
67+
{
68+
pre_place_const = Some((lhs, c));
4069
}
4170
}
4271

4372
let terminator = block.terminator();
4473
let terminator = match terminator.kind {
45-
TerminatorKind::SwitchInt {
46-
discr: Operand::Constant(ref c), ref targets, ..
47-
} => {
48-
let constant = c.const_.try_eval_bits(tcx, typing_env);
49-
if let Some(constant) = constant {
50-
let target = targets.target_for_value(constant);
51-
TerminatorKind::Goto { target }
52-
} else {
53-
continue;
54-
}
74+
TerminatorKind::SwitchInt { ref discr, ref targets, .. }
75+
if let Some(c) = try_get_const(discr, pre_place_const.take())
76+
&& let Some(constant) = c.const_.try_eval_bits(tcx, typing_env) =>
77+
{
78+
let target = targets.target_for_value(constant);
79+
TerminatorKind::Goto { target }
80+
}
81+
TerminatorKind::Assert { target, ref cond, expected, .. }
82+
if let Some(c) = try_get_const(&cond, pre_place_const.take())
83+
&& let Some(constant) = c.const_.try_eval_bool(tcx, typing_env)
84+
&& constant == expected =>
85+
{
86+
TerminatorKind::Goto { target }
5587
}
56-
TerminatorKind::Assert {
57-
target, cond: Operand::Constant(ref c), expected, ..
58-
} => match c.const_.try_eval_bool(tcx, typing_env) {
59-
Some(v) if v == expected => TerminatorKind::Goto { target },
60-
_ => continue,
61-
},
6288
_ => continue,
6389
};
6490
patch.patch_terminator(bb, terminator);

tests/mir-opt/const_prop/control_flow_simplification.hello.GVN.panic-abort.diff

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,17 @@
33

44
fn hello() -> () {
55
let mut _0: ();
6-
let mut _1: bool;
7-
let mut _2: !;
6+
let mut _1: !;
87

98
bb0: {
10-
StorageLive(_1);
11-
- _1 = const <bool as NeedsDrop>::NEEDS;
12-
- switchInt(move _1) -> [0: bb2, otherwise: bb1];
13-
+ _1 = const false;
14-
+ switchInt(const false) -> [0: bb2, otherwise: bb1];
9+
goto -> bb2;
1510
}
1611

1712
bb1: {
18-
_2 = begin_panic::<&str>(const "explicit panic") -> unwind unreachable;
13+
_1 = begin_panic::<&str>(const "explicit panic") -> unwind unreachable;
1914
}
2015

2116
bb2: {
22-
StorageDead(_1);
2317
return;
2418
}
2519
}

tests/mir-opt/const_prop/control_flow_simplification.hello.GVN.panic-unwind.diff

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,17 @@
33

44
fn hello() -> () {
55
let mut _0: ();
6-
let mut _1: bool;
7-
let mut _2: !;
6+
let mut _1: !;
87

98
bb0: {
10-
StorageLive(_1);
11-
- _1 = const <bool as NeedsDrop>::NEEDS;
12-
- switchInt(move _1) -> [0: bb2, otherwise: bb1];
13-
+ _1 = const false;
14-
+ switchInt(const false) -> [0: bb2, otherwise: bb1];
9+
goto -> bb2;
1510
}
1611

1712
bb1: {
18-
_2 = begin_panic::<&str>(const "explicit panic") -> unwind continue;
13+
_1 = begin_panic::<&str>(const "explicit panic") -> unwind continue;
1914
}
2015

2116
bb2: {
22-
StorageDead(_1);
2317
return;
2418
}
2519
}

tests/mir-opt/const_prop/control_flow_simplification.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
// skip-filecheck
21
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
32
//@ test-mir-pass: GVN
43
//@ compile-flags: -Zmir-opt-level=1
@@ -12,6 +11,9 @@ impl<This> NeedsDrop for This {}
1211
// EMIT_MIR control_flow_simplification.hello.GVN.diff
1312
// EMIT_MIR control_flow_simplification.hello.PreCodegen.before.mir
1413
fn hello<T>() {
14+
// CHECK-LABEL: fn hello(
15+
// CHECK: bb0:
16+
// CHECK-NEXT: return;
1517
if <bool>::NEEDS {
1618
panic!()
1719
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
//@ test-mir-pass: SimplifyConstCondition-after-inst-simplify
2+
//@ compile-flags: -Zmir-enable-passes=+InstSimplify-after-simplifycfg -Zub_checks=false -Zinline-mir
3+
4+
#![crate_type = "lib"]
5+
6+
// EMIT_MIR trivial_const.unwrap_unchecked.SimplifyConstCondition-after-inst-simplify.diff
7+
pub fn unwrap_unchecked(v: &Option<i32>) -> i32 {
8+
// CHECK-LABEL: fn unwrap_unchecked(
9+
// CHECK: bb0: {
10+
// CHECK: switchInt({{.*}}) -> [0: [[AssumeFalseBB:bb.*]], 1:
11+
// CHECK: [[AssumeFalseBB]]: {
12+
// CHECK-NEXT: unreachable;
13+
let v = unsafe { v.unwrap_unchecked() };
14+
v
15+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
- // MIR for `unwrap_unchecked` before SimplifyConstCondition-after-inst-simplify
2+
+ // MIR for `unwrap_unchecked` after SimplifyConstCondition-after-inst-simplify
3+
4+
fn unwrap_unchecked(_1: &Option<i32>) -> i32 {
5+
debug v => _1;
6+
let mut _0: i32;
7+
let _2: i32;
8+
let mut _3: std::option::Option<i32>;
9+
scope 1 {
10+
debug v => _2;
11+
}
12+
scope 2 (inlined #[track_caller] Option::<i32>::unwrap_unchecked) {
13+
let mut _4: isize;
14+
scope 3 {
15+
}
16+
scope 4 (inlined #[track_caller] unreachable_unchecked) {
17+
let _5: ();
18+
scope 5 (inlined core::ub_checks::check_language_ub) {
19+
let mut _6: bool;
20+
scope 6 (inlined core::ub_checks::check_language_ub::runtime) {
21+
}
22+
}
23+
}
24+
}
25+
26+
bb0: {
27+
StorageLive(_2);
28+
StorageLive(_3);
29+
_3 = copy (*_1);
30+
StorageLive(_4);
31+
StorageLive(_5);
32+
_4 = discriminant(_3);
33+
switchInt(move _4) -> [0: bb2, 1: bb3, otherwise: bb1];
34+
}
35+
36+
bb1: {
37+
unreachable;
38+
}
39+
40+
bb2: {
41+
- StorageLive(_6);
42+
- _6 = const false;
43+
- assume(copy _6);
44+
- _5 = unreachable_unchecked::precondition_check() -> [return: bb1, unwind unreachable];
45+
+ unreachable;
46+
}
47+
48+
bb3: {
49+
_2 = move ((_3 as Some).0: i32);
50+
StorageDead(_5);
51+
StorageDead(_4);
52+
StorageDead(_3);
53+
_0 = copy _2;
54+
StorageDead(_2);
55+
return;
56+
}
57+
}
58+

tests/mir-opt/dont_reset_cast_kind_without_updating_operand.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
// skip-filecheck
2-
//@ compile-flags: -Zmir-enable-passes=+Inline,+GVN --crate-type lib
1+
//@ test-mir-pass: GVN
2+
//@ compile-flags: -Zinline-mir --crate-type lib
33
// EMIT_MIR_FOR_EACH_BIT_WIDTH
44
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
55
// EMIT_MIR dont_reset_cast_kind_without_updating_operand.test.GVN.diff
66

77
fn test() {
8+
// CHECK-LABEL: fn test(
9+
// CHECK: debug slf => [[SLF:_.*]];
10+
// CHECK: debug _x => [[X:_.*]];
11+
// CHECK: [[X]] = copy [[SLF]] as *mut () (PtrToPtr);
812
let vp_ctx: &Box<()> = &Box::new(());
913
let slf: *const () = &raw const **vp_ctx;
1014
let bytes = std::ptr::slice_from_raw_parts(slf, 1);

0 commit comments

Comments
 (0)