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