Skip to content

Commit e622678

Browse files
Implement new dataflow framework and cursor
1 parent b9f9fc3 commit e622678

File tree

3 files changed

+1008
-0
lines changed

3 files changed

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

0 commit comments

Comments
 (0)