Skip to content

Commit a9d46c7

Browse files
Check block transfer function cache outside hot loop
Keeping this DRY required a refactor since we cannot borrow `Engine` mutably while passing a closure that also holds a reference to it. This refactor also cleans up `propagate_bits_into_graph_successors_of`, mostly to keep line lengths in check.
1 parent ecd67ee commit a9d46c7

File tree

1 file changed

+129
-109
lines changed

1 file changed

+129
-109
lines changed

src/librustc_mir/dataflow/generic/engine.rs

+129-109
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ pub struct Engine<'a, 'tcx, A>
2121
where
2222
A: Analysis<'tcx>,
2323
{
24-
bits_per_block: usize,
2524
tcx: TyCtxt<'tcx>,
2625
body: &'a mir::Body<'tcx>,
2726
def_id: DefId,
2827
dead_unwinds: Option<&'a BitSet<BasicBlock>>,
29-
entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
3028
analysis: A,
3129

3230
/// Cached, cumulative transfer functions for each block.
31+
///
32+
/// These are only computable for gen-kill problems.
3333
trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
3434
}
3535

@@ -98,25 +98,12 @@ where
9898
analysis: A,
9999
trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
100100
) -> Self {
101-
let bits_per_block = analysis.bits_per_block(body);
102-
103-
let bottom_value_set = if A::BOTTOM_VALUE == true {
104-
BitSet::new_filled(bits_per_block)
105-
} else {
106-
BitSet::new_empty(bits_per_block)
107-
};
108-
109-
let mut entry_sets = IndexVec::from_elem(bottom_value_set, body.basic_blocks());
110-
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
111-
112101
Engine {
113102
analysis,
114-
bits_per_block,
115103
tcx,
116104
body,
117105
def_id,
118106
dead_unwinds: None,
119-
entry_sets,
120107
trans_for_block,
121108
}
122109
}
@@ -126,37 +113,39 @@ where
126113
self
127114
}
128115

129-
pub fn iterate_to_fixpoint(mut self) -> Results<'tcx, A> {
130-
let mut temp_state = BitSet::new_empty(self.bits_per_block);
131-
132-
let mut dirty_queue: WorkQueue<BasicBlock> =
133-
WorkQueue::with_none(self.body.basic_blocks().len());
134-
135-
for (bb, _) in traversal::reverse_postorder(self.body) {
136-
dirty_queue.insert(bb);
137-
}
138-
139-
// Add blocks that are not reachable from START_BLOCK to the work queue. These blocks will
140-
// be processed after the ones added above.
141-
for bb in self.body.basic_blocks().indices() {
142-
dirty_queue.insert(bb);
143-
}
116+
pub fn iterate_to_fixpoint(self) -> Results<'tcx, A> {
117+
// Initialize the entry sets for each block.
144118

145-
while let Some(bb) = dirty_queue.pop() {
146-
let bb_data = &self.body[bb];
147-
let on_entry = &self.entry_sets[bb];
119+
let bits_per_block = self.analysis.bits_per_block(self.body);
120+
let bottom_value_set = if A::BOTTOM_VALUE == true {
121+
BitSet::new_filled(bits_per_block)
122+
} else {
123+
BitSet::new_empty(bits_per_block)
124+
};
148125

149-
temp_state.overwrite(on_entry);
150-
self.apply_whole_block_effect(&mut temp_state, bb, bb_data);
126+
let mut entry_sets = IndexVec::from_elem(bottom_value_set, self.body.basic_blocks());
127+
self.analysis.initialize_start_block(self.body, &mut entry_sets[mir::START_BLOCK]);
151128

152-
self.propagate_bits_into_graph_successors_of(
153-
&mut temp_state,
154-
(bb, bb_data),
155-
&mut dirty_queue,
129+
// To improve performance, we check for the existence of cached block transfer functions
130+
// *outside* the loop in `_iterate_to_fixpoint` below.
131+
if let Some(trans_for_block) = &self.trans_for_block {
132+
self._iterate_to_fixpoint(
133+
bits_per_block,
134+
&mut entry_sets,
135+
|state, bb| trans_for_block[bb].apply(state),
136+
);
137+
} else {
138+
self._iterate_to_fixpoint(
139+
bits_per_block,
140+
&mut entry_sets,
141+
|state, bb| {
142+
let block_data = &self.body[bb];
143+
apply_whole_block_effect(&self.analysis, state, bb, block_data);
144+
}
156145
);
157146
}
158147

159-
let Engine { tcx, body, def_id, trans_for_block, entry_sets, analysis, .. } = self;
148+
let Engine { tcx, def_id, body, analysis, trans_for_block, .. } = self;
160149
let results = Results { analysis, entry_sets };
161150

162151
let res = write_graphviz_results(tcx, def_id, body, &results, trans_for_block);
@@ -167,124 +156,155 @@ where
167156
results
168157
}
169158

170-
/// Applies the cumulative effect of an entire block, excluding the call return effect if one
171-
/// exists.
172-
fn apply_whole_block_effect(
159+
/// Helper function that propagates dataflow state into graph succesors until fixpoint is
160+
/// reached.
161+
fn _iterate_to_fixpoint(
173162
&self,
174-
state: &mut BitSet<A::Idx>,
175-
block: BasicBlock,
176-
block_data: &mir::BasicBlockData<'tcx>,
163+
bits_per_block: usize,
164+
entry_sets: &mut IndexVec<BasicBlock, BitSet<A::Idx>>,
165+
apply_block_effect: impl Fn(&mut BitSet<A::Idx>, BasicBlock),
177166
) {
178-
// Use the cached block transfer function if available.
179-
if let Some(trans_for_block) = &self.trans_for_block {
180-
trans_for_block[block].apply(state);
181-
return;
167+
let body = self.body;
168+
let mut state = BitSet::new_empty(bits_per_block);
169+
170+
let mut dirty_queue: WorkQueue<BasicBlock> =
171+
WorkQueue::with_none(body.basic_blocks().len());
172+
173+
for (bb, _) in traversal::reverse_postorder(body) {
174+
dirty_queue.insert(bb);
182175
}
183176

184-
// Otherwise apply effects one-by-one.
177+
// Add blocks that are not reachable from START_BLOCK to the work queue. These blocks will
178+
// be processed after the ones added above.
179+
for bb in body.basic_blocks().indices() {
180+
dirty_queue.insert(bb);
181+
}
182+
183+
while let Some(bb) = dirty_queue.pop() {
184+
state.overwrite(&entry_sets[bb]);
185+
apply_block_effect(&mut state, bb);
185186

186-
for (statement_index, statement) in block_data.statements.iter().enumerate() {
187-
let location = Location { block, statement_index };
188-
self.analysis.apply_before_statement_effect(state, statement, location);
189-
self.analysis.apply_statement_effect(state, statement, location);
187+
self.propagate_bits_into_graph_successors_of(
188+
entry_sets,
189+
&mut state,
190+
(bb, &body[bb]),
191+
&mut dirty_queue,
192+
);
190193
}
194+
}
191195

192-
let terminator = block_data.terminator();
193-
let location = Location { block, statement_index: block_data.statements.len() };
194-
self.analysis.apply_before_terminator_effect(state, terminator, location);
195-
self.analysis.apply_terminator_effect(state, terminator, location);
196+
fn propagate_state_to(
197+
&self,
198+
bb: BasicBlock,
199+
state: &BitSet<A::Idx>,
200+
entry_sets: &mut IndexVec<BasicBlock, BitSet<A::Idx>>,
201+
dirty_queue: &mut WorkQueue<BasicBlock>,
202+
) {
203+
let entry_set = &mut entry_sets[bb];
204+
let set_changed = self.analysis.join(entry_set, state);
205+
if set_changed {
206+
dirty_queue.insert(bb);
207+
}
196208
}
197209

198210
fn propagate_bits_into_graph_successors_of(
199-
&mut self,
200-
in_out: &mut BitSet<A::Idx>,
201-
(bb, bb_data): (BasicBlock, &'a mir::BasicBlockData<'tcx>),
202-
dirty_list: &mut WorkQueue<BasicBlock>,
211+
&self,
212+
entry_sets: &mut IndexVec<BasicBlock, BitSet<A::Idx>>,
213+
exit_state: &mut BitSet<A::Idx>,
214+
(bb, bb_data): (BasicBlock, &mir::BasicBlockData<'tcx>),
215+
dirty: &mut WorkQueue<BasicBlock>,
203216
) {
204-
use mir::TerminatorKind::*;
205-
206-
// FIXME: This should be implemented using a `for_each_successor` method on
207-
// `TerminatorKind`.
217+
use mir::TerminatorKind;
208218

209219
match bb_data.terminator().kind {
210-
| Return
211-
| Resume
212-
| Abort
213-
| GeneratorDrop
214-
| Unreachable
220+
| TerminatorKind::Return
221+
| TerminatorKind::Resume
222+
| TerminatorKind::Abort
223+
| TerminatorKind::GeneratorDrop
224+
| TerminatorKind::Unreachable
215225
=> {}
216226

217-
| Goto { target }
218-
| Assert { target, cleanup: None, .. }
219-
| Yield { resume: target, drop: None, .. }
220-
| Drop { target, location: _, unwind: None }
221-
| DropAndReplace { target, value: _, location: _, unwind: None }
222-
=> self.propagate_bits_into_entry_set_for(in_out, target, dirty_list),
227+
| TerminatorKind::Goto { target }
228+
| TerminatorKind::Assert { target, cleanup: None, .. }
229+
| TerminatorKind::Yield { resume: target, drop: None, .. }
230+
| TerminatorKind::Drop { target, location: _, unwind: None }
231+
| TerminatorKind::DropAndReplace { target, value: _, location: _, unwind: None }
232+
=> self.propagate_state_to(target, exit_state, entry_sets, dirty),
223233

224-
Yield { resume: target, drop: Some(drop), .. } => {
225-
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
226-
self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
234+
TerminatorKind::Yield { resume: target, drop: Some(drop), .. } => {
235+
self.propagate_state_to(target, exit_state, entry_sets, dirty);
236+
self.propagate_state_to(drop, exit_state, entry_sets, dirty);
227237
}
228238

229-
| Assert { target, cleanup: Some(unwind), .. }
230-
| Drop { target, location: _, unwind: Some(unwind) }
231-
| DropAndReplace { target, value: _, location: _, unwind: Some(unwind) }
239+
| TerminatorKind::Assert { target, cleanup: Some(unwind), .. }
240+
| TerminatorKind::Drop { target, location: _, unwind: Some(unwind) }
241+
| TerminatorKind::DropAndReplace { target, value: _, location: _, unwind: Some(unwind) }
232242
=> {
233-
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
243+
self.propagate_state_to(target, exit_state, entry_sets, dirty);
234244
if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
235-
self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
245+
self.propagate_state_to(unwind, exit_state, entry_sets, dirty);
236246
}
237247
}
238248

239-
SwitchInt { ref targets, .. } => {
249+
TerminatorKind::SwitchInt { ref targets, .. } => {
240250
for target in targets {
241-
self.propagate_bits_into_entry_set_for(in_out, *target, dirty_list);
251+
self.propagate_state_to(*target, exit_state, entry_sets, dirty);
242252
}
243253
}
244254

245-
Call { cleanup, ref destination, ref func, ref args, .. } => {
255+
TerminatorKind::Call { cleanup, ref destination, ref func, ref args, .. } => {
246256
if let Some(unwind) = cleanup {
247257
if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
248-
self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
258+
self.propagate_state_to(unwind, exit_state, entry_sets, dirty);
249259
}
250260
}
251261

252262
if let Some((ref dest_place, dest_bb)) = *destination {
253263
// N.B.: This must be done *last*, otherwise the unwind path will see the call
254264
// return effect.
255-
self.analysis.apply_call_return_effect(in_out, bb, func, args, dest_place);
256-
self.propagate_bits_into_entry_set_for(in_out, dest_bb, dirty_list);
265+
self.analysis.apply_call_return_effect(exit_state, bb, func, args, dest_place);
266+
self.propagate_state_to(dest_bb, exit_state, entry_sets, dirty);
257267
}
258268
}
259269

260-
FalseEdges { real_target, imaginary_target } => {
261-
self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
262-
self.propagate_bits_into_entry_set_for(in_out, imaginary_target, dirty_list);
270+
TerminatorKind::FalseEdges { real_target, imaginary_target } => {
271+
self.propagate_state_to(real_target, exit_state, entry_sets, dirty);
272+
self.propagate_state_to(imaginary_target, exit_state, entry_sets, dirty);
263273
}
264274

265-
FalseUnwind { real_target, unwind } => {
266-
self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
275+
TerminatorKind::FalseUnwind { real_target, unwind } => {
276+
self.propagate_state_to(real_target, exit_state, entry_sets, dirty);
267277
if let Some(unwind) = unwind {
268278
if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
269-
self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
279+
self.propagate_state_to(unwind, exit_state, entry_sets, dirty);
270280
}
271281
}
272282
}
273283
}
274284
}
285+
}
275286

276-
fn propagate_bits_into_entry_set_for(
277-
&mut self,
278-
in_out: &BitSet<A::Idx>,
279-
bb: BasicBlock,
280-
dirty_queue: &mut WorkQueue<BasicBlock>,
281-
) {
282-
let entry_set = &mut self.entry_sets[bb];
283-
let set_changed = self.analysis.join(entry_set, &in_out);
284-
if set_changed {
285-
dirty_queue.insert(bb);
286-
}
287+
/// Applies the cumulative effect of an entire block, excluding the call return effect if one
288+
/// exists.
289+
fn apply_whole_block_effect<A>(
290+
analysis: &'a A,
291+
state: &mut BitSet<A::Idx>,
292+
block: BasicBlock,
293+
block_data: &'a mir::BasicBlockData<'tcx>,
294+
)
295+
where
296+
A: Analysis<'tcx>,
297+
{
298+
for (statement_index, statement) in block_data.statements.iter().enumerate() {
299+
let location = Location { block, statement_index };
300+
analysis.apply_before_statement_effect(state, statement, location);
301+
analysis.apply_statement_effect(state, statement, location);
287302
}
303+
304+
let terminator = block_data.terminator();
305+
let location = Location { block, statement_index: block_data.statements.len() };
306+
analysis.apply_before_terminator_effect(state, terminator, location);
307+
analysis.apply_terminator_effect(state, terminator, location);
288308
}
289309

290310
// Graphviz

0 commit comments

Comments
 (0)