]> git.lizzy.rs Git - rust.git/blob - src/librustc_mir/dataflow/generic/cursor.rs
Add a `contains` method to `ResultsCursor`
[rust.git] / src / librustc_mir / dataflow / generic / cursor.rs
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     /// Returns `true` if the dataflow state at the current location contains the given element.
69     ///
70     /// Shorthand for `self.get().contains(elem)`
71     pub fn contains(&self, elem: A::Idx) -> bool {
72         self.state.contains(elem)
73     }
74
75     /// Resets the cursor to the start of the given basic block.
76     pub fn seek_to_block_start(&mut self, block: BasicBlock) {
77         self.state.overwrite(&self.results.borrow().entry_sets[block]);
78         self.pos = CursorPosition::BlockStart(block);
79         self.call_return_effect_applied = false;
80     }
81
82     /// Advances the cursor to hold all effects up to and including to the "before" effect of the
83     /// statement (or terminator) at the given location.
84     ///
85     /// If you wish to observe the full effect of a statement or terminator, not just the "before"
86     /// effect, use `seek_after` or `seek_after_assume_call_returns`.
87     pub fn seek_before(&mut self, target: Location) {
88         assert!(target <= self.body.terminator_loc(target.block));
89         self.seek_(target, false);
90     }
91
92     /// Advances the cursor to hold the full effect of all statements (and possibly closing
93     /// terminators) up to and including the `target`.
94     ///
95     /// If the `target` is a `Call` terminator, any call return effect for that terminator will
96     /// **not** be observed. Use `seek_after_assume_call_returns` if you wish to observe the call
97     /// return effect.
98     pub fn seek_after(&mut self, target: Location) {
99         assert!(target <= self.body.terminator_loc(target.block));
100
101         // If we have already applied the call return effect, we are currently pointing at a `Call`
102         // terminator. Unconditionally reset the dataflow cursor, since there is no way to "undo"
103         // the call return effect.
104         if self.call_return_effect_applied {
105             self.seek_to_block_start(target.block);
106         }
107
108         self.seek_(target, true);
109     }
110
111     /// Advances the cursor to hold all effects up to and including of the statement (or
112     /// terminator) at the given location.
113     ///
114     /// If the `target` is a `Call` terminator, any call return effect for that terminator will
115     /// be observed. Use `seek_after` if you do **not** wish to observe the call return effect.
116     pub fn seek_after_assume_call_returns(&mut self, target: Location) {
117         let terminator_loc = self.body.terminator_loc(target.block);
118         assert!(target.statement_index <= terminator_loc.statement_index);
119
120         self.seek_(target, true);
121
122         if target != terminator_loc {
123             return;
124         }
125
126         let terminator = self.body.basic_blocks()[target.block].terminator();
127         if let mir::TerminatorKind::Call {
128             destination: Some((return_place, _)), func, args, ..
129         } = &terminator.kind
130         {
131             if !self.call_return_effect_applied {
132                 self.call_return_effect_applied = true;
133                 self.results.borrow().analysis.apply_call_return_effect(
134                     &mut self.state,
135                     target.block,
136                     func,
137                     args,
138                     return_place,
139                 );
140             }
141         }
142     }
143
144     fn seek_(&mut self, target: Location, apply_after_effect_at_target: bool) {
145         use CursorPosition::*;
146
147         match self.pos {
148             // Return early if we are already at the target location.
149             Before(curr) if curr == target && !apply_after_effect_at_target => return,
150             After(curr) if curr == target && apply_after_effect_at_target => return,
151
152             // Otherwise, we must reset to the start of the target block if...
153
154             // we are in a different block entirely.
155             BlockStart(block) | Before(Location { block, .. }) | After(Location { block, .. })
156                 if block != target.block =>
157             {
158                 self.seek_to_block_start(target.block)
159             }
160
161             // we are in the same block but have advanced past the target statement.
162             Before(curr) | After(curr) if curr.statement_index > target.statement_index => {
163                 self.seek_to_block_start(target.block)
164             }
165
166             // we have already applied the entire effect of a statement but only wish to observe
167             // its "before" effect.
168             After(curr)
169                 if curr.statement_index == target.statement_index
170                     && !apply_after_effect_at_target =>
171             {
172                 self.seek_to_block_start(target.block)
173             }
174
175             // N.B., `call_return_effect_applied` is checked in `seek_after`, not here.
176             _ => (),
177         }
178
179         let analysis = &self.results.borrow().analysis;
180         let block_data = &self.body.basic_blocks()[target.block];
181
182         // At this point, the cursor is in the same block as the target location at an earlier
183         // statement.
184         debug_assert_eq!(target.block, self.pos.block());
185
186         // Find the first statement whose transfer function has not yet been applied.
187         let first_unapplied_statement = match self.pos {
188             BlockStart(_) => 0,
189             After(Location { statement_index, .. }) => statement_index + 1,
190
191             // If we have only applied the "before" effect for the current statement, apply the
192             // remainder before continuing.
193             Before(curr) => {
194                 if curr.statement_index == block_data.statements.len() {
195                     let terminator = block_data.terminator();
196                     analysis.apply_terminator_effect(&mut self.state, terminator, curr);
197                 } else {
198                     let statement = &block_data.statements[curr.statement_index];
199                     analysis.apply_statement_effect(&mut self.state, statement, curr);
200                 }
201
202                 // If all we needed to do was go from `Before` to `After` in the same statement,
203                 // we are now done.
204                 if curr.statement_index == target.statement_index {
205                     debug_assert!(apply_after_effect_at_target);
206                     self.pos = After(target);
207                     return;
208                 }
209
210                 curr.statement_index + 1
211             }
212         };
213
214         // We have now applied all effects prior to `first_unapplied_statement`.
215
216         // Apply the effects of all statements before `target`.
217         let mut location = Location { block: target.block, statement_index: 0 };
218         for statement_index in first_unapplied_statement..target.statement_index {
219             location.statement_index = statement_index;
220             let statement = &block_data.statements[statement_index];
221             analysis.apply_before_statement_effect(&mut self.state, statement, location);
222             analysis.apply_statement_effect(&mut self.state, statement, location);
223         }
224
225         // Apply the effect of the statement (or terminator) at `target`.
226         location.statement_index = target.statement_index;
227         if target.statement_index == block_data.statements.len() {
228             let terminator = &block_data.terminator();
229             analysis.apply_before_terminator_effect(&mut self.state, terminator, location);
230
231             if apply_after_effect_at_target {
232                 analysis.apply_terminator_effect(&mut self.state, terminator, location);
233                 self.pos = After(target);
234             } else {
235                 self.pos = Before(target);
236             }
237         } else {
238             let statement = &block_data.statements[target.statement_index];
239             analysis.apply_before_statement_effect(&mut self.state, statement, location);
240
241             if apply_after_effect_at_target {
242                 analysis.apply_statement_effect(&mut self.state, statement, location);
243                 self.pos = After(target)
244             } else {
245                 self.pos = Before(target);
246             }
247         }
248     }
249 }
250
251 #[derive(Clone, Copy, Debug)]
252 enum CursorPosition {
253     /// No effects within this block have been applied.
254     BlockStart(BasicBlock),
255
256     /// Only the "before" effect of the statement (or terminator) at this location has been
257     /// applied (along with the effects of all previous statements).
258     Before(Location),
259
260     /// The effects of all statements up to and including the one at this location have been
261     /// applied.
262     After(Location),
263 }
264
265 impl CursorPosition {
266     fn block(&self) -> BasicBlock {
267         match *self {
268             Self::BlockStart(block) => block,
269             Self::Before(loc) | Self::After(loc) => loc.block,
270         }
271     }
272 }