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