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
+
1
18
use rustc_index:: { Idx , IndexVec } ;
2
19
use rustc_middle:: mir:: * ;
3
20
use rustc_middle:: ty:: TyCtxt ;
@@ -10,26 +27,6 @@ pub(super) enum AddCallGuards {
10
27
}
11
28
pub ( super ) use self :: AddCallGuards :: * ;
12
29
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
-
33
30
impl < ' tcx > crate :: MirPass < ' tcx > for AddCallGuards {
34
31
fn run_pass ( & self , _tcx : TyCtxt < ' tcx > , body : & mut Body < ' tcx > ) {
35
32
let mut pred_count = IndexVec :: from_elem ( 0u8 , & body. basic_blocks ) ;
@@ -39,51 +36,38 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards {
39
36
}
40
37
}
41
38
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
+ }
55
43
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 ) =>
63
51
{
64
52
// It's a critical edge, break it
65
- * destination = new_block ( source_info , block . is_cleanup , * destination ) ;
53
+ work . push ( ( bb , Action :: Call ) ) ;
66
54
}
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 => {
78
62
let has_outputs = operands. iter ( ) . any ( |op| {
79
63
matches ! ( op, InlineAsmOperand :: InOut { .. } | InlineAsmOperand :: Out { .. } )
80
64
} ) ;
81
65
let has_labels =
82
66
operands. iter ( ) . any ( |op| matches ! ( op, InlineAsmOperand :: Label { .. } ) ) ;
83
67
if has_outputs && ( has_labels || generates_invoke ( unwind) ) {
84
- for target in targets. iter_mut ( ) {
68
+ for ( target_index , target) in targets. iter ( ) . enumerate ( ) {
85
69
if pred_count[ * target] > 1 {
86
- * target = new_block ( source_info , block . is_cleanup , * target ) ;
70
+ work . push ( ( bb , Action :: Asm { target_index } ) ) ;
87
71
}
88
72
}
89
73
}
@@ -92,9 +76,50 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards {
92
76
}
93
77
}
94
78
95
- debug ! ( "Broke {} N edges" , new_blocks. len( ) ) ;
79
+ if work. is_empty ( ) {
80
+ return ;
81
+ }
96
82
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) ;
98
123
}
99
124
100
125
fn is_required ( & self ) -> bool {
0 commit comments