rustc_mir_dataflow/framework/
cursor.rs

1//! Random access inspection of the results of a dataflow analysis.
2
3use std::cmp::Ordering;
4use std::ops::{Deref, DerefMut};
5
6#[cfg(debug_assertions)]
7use rustc_index::bit_set::DenseBitSet;
8use rustc_middle::mir::{self, BasicBlock, Location};
9
10use super::{Analysis, Direction, Effect, EffectIndex, Results};
11
12/// Some `ResultsCursor`s want to own a `Results`, and some want to borrow a `Results`, either
13/// mutable or immutably. This type allows all of the above. It's similar to `Cow`.
14pub enum ResultsHandle<'a, 'tcx, A>
15where
16    A: Analysis<'tcx>,
17{
18    BorrowedMut(&'a mut Results<'tcx, A>),
19    Owned(Results<'tcx, A>),
20}
21
22impl<'tcx, A> Deref for ResultsHandle<'_, 'tcx, A>
23where
24    A: Analysis<'tcx>,
25{
26    type Target = Results<'tcx, A>;
27
28    fn deref(&self) -> &Results<'tcx, A> {
29        match self {
30            ResultsHandle::BorrowedMut(borrowed) => borrowed,
31            ResultsHandle::Owned(owned) => owned,
32        }
33    }
34}
35
36impl<'tcx, A> DerefMut for ResultsHandle<'_, 'tcx, A>
37where
38    A: Analysis<'tcx>,
39{
40    fn deref_mut(&mut self) -> &mut Results<'tcx, A> {
41        match self {
42            ResultsHandle::BorrowedMut(borrowed) => borrowed,
43            ResultsHandle::Owned(owned) => owned,
44        }
45    }
46}
47
48/// Allows random access inspection of the results of a dataflow analysis. Use this when you want
49/// to inspect domain values only in certain locations; use `ResultsVisitor` if you want to inspect
50/// domain values in many or all locations.
51///
52/// Because `Results` only has domain values for the entry of each basic block, these inspections
53/// involve some amount of domain value recomputations. This cursor only has linear performance
54/// within a basic block when its statements are visited in the same order as the `DIRECTION` of
55/// the analysis. In the worst case—when statements are visited in *reverse* order—performance will
56/// be quadratic in the number of statements in the block. The order in which basic blocks are
57/// inspected has no impact on performance.
58pub struct ResultsCursor<'mir, 'tcx, A>
59where
60    A: Analysis<'tcx>,
61{
62    body: &'mir mir::Body<'tcx>,
63    results: ResultsHandle<'mir, 'tcx, A>,
64    state: A::Domain,
65
66    pos: CursorPosition,
67
68    /// Indicates that `state` has been modified with a custom effect.
69    ///
70    /// When this flag is set, we need to reset to an entry set before doing a seek.
71    state_needs_reset: bool,
72
73    #[cfg(debug_assertions)]
74    reachable_blocks: DenseBitSet<BasicBlock>,
75}
76
77impl<'mir, 'tcx, A> ResultsCursor<'mir, 'tcx, A>
78where
79    A: Analysis<'tcx>,
80{
81    /// Returns the dataflow state at the current location.
82    pub fn get(&self) -> &A::Domain {
83        &self.state
84    }
85
86    /// Returns the body this analysis was run on.
87    pub fn body(&self) -> &'mir mir::Body<'tcx> {
88        self.body
89    }
90
91    /// Returns a new cursor that can inspect `results`.
92    pub fn new(body: &'mir mir::Body<'tcx>, results: ResultsHandle<'mir, 'tcx, A>) -> Self {
93        let bottom_value = results.analysis.bottom_value(body);
94        ResultsCursor {
95            body,
96            results,
97
98            // Initialize to the `bottom_value` and set `state_needs_reset` to tell the cursor that
99            // it needs to reset to block entry before the first seek. The cursor position is
100            // immaterial.
101            state_needs_reset: true,
102            state: bottom_value,
103            pos: CursorPosition::block_entry(mir::START_BLOCK),
104
105            #[cfg(debug_assertions)]
106            reachable_blocks: mir::traversal::reachable_as_bitset(body),
107        }
108    }
109
110    /// Allows inspection of unreachable basic blocks even with `debug_assertions` enabled.
111    #[cfg(test)]
112    pub(crate) fn allow_unreachable(&mut self) {
113        #[cfg(debug_assertions)]
114        self.reachable_blocks.insert_all()
115    }
116
117    /// Returns the underlying `Results`.
118    pub fn results(&self) -> &Results<'tcx, A> {
119        &self.results
120    }
121
122    /// Returns the underlying `Results`.
123    pub fn mut_results(&mut self) -> &mut Results<'tcx, A> {
124        &mut self.results
125    }
126
127    /// Returns the `Analysis` used to generate the underlying `Results`.
128    pub fn analysis(&self) -> &A {
129        &self.results.analysis
130    }
131
132    /// Returns the `Analysis` used to generate the underlying `Results`.
133    pub fn mut_analysis(&mut self) -> &mut A {
134        &mut self.results.analysis
135    }
136
137    /// Resets the cursor to hold the entry set for the given basic block.
138    ///
139    /// For forward dataflow analyses, this is the dataflow state prior to the first statement.
140    ///
141    /// For backward dataflow analyses, this is the dataflow state after the terminator.
142    pub(super) fn seek_to_block_entry(&mut self, block: BasicBlock) {
143        #[cfg(debug_assertions)]
144        assert!(self.reachable_blocks.contains(block));
145
146        self.state.clone_from(self.results.entry_set_for_block(block));
147        self.pos = CursorPosition::block_entry(block);
148        self.state_needs_reset = false;
149    }
150
151    /// Resets the cursor to hold the state prior to the first statement in a basic block.
152    ///
153    /// For forward analyses, this is the entry set for the given block.
154    ///
155    /// For backward analyses, this is the state that will be propagated to its
156    /// predecessors (ignoring edge-specific effects).
157    pub fn seek_to_block_start(&mut self, block: BasicBlock) {
158        if A::Direction::IS_FORWARD {
159            self.seek_to_block_entry(block)
160        } else {
161            self.seek_after(Location { block, statement_index: 0 }, Effect::Primary)
162        }
163    }
164
165    /// Resets the cursor to hold the state after the terminator in a basic block.
166    ///
167    /// For backward analyses, this is the entry set for the given block.
168    ///
169    /// For forward analyses, this is the state that will be propagated to its
170    /// successors (ignoring edge-specific effects).
171    pub fn seek_to_block_end(&mut self, block: BasicBlock) {
172        if A::Direction::IS_BACKWARD {
173            self.seek_to_block_entry(block)
174        } else {
175            self.seek_after(self.body.terminator_loc(block), Effect::Primary)
176        }
177    }
178
179    /// Advances the cursor to hold the dataflow state at `target` before its "primary" effect is
180    /// applied.
181    ///
182    /// The "early" effect at the target location *will be* applied.
183    pub fn seek_before_primary_effect(&mut self, target: Location) {
184        self.seek_after(target, Effect::Early)
185    }
186
187    /// Advances the cursor to hold the dataflow state at `target` after its "primary" effect is
188    /// applied.
189    ///
190    /// The "early" effect at the target location will be applied as well.
191    pub fn seek_after_primary_effect(&mut self, target: Location) {
192        self.seek_after(target, Effect::Primary)
193    }
194
195    fn seek_after(&mut self, target: Location, effect: Effect) {
196        assert!(target <= self.body.terminator_loc(target.block));
197
198        // Reset to the entry of the target block if any of the following are true:
199        //   - A custom effect has been applied to the cursor state.
200        //   - We are in a different block than the target.
201        //   - We are in the same block but have advanced past the target effect.
202        if self.state_needs_reset || self.pos.block != target.block {
203            self.seek_to_block_entry(target.block);
204        } else if let Some(curr_effect) = self.pos.curr_effect_index {
205            let mut ord = curr_effect.statement_index.cmp(&target.statement_index);
206            if A::Direction::IS_BACKWARD {
207                ord = ord.reverse()
208            }
209
210            match ord.then_with(|| curr_effect.effect.cmp(&effect)) {
211                Ordering::Equal => return,
212                Ordering::Greater => self.seek_to_block_entry(target.block),
213                Ordering::Less => {}
214            }
215        }
216
217        // At this point, the cursor is in the same block as the target location at an earlier
218        // statement.
219        debug_assert_eq!(target.block, self.pos.block);
220
221        let block_data = &self.body[target.block];
222        #[rustfmt::skip]
223        let next_effect = if A::Direction::IS_FORWARD {
224            self.pos.curr_effect_index.map_or_else(
225                || Effect::Early.at_index(0),
226                EffectIndex::next_in_forward_order,
227            )
228        } else {
229            self.pos.curr_effect_index.map_or_else(
230                || Effect::Early.at_index(block_data.statements.len()),
231                EffectIndex::next_in_backward_order,
232            )
233        };
234
235        let target_effect_index = effect.at_index(target.statement_index);
236
237        A::Direction::apply_effects_in_range(
238            &mut self.results.analysis,
239            &mut self.state,
240            target.block,
241            block_data,
242            next_effect..=target_effect_index,
243        );
244
245        self.pos =
246            CursorPosition { block: target.block, curr_effect_index: Some(target_effect_index) };
247    }
248
249    /// Applies `f` to the cursor's internal state.
250    ///
251    /// This can be used, e.g., to apply the call return effect directly to the cursor without
252    /// creating an extra copy of the dataflow state.
253    pub fn apply_custom_effect(&mut self, f: impl FnOnce(&mut A, &mut A::Domain)) {
254        f(&mut self.results.analysis, &mut self.state);
255        self.state_needs_reset = true;
256    }
257}
258
259#[derive(Clone, Copy, Debug)]
260struct CursorPosition {
261    block: BasicBlock,
262    curr_effect_index: Option<EffectIndex>,
263}
264
265impl CursorPosition {
266    fn block_entry(block: BasicBlock) -> CursorPosition {
267        CursorPosition { block, curr_effect_index: None }
268    }
269}