Skip to content

Commit 47dce1b

Browse files
Add test for ResultsCursor
This is a unit test that ensures the `seek` functions work correctly.
1 parent 355cfcd commit 47dce1b

File tree

3 files changed

+356
-0
lines changed

3 files changed

+356
-0
lines changed

src/librustc/mir/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,31 @@ impl<'tcx> Body<'tcx> {
215215
}
216216
}
217217

218+
/// Returns a partially initialized MIR body containing only a list of basic blocks.
219+
///
220+
/// The returned MIR contains no `LocalDecl`s (even for the return place) or source scopes. It
221+
/// is only useful for testing but cannot be `#[cfg(test)]` because it is used in a different
222+
/// crate.
223+
pub fn new_cfg_only(basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>) -> Self {
224+
Body {
225+
phase: MirPhase::Build,
226+
basic_blocks,
227+
source_scopes: IndexVec::new(),
228+
yield_ty: None,
229+
generator_drop: None,
230+
generator_layout: None,
231+
local_decls: IndexVec::new(),
232+
user_type_annotations: IndexVec::new(),
233+
arg_count: 0,
234+
spread_arg: None,
235+
span: DUMMY_SP,
236+
control_flow_destroyed: Vec::new(),
237+
generator_kind: None,
238+
var_debug_info: Vec::new(),
239+
ignore_interior_mut_in_const_validation: false,
240+
}
241+
}
242+
218243
#[inline]
219244
pub fn basic_blocks(&self) -> &IndexVec<BasicBlock, BasicBlockData<'tcx>> {
220245
&self.basic_blocks

src/librustc_mir/dataflow/generic/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,6 @@ impl<T: Idx> GenKill<T> for BitSet<T> {
307307
self.remove(elem);
308308
}
309309
}
310+
311+
#[cfg(test)]
312+
mod tests;
Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
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

Comments
 (0)