3
3
//! post-order traversal of the blocks.
4
4
5
5
use crate :: MirPass ;
6
- use rustc_data_structures:: fx:: { FxHashMap , FxHashSet } ;
6
+ use rustc_data_structures:: fx:: FxHashSet ;
7
+ use rustc_middle:: mir:: interpret:: Scalar ;
8
+ use rustc_middle:: mir:: patch:: MirPatch ;
7
9
use rustc_middle:: mir:: * ;
8
- use rustc_middle:: ty:: TyCtxt ;
10
+ use rustc_middle:: ty:: { self , TyCtxt } ;
11
+ use rustc_target:: abi:: Size ;
9
12
10
13
pub struct UnreachablePropagation ;
11
14
@@ -20,102 +23,134 @@ impl MirPass<'_> for UnreachablePropagation {
20
23
}
21
24
22
25
fn run_pass < ' tcx > ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
26
+ let mut patch = MirPatch :: new ( body) ;
23
27
let mut unreachable_blocks = FxHashSet :: default ( ) ;
24
- let mut replacements = FxHashMap :: default ( ) ;
25
28
26
29
for ( bb, bb_data) in traversal:: postorder ( body) {
27
30
let terminator = bb_data. terminator ( ) ;
28
- if terminator. kind == TerminatorKind :: Unreachable {
29
- unreachable_blocks. insert ( bb) ;
30
- } else {
31
- let is_unreachable = |succ : BasicBlock | unreachable_blocks. contains ( & succ) ;
32
- let terminator_kind_opt = remove_successors ( & terminator. kind , is_unreachable) ;
33
-
34
- if let Some ( terminator_kind) = terminator_kind_opt {
35
- if terminator_kind == TerminatorKind :: Unreachable {
36
- unreachable_blocks. insert ( bb) ;
37
- }
38
- replacements. insert ( bb, terminator_kind) ;
31
+ let is_unreachable = match & terminator. kind {
32
+ TerminatorKind :: Unreachable => true ,
33
+ // This will unconditionally run into an unreachable and is therefore unreachable as well.
34
+ TerminatorKind :: Goto { target } if unreachable_blocks. contains ( target) => {
35
+ patch. patch_terminator ( bb, TerminatorKind :: Unreachable ) ;
36
+ true
37
+ }
38
+ // Try to remove unreachable targets from the switch.
39
+ TerminatorKind :: SwitchInt { .. } => {
40
+ remove_successors_from_switch ( tcx, bb, & unreachable_blocks, body, & mut patch)
39
41
}
42
+ _ => false ,
43
+ } ;
44
+ if is_unreachable {
45
+ unreachable_blocks. insert ( bb) ;
40
46
}
41
47
}
42
48
43
- // We do want do keep some unreachable blocks, but make them empty.
44
- for bb in unreachable_blocks {
45
- if !tcx. consider_optimizing ( || {
46
- format ! ( "UnreachablePropagation {:?} " , body. source. def_id( ) )
47
- } ) {
48
- break ;
49
- }
50
-
51
- body. basic_blocks_mut ( ) [ bb] . statements . clear ( ) ;
49
+ if !tcx
50
+ . consider_optimizing ( || format ! ( "UnreachablePropagation {:?} " , body. source. def_id( ) ) )
51
+ {
52
+ return ;
52
53
}
53
54
54
- for ( bb, terminator_kind) in replacements {
55
- if !tcx. consider_optimizing ( || {
56
- format ! ( "UnreachablePropagation {:?} " , body. source. def_id( ) )
57
- } ) {
58
- break ;
59
- }
55
+ patch. apply ( body) ;
60
56
61
- body. basic_blocks_mut ( ) [ bb] . terminator_mut ( ) . kind = terminator_kind;
57
+ // We do want do keep some unreachable blocks, but make them empty.
58
+ for bb in unreachable_blocks {
59
+ body. basic_blocks_mut ( ) [ bb] . statements . clear ( ) ;
62
60
}
63
-
64
- // Do not remove dead blocks, let `SimplifyCfg` do it.
65
61
}
66
62
}
67
63
68
- fn remove_successors < ' tcx , F > (
69
- terminator_kind : & TerminatorKind < ' tcx > ,
70
- is_unreachable : F ,
71
- ) -> Option < TerminatorKind < ' tcx > >
72
- where
73
- F : Fn ( BasicBlock ) -> bool ,
74
- {
75
- let terminator = match terminator_kind {
76
- // This will unconditionally run into an unreachable and is therefore unreachable as well.
77
- TerminatorKind :: Goto { target } if is_unreachable ( * target) => TerminatorKind :: Unreachable ,
78
- TerminatorKind :: SwitchInt { targets, discr } => {
79
- let otherwise = targets. otherwise ( ) ;
80
-
81
- // If all targets are unreachable, we can be unreachable as well.
82
- if targets. all_targets ( ) . iter ( ) . all ( |bb| is_unreachable ( * bb) ) {
83
- TerminatorKind :: Unreachable
84
- } else if is_unreachable ( otherwise) {
85
- // If there are multiple targets, don't delete unreachable branches (like an unreachable otherwise)
86
- // unless otherwise is unreachable, in which case deleting a normal branch causes it to be merged with
87
- // the otherwise, keeping its unreachable.
88
- // This looses information about reachability causing worse codegen.
89
- // For example (see tests/codegen/match-optimizes-away.rs)
90
- //
91
- // pub enum Two { A, B }
92
- // pub fn identity(x: Two) -> Two {
93
- // match x {
94
- // Two::A => Two::A,
95
- // Two::B => Two::B,
96
- // }
97
- // }
98
- //
99
- // This generates a `switchInt() -> [0: 0, 1: 1, otherwise: unreachable]`, which allows us or LLVM to
100
- // turn it into just `x` later. Without the unreachable, such a transformation would be illegal.
101
- // If the otherwise branch is unreachable, we can delete all other unreachable targets, as they will
102
- // still point to the unreachable and therefore not lose reachability information.
103
- let reachable_iter = targets. iter ( ) . filter ( |( _, bb) | !is_unreachable ( * bb) ) ;
104
-
105
- let new_targets = SwitchTargets :: new ( reachable_iter, otherwise) ;
106
-
107
- // No unreachable branches were removed.
108
- if new_targets. all_targets ( ) . len ( ) == targets. all_targets ( ) . len ( ) {
109
- return None ;
110
- }
64
+ /// Return whether the current terminator is fully unreachable.
65
+ fn remove_successors_from_switch < ' tcx > (
66
+ tcx : TyCtxt < ' tcx > ,
67
+ bb : BasicBlock ,
68
+ unreachable_blocks : & FxHashSet < BasicBlock > ,
69
+ body : & Body < ' tcx > ,
70
+ patch : & mut MirPatch < ' tcx > ,
71
+ ) -> bool {
72
+ let terminator = body. basic_blocks [ bb] . terminator ( ) ;
73
+ let TerminatorKind :: SwitchInt { discr, targets } = & terminator. kind else { bug ! ( ) } ;
74
+ let source_info = terminator. source_info ;
75
+ let location = body. terminator_loc ( bb) ;
76
+
77
+ let is_unreachable = |bb| unreachable_blocks. contains ( & bb) ;
78
+
79
+ // If there are multiple targets, we want to keep information about reachability for codegen.
80
+ // For example (see tests/codegen/match-optimizes-away.rs)
81
+ //
82
+ // pub enum Two { A, B }
83
+ // pub fn identity(x: Two) -> Two {
84
+ // match x {
85
+ // Two::A => Two::A,
86
+ // Two::B => Two::B,
87
+ // }
88
+ // }
89
+ //
90
+ // This generates a `switchInt() -> [0: 0, 1: 1, otherwise: unreachable]`, which allows us or LLVM to
91
+ // turn it into just `x` later. Without the unreachable, such a transformation would be illegal.
92
+ //
93
+ // In order to preserve this information, we record reachable and unreachable targets as
94
+ // `Assume` statements in MIR.
95
+
96
+ let discr_ty = discr. ty ( body, tcx) ;
97
+ let discr_size = Size :: from_bits ( match discr_ty. kind ( ) {
98
+ ty:: Uint ( uint) => uint. normalize ( tcx. sess . target . pointer_width ) . bit_width ( ) . unwrap ( ) ,
99
+ ty:: Int ( int) => int. normalize ( tcx. sess . target . pointer_width ) . bit_width ( ) . unwrap ( ) ,
100
+ ty:: Char => 32 ,
101
+ ty:: Bool => 1 ,
102
+ other => bug ! ( "unhandled type: {:?}" , other) ,
103
+ } ) ;
104
+
105
+ let mut add_assumption = |binop, value| {
106
+ let local = patch. new_temp ( tcx. types . bool , source_info. span ) ;
107
+ let value = Operand :: Constant ( Box :: new ( ConstOperand {
108
+ span : source_info. span ,
109
+ user_ty : None ,
110
+ const_ : Const :: from_scalar ( tcx, Scalar :: from_uint ( value, discr_size) , discr_ty) ,
111
+ } ) ) ;
112
+ let cmp = Rvalue :: BinaryOp ( binop, Box :: new ( ( discr. to_copy ( ) , value) ) ) ;
113
+ patch. add_assign ( location, local. into ( ) , cmp) ;
114
+
115
+ let assume = NonDivergingIntrinsic :: Assume ( Operand :: Move ( local. into ( ) ) ) ;
116
+ patch. add_statement ( location, StatementKind :: Intrinsic ( Box :: new ( assume) ) ) ;
117
+ } ;
111
118
112
- TerminatorKind :: SwitchInt { discr : discr. clone ( ) , targets : new_targets }
113
- } else {
114
- // If the otherwise branch is reachable, we don't want to delete any unreachable branches.
115
- return None ;
116
- }
119
+ let reachable_iter = targets. iter ( ) . filter ( |& ( value, bb) | {
120
+ let is_unreachable = is_unreachable ( bb) ;
121
+ if is_unreachable {
122
+ // We remove this target from the switch, so record the inequality using `Assume`.
123
+ add_assumption ( BinOp :: Ne , value) ;
124
+ false
125
+ } else {
126
+ true
127
+ }
128
+ } ) ;
129
+
130
+ let otherwise = targets. otherwise ( ) ;
131
+ let new_targets = SwitchTargets :: new ( reachable_iter, otherwise) ;
132
+
133
+ let num_targets = new_targets. all_targets ( ) . len ( ) ;
134
+ let otherwise_unreachable = is_unreachable ( otherwise) ;
135
+ let fully_unreachable = num_targets == 1 && otherwise_unreachable;
136
+
137
+ let terminator = match ( num_targets, otherwise_unreachable) {
138
+ // If all targets are unreachable, we can be unreachable as well.
139
+ ( 1 , true ) => TerminatorKind :: Unreachable ,
140
+ ( 1 , false ) => TerminatorKind :: Goto { target : otherwise } ,
141
+ ( 2 , true ) => {
142
+ // All targets are unreachable except one. Record the equality, and make it a goto.
143
+ let ( value, target) = new_targets. iter ( ) . next ( ) . unwrap ( ) ;
144
+ add_assumption ( BinOp :: Eq , value) ;
145
+ TerminatorKind :: Goto { target }
117
146
}
118
- _ => return None ,
147
+ _ if num_targets == targets. all_targets ( ) . len ( ) => {
148
+ // Nothing has changed.
149
+ return false ;
150
+ }
151
+ _ => TerminatorKind :: SwitchInt { discr : discr. clone ( ) , targets : new_targets } ,
119
152
} ;
120
- Some ( terminator)
153
+
154
+ patch. patch_terminator ( bb, terminator) ;
155
+ fully_unreachable
121
156
}
0 commit comments