Skip to content

Commit abb068e

Browse files
Add test for ResultsCursor
1 parent dcdfc3c commit abb068e

File tree

4 files changed

+326
-0
lines changed

4 files changed

+326
-0
lines changed

src/librustc/mir/mod.rs

+25
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,31 @@ impl<'tcx> Body<'tcx> {
204204
}
205205
}
206206

207+
/// Returns a partially initialized MIR body containing only a list of basic blocks.
208+
///
209+
/// The returned MIR contains no `LocalDecl`s (even for the return place) or source scopes. It
210+
/// is only useful for testing but cannot be `#[cfg(test)]` because it is used in a different
211+
/// crate.
212+
pub fn new_cfg_only(basic_blocks: IndexVec<BasicBlock, BasicBlockData<'tcx>>) -> Self {
213+
Body {
214+
phase: MirPhase::Build,
215+
basic_blocks,
216+
source_scopes: IndexVec::new(),
217+
source_scope_local_data: ClearCrossCrate::Clear,
218+
yield_ty: None,
219+
generator_drop: None,
220+
generator_layout: None,
221+
local_decls: IndexVec::new(),
222+
user_type_annotations: IndexVec::new(),
223+
arg_count: 0,
224+
__upvar_debuginfo_codegen_only_do_not_use: Vec::new(),
225+
spread_arg: None,
226+
span: syntax_pos::DUMMY_SP,
227+
cache: cache::Cache::new(),
228+
control_flow_destroyed: Vec::new(),
229+
}
230+
}
231+
207232
#[inline]
208233
pub fn basic_blocks(&self) -> &IndexVec<BasicBlock, BasicBlockData<'tcx>> {
209234
&self.basic_blocks

src/librustc_mir/dataflow/generic/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -303,3 +303,6 @@ impl<T: Idx> GenKill<T> for BitSet<T> {
303303
self.remove(elem);
304304
}
305305
}
306+
307+
#[cfg(test)]
308+
mod tests;
+297
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
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+
}

src/librustc_mir/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
2727
#![feature(range_is_empty)]
2828
#![feature(stmt_expr_attributes)]
2929
#![feature(bool_to_option)]
30+
#![feature(matches_macro)]
3031

3132
#![recursion_limit="256"]
3233

0 commit comments

Comments
 (0)