1+ //! Breaks outgoing critical edges for call terminators in the MIR.
2+ //!
3+ //! Critical edges are edges that are neither the only edge leaving a
4+ //! block, nor the only edge entering one.
5+ //!
6+ //! When you want something to happen "along" an edge, you can either
7+ //! do at the end of the predecessor block, or at the start of the
8+ //! successor block. Critical edges have to be broken in order to prevent
9+ //! "edge actions" from affecting other edges. We need this for calls that are
10+ //! codegened to LLVM invoke instructions, because invoke is a block terminator
11+ //! in LLVM so we can't insert any code to handle the call's result into the
12+ //! block that performs the call.
13+ //!
14+ //! This function will break those edges by inserting new blocks along them.
15+ //!
16+ //! NOTE: Simplify CFG will happily undo most of the work this pass does.
17+
118use rustc_index:: { Idx , IndexVec } ;
219use rustc_middle:: mir:: * ;
320use rustc_middle:: ty:: TyCtxt ;
@@ -10,26 +27,6 @@ pub(super) enum AddCallGuards {
1027}
1128pub ( super ) use self :: AddCallGuards :: * ;
1229
13- /**
14- * Breaks outgoing critical edges for call terminators in the MIR.
15- *
16- * Critical edges are edges that are neither the only edge leaving a
17- * block, nor the only edge entering one.
18- *
19- * When you want something to happen "along" an edge, you can either
20- * do at the end of the predecessor block, or at the start of the
21- * successor block. Critical edges have to be broken in order to prevent
22- * "edge actions" from affecting other edges. We need this for calls that are
23- * codegened to LLVM invoke instructions, because invoke is a block terminator
24- * in LLVM so we can't insert any code to handle the call's result into the
25- * block that performs the call.
26- *
27- * This function will break those edges by inserting new blocks along them.
28- *
29- * NOTE: Simplify CFG will happily undo most of the work this pass does.
30- *
31- */
32-
3330impl < ' tcx > crate :: MirPass < ' tcx > for AddCallGuards {
3431 fn run_pass ( & self , _tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
3532 let mut pred_count = IndexVec :: from_elem ( 0u8 , & body. basic_blocks ) ;
@@ -39,51 +36,38 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards {
3936 }
4037 }
4138
42- // We need a place to store the new blocks generated
43- let mut new_blocks = Vec :: new ( ) ;
44-
45- let cur_len = body. basic_blocks . len ( ) ;
46- let mut new_block = |source_info : SourceInfo , is_cleanup : bool , target : BasicBlock | {
47- let block = BasicBlockData :: new (
48- Some ( Terminator { source_info, kind : TerminatorKind :: Goto { target } } ) ,
49- is_cleanup,
50- ) ;
51- let idx = cur_len + new_blocks. len ( ) ;
52- new_blocks. push ( block) ;
53- BasicBlock :: new ( idx)
54- } ;
39+ enum Action {
40+ Call ,
41+ Asm { target_index : usize } ,
42+ }
5543
56- for block in body. basic_blocks_mut ( ) {
57- match block . terminator {
58- Some ( Terminator {
59- kind : TerminatorKind :: Call { target : Some ( ref mut destination ) , unwind , .. } ,
60- source_info ,
61- } ) if pred_count[ * destination] > 1
62- && ( generates_invoke ( unwind) || self == & AllCallEdges ) =>
44+ let mut work = Vec :: with_capacity ( body. basic_blocks . len ( ) ) ;
45+ for ( bb , block ) in body . basic_blocks . iter_enumerated ( ) {
46+ let term = block . terminator ( ) ;
47+ match term . kind {
48+ TerminatorKind :: Call { target : Some ( destination ) , unwind , .. }
49+ if pred_count[ destination] > 1
50+ && ( generates_invoke ( unwind) || self == & AllCallEdges ) =>
6351 {
6452 // It's a critical edge, break it
65- * destination = new_block ( source_info , block . is_cleanup , * destination ) ;
53+ work . push ( ( bb , Action :: Call ) ) ;
6654 }
67- Some ( Terminator {
68- kind :
69- TerminatorKind :: InlineAsm {
70- asm_macro : InlineAsmMacro :: Asm ,
71- ref mut targets,
72- ref operands,
73- unwind,
74- ..
75- } ,
76- source_info,
77- } ) if self == & CriticalCallEdges => {
55+ TerminatorKind :: InlineAsm {
56+ asm_macro : InlineAsmMacro :: Asm ,
57+ ref targets,
58+ ref operands,
59+ unwind,
60+ ..
61+ } if self == & CriticalCallEdges => {
7862 let has_outputs = operands. iter ( ) . any ( |op| {
7963 matches ! ( op, InlineAsmOperand :: InOut { .. } | InlineAsmOperand :: Out { .. } )
8064 } ) ;
8165 let has_labels =
8266 operands. iter ( ) . any ( |op| matches ! ( op, InlineAsmOperand :: Label { .. } ) ) ;
8367 if has_outputs && ( has_labels || generates_invoke ( unwind) ) {
84- for target in targets. iter_mut ( ) {
68+ for ( target_index , target) in targets. iter ( ) . enumerate ( ) {
8569 if pred_count[ * target] > 1 {
86- * target = new_block ( source_info , block . is_cleanup , * target ) ;
70+ work . push ( ( bb , Action :: Asm { target_index } ) ) ;
8771 }
8872 }
8973 }
@@ -92,9 +76,50 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards {
9276 }
9377 }
9478
95- debug ! ( "Broke {} N edges" , new_blocks. len( ) ) ;
79+ if work. is_empty ( ) {
80+ return ;
81+ }
9682
97- body. basic_blocks_mut ( ) . extend ( new_blocks) ;
83+ // We need a place to store the new blocks generated
84+ let mut new_blocks = Vec :: with_capacity ( work. len ( ) ) ;
85+
86+ let cur_len = body. basic_blocks . len ( ) ;
87+ let mut new_block = |source_info : SourceInfo , is_cleanup : bool , target : BasicBlock | {
88+ let block = BasicBlockData :: new (
89+ Some ( Terminator { source_info, kind : TerminatorKind :: Goto { target } } ) ,
90+ is_cleanup,
91+ ) ;
92+ let idx = cur_len + new_blocks. len ( ) ;
93+ new_blocks. push ( block) ;
94+ BasicBlock :: new ( idx)
95+ } ;
96+
97+ let basic_blocks = body. basic_blocks . as_mut ( ) ;
98+ for ( source, action) in work {
99+ let block = & mut basic_blocks[ source] ;
100+ let is_cleanup = block. is_cleanup ;
101+ let term = block. terminator_mut ( ) ;
102+ let source_info = term. source_info ;
103+ let destination = match action {
104+ Action :: Call => {
105+ let TerminatorKind :: Call { target : Some ( ref mut destination) , .. } = term. kind
106+ else {
107+ unreachable ! ( )
108+ } ;
109+ destination
110+ }
111+ Action :: Asm { target_index } => {
112+ let TerminatorKind :: InlineAsm { ref mut targets, .. } = term. kind else {
113+ unreachable ! ( )
114+ } ;
115+ & mut targets[ target_index]
116+ }
117+ } ;
118+ * destination = new_block ( source_info, is_cleanup, * destination) ;
119+ }
120+
121+ debug ! ( "Broke {} N edges" , new_blocks. len( ) ) ;
122+ basic_blocks. extend ( new_blocks) ;
98123 }
99124
100125 fn is_required ( & self ) -> bool {
0 commit comments