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,80 +27,48 @@ 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 ) ;
33+ pred_count[ START_BLOCK ] = 1 ; // Add entry edge.
3634 for ( _, data) in body. basic_blocks . iter_enumerated ( ) {
3735 for succ in data. terminator ( ) . successors ( ) {
3836 pred_count[ succ] = pred_count[ succ] . saturating_add ( 1 ) ;
3937 }
4038 }
4139
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- } ;
40+ enum Action {
41+ Call ,
42+ Asm { target_index : usize } ,
43+ }
5544
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 ) =>
45+ let mut work = Vec :: with_capacity ( body. basic_blocks . len ( ) ) ;
46+ for ( bb , block ) in body . basic_blocks . iter_enumerated ( ) {
47+ let term = block . terminator ( ) ;
48+ match term . kind {
49+ TerminatorKind :: Call { target : Some ( destination ) , unwind , .. }
50+ if pred_count[ destination] > 1
51+ && ( generates_invoke ( unwind) || self == & AllCallEdges ) =>
6352 {
6453 // It's a critical edge, break it
65- * destination = new_block ( source_info , block . is_cleanup , * destination ) ;
54+ work . push ( ( bb , Action :: Call ) ) ;
6655 }
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 => {
56+ TerminatorKind :: InlineAsm {
57+ asm_macro : InlineAsmMacro :: Asm ,
58+ ref targets,
59+ ref operands,
60+ unwind,
61+ ..
62+ } if self == & CriticalCallEdges => {
7863 let has_outputs = operands. iter ( ) . any ( |op| {
7964 matches ! ( op, InlineAsmOperand :: InOut { .. } | InlineAsmOperand :: Out { .. } )
8065 } ) ;
8166 let has_labels =
8267 operands. iter ( ) . any ( |op| matches ! ( op, InlineAsmOperand :: Label { .. } ) ) ;
8368 if has_outputs && ( has_labels || generates_invoke ( unwind) ) {
84- for target in targets. iter_mut ( ) {
69+ for ( target_index , target) in targets. iter ( ) . enumerate ( ) {
8570 if pred_count[ * target] > 1 {
86- * target = new_block ( source_info , block . is_cleanup , * target ) ;
71+ work . push ( ( bb , Action :: Asm { target_index } ) ) ;
8772 }
8873 }
8974 }
@@ -92,9 +77,50 @@ impl<'tcx> crate::MirPass<'tcx> for AddCallGuards {
9277 }
9378 }
9479
95- debug ! ( "Broke {} N edges" , new_blocks. len( ) ) ;
80+ if work. is_empty ( ) {
81+ return ;
82+ }
83+
84+ // We need a place to store the new blocks generated
85+ let mut new_blocks = Vec :: new ( ) ;
9686
97- body. basic_blocks_mut ( ) . extend ( new_blocks) ;
87+ let cur_len = body. basic_blocks . len ( ) ;
88+ let mut new_block = |source_info : SourceInfo , is_cleanup : bool , target : BasicBlock | {
89+ let block = BasicBlockData :: new (
90+ Some ( Terminator { source_info, kind : TerminatorKind :: Goto { target } } ) ,
91+ is_cleanup,
92+ ) ;
93+ let idx = cur_len + new_blocks. len ( ) ;
94+ new_blocks. push ( block) ;
95+ BasicBlock :: new ( idx)
96+ } ;
97+
98+ let basic_blocks = body. basic_blocks . as_mut ( ) ;
99+ for ( source, action) in work {
100+ let block = & mut basic_blocks[ source] ;
101+ let is_cleanup = block. is_cleanup ;
102+ let term = block. terminator_mut ( ) ;
103+ let source_info = term. source_info ;
104+ match action {
105+ Action :: Call => {
106+ let TerminatorKind :: Call { target : Some ( ref mut destination) , .. } = term. kind
107+ else {
108+ unreachable ! ( )
109+ } ;
110+ * destination = new_block ( source_info, is_cleanup, * destination) ;
111+ }
112+ Action :: Asm { target_index } => {
113+ let TerminatorKind :: InlineAsm { ref mut targets, .. } = term. kind else {
114+ unreachable ! ( )
115+ } ;
116+ targets[ target_index] =
117+ new_block ( source_info, is_cleanup, targets[ target_index] ) ;
118+ }
119+ }
120+ }
121+
122+ debug ! ( "Broke {} N edges" , new_blocks. len( ) ) ;
123+ basic_blocks. extend ( new_blocks) ;
98124 }
99125
100126 fn is_required ( & self ) -> bool {
0 commit comments