Skip to content

Commit 07c51f6

Browse files
Implement new dataflow framework and cursor
1 parent 2fb4c44 commit 07c51f6

File tree

3 files changed

+995
-0
lines changed

3 files changed

+995
-0
lines changed
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
//! Random access inspection of the results of a dataflow analysis.
2+
3+
use std::borrow::Borrow;
4+
5+
use rustc::mir::{self, BasicBlock, Location};
6+
use rustc_index::bit_set::BitSet;
7+
8+
use super::{Analysis, Results};
9+
10+
/// A `ResultsCursor` that borrows the underlying `Results`.
11+
pub type ResultsRefCursor<'a, 'mir, 'tcx, A> = ResultsCursor<'mir, 'tcx, A, &'a Results<'tcx, A>>;
12+
13+
/// Allows random access inspection of the results of a dataflow analysis.
14+
///
15+
/// This cursor only has linear performance within a basic block when its statements are visited in
16+
/// order. In the worst case—when statements are visited in *reverse* order—performance will be
17+
/// quadratic in the number of statements in the block. The order in which basic blocks are
18+
/// inspected has no impact on performance.
19+
///
20+
/// A `ResultsCursor` can either own (the default) or borrow the dataflow results it inspects. The
21+
/// type of ownership is determined by `R` (see `ResultsRefCursor` above).
22+
pub struct ResultsCursor<'mir, 'tcx, A, R = Results<'tcx, A>>
23+
where
24+
A: Analysis<'tcx>,
25+
{
26+
body: &'mir mir::Body<'tcx>,
27+
results: R,
28+
state: BitSet<A::Idx>,
29+
30+
pos: CursorPosition,
31+
32+
/// When this flag is set, the cursor is pointing at a `Call` terminator whose call return
33+
/// effect has been applied to `state`.
34+
///
35+
/// This flag helps to ensure that multiple calls to `seek_after_assume_call_returns` with the
36+
/// same target will result in exactly one invocation of `apply_call_return_effect`. It is
37+
/// sufficient to clear this only in `seek_to_block_start`, since seeking away from a
38+
/// terminator will always require a cursor reset.
39+
call_return_effect_applied: bool,
40+
}
41+
42+
impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
43+
where
44+
A: Analysis<'tcx>,
45+
R: Borrow<Results<'tcx, A>>,
46+
{
47+
/// Returns a new cursor for `results` that points to the start of the `START_BLOCK`.
48+
pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
49+
ResultsCursor {
50+
body,
51+
pos: CursorPosition::BlockStart(mir::START_BLOCK),
52+
state: results.borrow().entry_sets[mir::START_BLOCK].clone(),
53+
call_return_effect_applied: false,
54+
results,
55+
}
56+
}
57+
58+
/// Returns the `Analysis` used to generate the underlying results.
59+
pub fn analysis(&self) -> &A {
60+
&self.results.borrow().analysis
61+
}
62+
63+
/// Returns the dataflow state at the current location.
64+
pub fn get(&self) -> &BitSet<A::Idx> {
65+
&self.state
66+
}
67+
68+
/// Resets the cursor to the start of the given basic block.
69+
pub fn seek_to_block_start(&mut self, block: BasicBlock) {
70+
self.state.overwrite(&self.results.borrow().entry_sets[block]);
71+
self.pos = CursorPosition::BlockStart(block);
72+
self.call_return_effect_applied = false;
73+
}
74+
75+
/// Advances the cursor to hold all effects up to and including to the "before" effect of the
76+
/// statement (or terminator) at the given location.
77+
///
78+
/// If you wish to observe the full effect of a statement or terminator, not just the "before"
79+
/// effect, use `seek_after` or `seek_after_assume_call_returns`.
80+
pub fn seek_before(&mut self, target: Location) {
81+
assert!(target <= self.body.terminator_loc(target.block));
82+
self._seek(target, false);
83+
}
84+
85+
/// Advances the cursor to hold the full effect of all statements (and possibly closing
86+
/// terminators) up to and including the `target`.
87+
///
88+
/// If the `target` is a `Call` terminator, any call return effect for that terminator will
89+
/// **not** be observed. Use `seek_after_assume_call_returns` if you wish to observe the call
90+
/// return effect.
91+
pub fn seek_after(&mut self, target: Location) {
92+
assert!(target <= self.body.terminator_loc(target.block));
93+
94+
// If we have already applied the call return effect, we are currently pointing at a `Call`
95+
// terminator. Unconditionally reset the dataflow cursor, since there is no way to "undo"
96+
// the call return effect.
97+
if self.call_return_effect_applied {
98+
self.seek_to_block_start(target.block);
99+
}
100+
101+
self._seek(target, true);
102+
}
103+
104+
/// Advances the cursor to hold all effects up to and including of the statement (or
105+
/// terminator) at the given location.
106+
///
107+
/// If the `target` is a `Call` terminator, any call return effect for that terminator will
108+
/// be observed. Use `seek_after` if you do **not** wish to observe the call return effect.
109+
pub fn seek_after_assume_call_returns(&mut self, target: Location) {
110+
let terminator_loc = self.body.terminator_loc(target.block);
111+
assert!(target.statement_index <= terminator_loc.statement_index);
112+
113+
self._seek(target, true);
114+
115+
if target != terminator_loc {
116+
return;
117+
}
118+
119+
let terminator = self.body.basic_blocks()[target.block].terminator();
120+
if let mir::TerminatorKind::Call {
121+
destination: Some((return_place, _)), func, args, ..
122+
} = &terminator.kind
123+
{
124+
if !self.call_return_effect_applied {
125+
self.call_return_effect_applied = true;
126+
self.results.borrow().analysis.apply_call_return_effect(
127+
&mut self.state,
128+
target.block,
129+
func,
130+
args,
131+
return_place,
132+
);
133+
}
134+
}
135+
}
136+
137+
fn _seek(&mut self, target: Location, apply_after_effect_at_target: bool) {
138+
use CursorPosition::*;
139+
140+
match self.pos {
141+
// Return early if we are already at the target location.
142+
Before(curr) if curr == target && !apply_after_effect_at_target => return,
143+
After(curr) if curr == target && apply_after_effect_at_target => return,
144+
145+
// Otherwise, we must reset to the start of the target block if...
146+
147+
// we are in a different block entirely.
148+
BlockStart(block) | Before(Location { block, .. }) | After(Location { block, .. })
149+
if block != target.block =>
150+
{
151+
self.seek_to_block_start(target.block)
152+
}
153+
154+
// we are in the same block but have advanced past the target statement.
155+
Before(curr) | After(curr) if curr.statement_index > target.statement_index => {
156+
self.seek_to_block_start(target.block)
157+
}
158+
159+
// we have already applied the entire effect of a statement but only wish to observe
160+
// its "before" effect.
161+
After(curr)
162+
if curr.statement_index == target.statement_index
163+
&& !apply_after_effect_at_target =>
164+
{
165+
self.seek_to_block_start(target.block)
166+
}
167+
168+
// N.B., `call_return_effect_applied` is checked in `seek_after`, not here.
169+
_ => (),
170+
}
171+
172+
let analysis = &self.results.borrow().analysis;
173+
let block_data = &self.body.basic_blocks()[target.block];
174+
175+
// At this point, the cursor is in the same block as the target location at an earlier
176+
// statement.
177+
debug_assert_eq!(target.block, self.pos.block());
178+
179+
// Find the first statement whose transfer function has not yet been applied.
180+
let first_unapplied_statement = match self.pos {
181+
BlockStart(_) => 0,
182+
After(Location { statement_index, .. }) => statement_index + 1,
183+
184+
// If we have only applied the "before" effect for the current statement, apply the
185+
// remainder before continuing.
186+
Before(curr) => {
187+
if curr.statement_index == block_data.statements.len() {
188+
let terminator = block_data.terminator();
189+
analysis.apply_terminator_effect(&mut self.state, terminator, curr);
190+
} else {
191+
let statement = &block_data.statements[curr.statement_index];
192+
analysis.apply_statement_effect(&mut self.state, statement, curr);
193+
}
194+
195+
// If all we needed to do was go from `Before` to `After` in the same statement,
196+
// we are now done.
197+
if curr.statement_index == target.statement_index {
198+
debug_assert!(apply_after_effect_at_target);
199+
self.pos = After(target);
200+
return;
201+
}
202+
203+
curr.statement_index + 1
204+
}
205+
};
206+
207+
// We have now applied all effects prior to `first_unapplied_statement`.
208+
209+
// Apply the effects of all statements before `target`.
210+
let mut location = Location { block: target.block, statement_index: 0 };
211+
for statement_index in first_unapplied_statement..target.statement_index {
212+
location.statement_index = statement_index;
213+
let statement = &block_data.statements[statement_index];
214+
analysis.apply_before_statement_effect(&mut self.state, statement, location);
215+
analysis.apply_statement_effect(&mut self.state, statement, location);
216+
}
217+
218+
// Apply the effect of the statement (or terminator) at `target`.
219+
location.statement_index = target.statement_index;
220+
if target.statement_index == block_data.statements.len() {
221+
let terminator = &block_data.terminator();
222+
analysis.apply_before_terminator_effect(&mut self.state, terminator, location);
223+
224+
if apply_after_effect_at_target {
225+
analysis.apply_terminator_effect(&mut self.state, terminator, location);
226+
self.pos = After(target);
227+
} else {
228+
self.pos = Before(target);
229+
}
230+
} else {
231+
let statement = &block_data.statements[target.statement_index];
232+
analysis.apply_before_statement_effect(&mut self.state, statement, location);
233+
234+
if apply_after_effect_at_target {
235+
analysis.apply_statement_effect(&mut self.state, statement, location);
236+
self.pos = After(target)
237+
} else {
238+
self.pos = Before(target);
239+
}
240+
}
241+
}
242+
}
243+
244+
#[derive(Clone, Copy, Debug)]
245+
enum CursorPosition {
246+
/// No effects within this block have been applied.
247+
BlockStart(BasicBlock),
248+
249+
/// Only the "before" effect of the statement (or terminator) at this location has been
250+
/// applied (along with the effects of all previous statements).
251+
Before(Location),
252+
253+
/// The effects of all statements up to and including the one at this location have been
254+
/// applied.
255+
After(Location),
256+
}
257+
258+
impl CursorPosition {
259+
fn block(&self) -> BasicBlock {
260+
match *self {
261+
Self::BlockStart(block) => block,
262+
Self::Before(loc) | Self::After(loc) => loc.block,
263+
}
264+
}
265+
}

0 commit comments

Comments
 (0)