|
| 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