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