|
| 1 | +use rustc::mir::{self, Location, BasicBlock}; |
| 2 | +use rustc::ty; |
| 3 | +use rustc_index::bit_set::BitSet; |
| 4 | +use rustc_index::vec::IndexVec; |
| 5 | + |
| 6 | +use crate::dataflow::BottomValue; |
| 7 | +use super::*; |
| 8 | + |
| 9 | +#[test] |
| 10 | +fn cursor_seek() { |
| 11 | + let body = dummy_body(); |
| 12 | + let body = &body; |
| 13 | + let analysis = Dummy { body }; |
| 14 | + |
| 15 | + let mut cursor = Results { |
| 16 | + entry_sets: analysis.entry_sets(), |
| 17 | + analysis, |
| 18 | + }.into_cursor(body); |
| 19 | + |
| 20 | + // Sanity check: the dummy call return effect is unique and actually being applied. |
| 21 | + |
| 22 | + let call_terminator_loc = Location { block: BasicBlock::from_usize(2), statement_index: 2 }; |
| 23 | + assert!(is_call_terminator_non_diverging(body, call_terminator_loc)); |
| 24 | + |
| 25 | + let call_return_effect = |
| 26 | + SeekTarget::AfterAssumeCallReturns(call_terminator_loc).effect(body).unwrap(); |
| 27 | + assert_ne!(call_return_effect, SeekTarget::After(call_terminator_loc).effect(body).unwrap()); |
| 28 | + |
| 29 | + cursor.seek_after(call_terminator_loc); |
| 30 | + assert!(!cursor.get().contains(call_return_effect)); |
| 31 | + cursor.seek_after_assume_call_returns(call_terminator_loc); |
| 32 | + assert!(cursor.get().contains(call_return_effect)); |
| 33 | + |
| 34 | + let every_target = || body |
| 35 | + .basic_blocks() |
| 36 | + .iter_enumerated() |
| 37 | + .flat_map(|(bb, _)| SeekTarget::iter_in_block(body, bb)); |
| 38 | + |
| 39 | + let mut seek_to_target = |targ| { |
| 40 | + use SeekTarget::*; |
| 41 | + |
| 42 | + match targ { |
| 43 | + BlockStart(block) => cursor.seek_to_block_start(block), |
| 44 | + Before(loc) => cursor.seek_before(loc), |
| 45 | + After(loc) => cursor.seek_after(loc), |
| 46 | + AfterAssumeCallReturns(loc) => cursor.seek_after_assume_call_returns(loc), |
| 47 | + } |
| 48 | + |
| 49 | + assert_eq!(cursor.get(), &cursor.analysis().expected_state_at(targ)); |
| 50 | + }; |
| 51 | + |
| 52 | + // Seek *to* every possible `SeekTarget` *from* every possible `SeekTarget`. |
| 53 | + // |
| 54 | + // By resetting the cursor to `from` each time it changes, we end up checking some edges twice. |
| 55 | + // What we really want is an Eulerian cycle for the complete digraph over all possible |
| 56 | + // `SeekTarget`s, but it's not worth spending the time to compute it. |
| 57 | + for from in every_target() { |
| 58 | + seek_to_target(from); |
| 59 | + |
| 60 | + for to in every_target() { |
| 61 | + seek_to_target(to); |
| 62 | + seek_to_target(from); |
| 63 | + } |
| 64 | + } |
| 65 | +} |
| 66 | + |
| 67 | +const BASIC_BLOCK_OFFSET: usize = 100; |
| 68 | + |
| 69 | +#[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 70 | +enum SeekTarget { |
| 71 | + BlockStart(BasicBlock), |
| 72 | + Before(Location), |
| 73 | + After(Location), |
| 74 | + AfterAssumeCallReturns(Location), |
| 75 | +} |
| 76 | + |
| 77 | +impl SeekTarget { |
| 78 | + fn block(&self) -> BasicBlock { |
| 79 | + use SeekTarget::*; |
| 80 | + |
| 81 | + match *self { |
| 82 | + BlockStart(block) => block, |
| 83 | + Before(loc) | After(loc) | AfterAssumeCallReturns(loc) => loc.block, |
| 84 | + } |
| 85 | + } |
| 86 | + |
| 87 | + /// Returns the largest index (not including the basic block index) that should be set when |
| 88 | + /// seeking to this location. |
| 89 | + /// |
| 90 | + /// The index for `AfterAssumeCallReturns` is the same as `After` unless the cursor is pointing |
| 91 | + /// at a `Call` terminator that can return. Returns `None` for `BlockStart`. |
| 92 | + fn effect(&self, body: &mir::Body<'_>) -> Option<usize> { |
| 93 | + use SeekTarget::*; |
| 94 | + |
| 95 | + let idx = match *self { |
| 96 | + BlockStart(_) => return None, |
| 97 | + |
| 98 | + AfterAssumeCallReturns(loc) if is_call_terminator_non_diverging(body, loc) |
| 99 | + => loc.statement_index * 2 + 2, |
| 100 | + |
| 101 | + Before(loc) => loc.statement_index * 2, |
| 102 | + After(loc) | AfterAssumeCallReturns(loc) => loc.statement_index * 2 + 1, |
| 103 | + }; |
| 104 | + |
| 105 | + assert!(idx < BASIC_BLOCK_OFFSET, "Too many statements in basic block"); |
| 106 | + Some(idx) |
| 107 | + } |
| 108 | + |
| 109 | + /// An iterator over all possible `SeekTarget`s in a given block in order, starting with |
| 110 | + /// `BlockStart`. |
| 111 | + /// |
| 112 | + /// This includes both `After` and `AfterAssumeCallReturns` for every `Location`. |
| 113 | + fn iter_in_block(body: &mir::Body<'_>, block: BasicBlock) -> impl Iterator<Item = Self> { |
| 114 | + let statements_and_terminator = (0..=body[block].statements.len()) |
| 115 | + .flat_map(|i| (0..3).map(move |j| (i, j))) |
| 116 | + .map(move |(i, kind)| { |
| 117 | + let loc = Location { block, statement_index: i }; |
| 118 | + match kind { |
| 119 | + 0 => SeekTarget::Before(loc), |
| 120 | + 1 => SeekTarget::After(loc), |
| 121 | + 2 => SeekTarget::AfterAssumeCallReturns(loc), |
| 122 | + _ => unreachable!(), |
| 123 | + } |
| 124 | + }); |
| 125 | + |
| 126 | + std::iter::once(SeekTarget::BlockStart(block)) |
| 127 | + .chain(statements_and_terminator) |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +/// A mock dataflow analysis for testing `ResultsCursor`. |
| 132 | +/// |
| 133 | +/// `Dummy` assigns an unique, monotonically index to each possible cursor position, as well as one |
| 134 | +/// to each basic block. Instead of being iterated to fixpoint, `Dummy::entry_sets` is used to |
| 135 | +/// construct the entry set to each block such that the dataflow state observed by `ResultsCursor` |
| 136 | +/// should be unique for every location (see `expected_state_at`). |
| 137 | +struct Dummy<'tcx> { |
| 138 | + body: &'tcx mir::Body<'tcx>, |
| 139 | +} |
| 140 | + |
| 141 | +impl Dummy<'tcx> { |
| 142 | + fn entry_sets(&self) -> IndexVec<BasicBlock, BitSet<usize>> { |
| 143 | + let empty = BitSet::new_empty(self.bits_per_block(self.body)); |
| 144 | + let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks()); |
| 145 | + |
| 146 | + for (bb, _) in self.body.basic_blocks().iter_enumerated() { |
| 147 | + ret[bb].insert(BASIC_BLOCK_OFFSET + bb.index()); |
| 148 | + } |
| 149 | + |
| 150 | + ret |
| 151 | + } |
| 152 | + |
| 153 | + /// Returns the expected state at the given `SeekTarget`. |
| 154 | + /// |
| 155 | + /// This is the union of index of the target basic block, the index assigned to the of the |
| 156 | + /// target statement or terminator, and the indices of all preceding statements in the MIR. |
| 157 | + /// |
| 158 | + /// For example, the expected state when calling |
| 159 | + /// `seek_before(Location { block: 2, statement_index: 2 })` would be `[102, 0, 1, 2, 3, 4]`. |
| 160 | + fn expected_state_at(&self, target: SeekTarget) -> BitSet<usize> { |
| 161 | + let mut ret = BitSet::new_empty(self.bits_per_block(self.body)); |
| 162 | + ret.insert(BASIC_BLOCK_OFFSET + target.block().index()); |
| 163 | + |
| 164 | + if let Some(target_effect) = target.effect(self.body) { |
| 165 | + for i in 0..=target_effect { |
| 166 | + ret.insert(i); |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + ret |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +impl BottomValue for Dummy<'tcx> { |
| 175 | + const BOTTOM_VALUE: bool = false; |
| 176 | +} |
| 177 | + |
| 178 | +impl AnalysisDomain<'tcx> for Dummy<'tcx> { |
| 179 | + type Idx = usize; |
| 180 | + |
| 181 | + const NAME: &'static str = "dummy"; |
| 182 | + |
| 183 | + fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize { |
| 184 | + BASIC_BLOCK_OFFSET + body.basic_blocks().len() |
| 185 | + } |
| 186 | + |
| 187 | + fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) { |
| 188 | + unimplemented!(); |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +impl Analysis<'tcx> for Dummy<'tcx> { |
| 193 | + fn apply_statement_effect( |
| 194 | + &self, |
| 195 | + state: &mut BitSet<Self::Idx>, |
| 196 | + _statement: &mir::Statement<'tcx>, |
| 197 | + location: Location, |
| 198 | + ) { |
| 199 | + let idx = SeekTarget::After(location).effect(self.body).unwrap(); |
| 200 | + assert!(state.insert(idx)); |
| 201 | + } |
| 202 | + |
| 203 | + fn apply_before_statement_effect( |
| 204 | + &self, |
| 205 | + state: &mut BitSet<Self::Idx>, |
| 206 | + _statement: &mir::Statement<'tcx>, |
| 207 | + location: Location, |
| 208 | + ) { |
| 209 | + let idx = SeekTarget::Before(location).effect(self.body).unwrap(); |
| 210 | + assert!(state.insert(idx)); |
| 211 | + } |
| 212 | + |
| 213 | + fn apply_terminator_effect( |
| 214 | + &self, |
| 215 | + state: &mut BitSet<Self::Idx>, |
| 216 | + _terminator: &mir::Terminator<'tcx>, |
| 217 | + location: Location, |
| 218 | + ) { |
| 219 | + let idx = SeekTarget::After(location).effect(self.body).unwrap(); |
| 220 | + assert!(state.insert(idx)); |
| 221 | + } |
| 222 | + |
| 223 | + fn apply_before_terminator_effect( |
| 224 | + &self, |
| 225 | + state: &mut BitSet<Self::Idx>, |
| 226 | + _terminator: &mir::Terminator<'tcx>, |
| 227 | + location: Location, |
| 228 | + ) { |
| 229 | + let idx = SeekTarget::Before(location).effect(self.body).unwrap(); |
| 230 | + assert!(state.insert(idx)); |
| 231 | + } |
| 232 | + |
| 233 | + fn apply_call_return_effect( |
| 234 | + &self, |
| 235 | + state: &mut BitSet<Self::Idx>, |
| 236 | + block: BasicBlock, |
| 237 | + _func: &mir::Operand<'tcx>, |
| 238 | + _args: &[mir::Operand<'tcx>], |
| 239 | + _return_place: &mir::Place<'tcx>, |
| 240 | + ) { |
| 241 | + let location = self.body.terminator_loc(block); |
| 242 | + let idx = SeekTarget::AfterAssumeCallReturns(location).effect(self.body).unwrap(); |
| 243 | + assert!(state.insert(idx)); |
| 244 | + } |
| 245 | +} |
| 246 | + |
| 247 | +fn is_call_terminator_non_diverging(body: &mir::Body<'_>, loc: Location) -> bool { |
| 248 | + loc == body.terminator_loc(loc.block) |
| 249 | + && matches!( |
| 250 | + body[loc.block].terminator().kind, |
| 251 | + mir::TerminatorKind::Call { destination: Some(_), .. } |
| 252 | + ) |
| 253 | +} |
| 254 | + |
| 255 | +fn dummy_body() -> mir::Body<'static> { |
| 256 | + let span = syntax_pos::DUMMY_SP; |
| 257 | + let source_info = mir::SourceInfo { scope: mir::OUTERMOST_SOURCE_SCOPE, span }; |
| 258 | + |
| 259 | + let mut blocks = IndexVec::new(); |
| 260 | + let mut block = |n, kind| { |
| 261 | + let nop = mir::Statement { |
| 262 | + source_info, |
| 263 | + kind: mir::StatementKind::Nop, |
| 264 | + }; |
| 265 | + |
| 266 | + blocks.push(mir::BasicBlockData { |
| 267 | + statements: std::iter::repeat(&nop).cloned().take(n).collect(), |
| 268 | + terminator: Some(mir::Terminator { source_info, kind }), |
| 269 | + is_cleanup: false, |
| 270 | + }) |
| 271 | + }; |
| 272 | + |
| 273 | + let dummy_place = mir::Place { |
| 274 | + base: mir::PlaceBase::Local(mir::RETURN_PLACE), |
| 275 | + projection: ty::List::empty(), |
| 276 | + }; |
| 277 | + |
| 278 | + block(4, mir::TerminatorKind::Return); |
| 279 | + block(1, mir::TerminatorKind::Return); |
| 280 | + block(2, mir::TerminatorKind::Call { |
| 281 | + func: mir::Operand::Copy(dummy_place.clone()), |
| 282 | + args: vec![], |
| 283 | + destination: Some((dummy_place.clone(), mir::START_BLOCK)), |
| 284 | + cleanup: None, |
| 285 | + from_hir_call: false, |
| 286 | + }); |
| 287 | + block(3, mir::TerminatorKind::Return); |
| 288 | + block(4, mir::TerminatorKind::Call { |
| 289 | + func: mir::Operand::Copy(dummy_place.clone()), |
| 290 | + args: vec![], |
| 291 | + destination: Some((dummy_place.clone(), mir::START_BLOCK)), |
| 292 | + cleanup: None, |
| 293 | + from_hir_call: false, |
| 294 | + }); |
| 295 | + |
| 296 | + mir::Body::new_cfg_only(blocks) |
| 297 | +} |
0 commit comments