33//! post-order traversal of the blocks.
44
55use 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 ;
79use rustc_middle:: mir:: * ;
8- use rustc_middle:: ty:: TyCtxt ;
10+ use rustc_middle:: ty:: { self , TyCtxt } ;
11+ use rustc_target:: abi:: Size ;
912
1013pub struct UnreachablePropagation ;
1114
@@ -20,102 +23,134 @@ impl MirPass<'_> for UnreachablePropagation {
2023 }
2124
2225 fn run_pass < ' tcx > ( & self , tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
26+ let mut patch = MirPatch :: new ( body) ;
2327 let mut unreachable_blocks = FxHashSet :: default ( ) ;
24- let mut replacements = FxHashMap :: default ( ) ;
2528
2629 for ( bb, bb_data) in traversal:: postorder ( body) {
2730 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)
3941 }
42+ _ => false ,
43+ } ;
44+ if is_unreachable {
45+ unreachable_blocks. insert ( bb) ;
4046 }
4147 }
4248
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 ;
5253 }
5354
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) ;
6056
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 ( ) ;
6260 }
63-
64- // Do not remove dead blocks, let `SimplifyCfg` do it.
6561 }
6662}
6763
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+ } ;
111118
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 }
117146 }
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 } ,
119152 } ;
120- Some ( terminator)
153+
154+ patch. patch_terminator ( bb, terminator) ;
155+ fully_unreachable
121156}
0 commit comments