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