Skip to content

Commit 61efd19

Browse files
committed
Auto merge of #147477 - cjgillot:split-call-guards, r=tmiasko
Refactor AddCallGuards in two loops. This PR splits the pass into an analysis loop and a change loop. This allows to avoid invalidating CFG caches if there are no changes to be performed. r? `@ghost` for perf
2 parents b6f0945 + 5702fbf commit 61efd19

File tree

1 file changed

+81
-56
lines changed

1 file changed

+81
-56
lines changed

compiler/rustc_mir_transform/src/add_call_guards.rs

Lines changed: 81 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
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+
118
use rustc_index::{Idx, IndexVec};
219
use rustc_middle::mir::*;
320
use rustc_middle::ty::TyCtxt;
@@ -10,26 +27,6 @@ pub(super) enum AddCallGuards {
1027
}
1128
pub(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-
3330
impl<'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

Comments
 (0)