1//! A framework that can express both [gen-kill] and generic dataflow problems.
2//!
3//! To use this framework, implement the [`Analysis`] trait. There used to be a `GenKillAnalysis`
4//! alternative trait for gen-kill analyses that would pre-compute the transfer function for each
5//! block. It was intended as an optimization, but it ended up not being any faster than
6//! `Analysis`.
7//!
8//! The `impls` module contains several examples of dataflow analyses.
9//!
10//! Then call `iterate_to_fixpoint` on your type that impls `Analysis` to get a `Results`. From
11//! there, you can use a `ResultsCursor` to inspect the fixpoint solution to your dataflow problem
12//! (good for inspecting a small number of locations), or implement the `ResultsVisitor` interface
13//! and use `visit_results` (good for inspecting many or all locations). The following example uses
14//! the `ResultsCursor` approach.
15//!
16//! ```ignore (cross-crate-imports)
17//! use rustc_const_eval::dataflow::Analysis; // Makes `iterate_to_fixpoint` available.
18//!
19//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>) {
20//! let analysis = MyAnalysis::new()
21//! .iterate_to_fixpoint(tcx, body, None)
22//! .into_results_cursor(body);
23//!
24//! // Print the dataflow state *after* each statement in the start block.
25//! for (_, statement_index) in body.block_data[START_BLOCK].statements.iter_enumerated() {
26//! cursor.seek_after(Location { block: START_BLOCK, statement_index });
27//! let state = cursor.get();
28//! println!("{:?}", state);
29//! }
30//! }
31//! ```
32//!
33//! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems
3435use rustc_data_structures::work_queue::WorkQueue;
36use rustc_index::bit_set::{DenseBitSet, MixedBitSet};
37use rustc_index::{Idx, IndexVec};
38use rustc_middle::bug;
39use rustc_middle::mir::{
40self, BasicBlock, BasicBlockData, CallReturnPlaces, Location, TerminatorEdges, traversal,
41};
42use rustc_middle::ty::TyCtxt;
43use tracing::error;
4445use self::graphviz::write_graphviz_results;
46use super::fmt::DebugWithContext;
4748mod cursor;
49mod direction;
50pub mod fmt;
51pub mod graphviz;
52pub mod lattice;
53mod results;
54mod visitor;
5556pub use self::cursor::ResultsCursor;
57pub use self::direction::{Backward, Direction, Forward};
58pub use self::lattice::{JoinSemiLattice, MaybeReachable};
59pub use self::results::{EntryStates, Results};
60pub use self::visitor::{ResultsVisitor, visit_reachable_results, visit_results};
6162/// Analysis domains are all bitsets of various kinds. This trait holds
63/// operations needed by all of them.
64pub trait BitSetExt<T> {
65fn contains(&self, elem: T) -> bool;
66}
6768impl<T: Idx> BitSetExt<T> for DenseBitSet<T> {
69fn contains(&self, elem: T) -> bool {
70self.contains(elem)
71 }
72}
7374impl<T: Idx> BitSetExt<T> for MixedBitSet<T> {
75fn contains(&self, elem: T) -> bool {
76self.contains(elem)
77 }
78}
7980/// A dataflow problem with an arbitrarily complex transfer function.
81///
82/// This trait specifies the lattice on which this analysis operates (the domain), its
83/// initial value at the entry point of each basic block, and various operations.
84///
85/// # Convergence
86///
87/// When implementing this trait it's possible to choose a transfer function such that the analysis
88/// does not reach fixpoint. To guarantee convergence, your transfer functions must maintain the
89/// following invariant:
90///
91/// > If the dataflow state **before** some point in the program changes to be greater
92/// than the prior state **before** that point, the dataflow state **after** that point must
93/// also change to be greater than the prior state **after** that point.
94///
95/// This invariant guarantees that the dataflow state at a given point in the program increases
96/// monotonically until fixpoint is reached. Note that this monotonicity requirement only applies
97/// to the same point in the program at different points in time. The dataflow state at a given
98/// point in the program may or may not be greater than the state at any preceding point.
99pub trait Analysis<'tcx> {
100/// The type that holds the dataflow state at any given point in the program.
101type Domain: Clone + JoinSemiLattice;
102103/// The direction of this analysis. Either `Forward` or `Backward`.
104type Direction: Direction = Forward;
105106/// Auxiliary data used for analyzing `SwitchInt` terminators, if necessary.
107type SwitchIntData = !;
108109/// A descriptive name for this analysis. Used only for debugging.
110 ///
111 /// This name should be brief and contain no spaces, periods or other characters that are not
112 /// suitable as part of a filename.
113const NAME: &'static str;
114115/// Returns the initial value of the dataflow state upon entry to each basic block.
116fn bottom_value(&self, body: &mir::Body<'tcx>) -> Self::Domain;
117118/// Mutates the initial value of the dataflow state upon entry to the `START_BLOCK`.
119 ///
120 /// For backward analyses, initial state (besides the bottom value) is not yet supported. Trying
121 /// to mutate the initial state will result in a panic.
122//
123 // FIXME: For backward dataflow analyses, the initial state should be applied to every basic
124 // block where control flow could exit the MIR body (e.g., those terminated with `return` or
125 // `resume`). It's not obvious how to handle `yield` points in coroutines, however.
126fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut Self::Domain);
127128/// Given an `EffectIndex`, calls the appropriate `apply_*` method in the
129 /// {early,primary} x {statement,terminator} space.
130 ///
131 /// Do not override this; instead override one or more of the `apply_*` methods.
132#[inline]
133fn apply_effect<'mir>(
134&self,
135 state: &mut Self::Domain,
136 block: BasicBlock,
137 block_data: &'mir BasicBlockData<'tcx>,
138 idx: EffectIndex,
139 ) {
140let statement_index = idx.statement_index;
141let terminator_index = block_data.statements.len();
142let loc = Location { block, statement_index };
143let is_terminator = statement_index == terminator_index;
144145if !is_terminator {
146let statement = &block_data.statements[statement_index];
147match idx.effect {
148 Effect::Early => self.apply_early_statement_effect(state, statement, loc),
149 Effect::Primary => self.apply_primary_statement_effect(state, statement, loc),
150 }
151 } else {
152let terminator = block_data.terminator();
153match idx.effect {
154 Effect::Early => self.apply_early_terminator_effect(state, terminator, loc),
155 Effect::Primary => {
156self.apply_primary_terminator_effect(state, terminator, loc);
157 }
158 }
159 }
160 }
161162/// Updates the current dataflow state with an "early" effect, i.e. one
163 /// that occurs immediately before the given statement.
164 ///
165 /// This method is useful if the consumer of the results of this analysis only needs to observe
166 /// *part* of the effect of a statement (e.g. for two-phase borrows). As a general rule,
167 /// analyses should not implement this without also implementing
168 /// `apply_primary_statement_effect`.
169fn apply_early_statement_effect(
170&self,
171 _state: &mut Self::Domain,
172 _statement: &mir::Statement<'tcx>,
173 _location: Location,
174 ) {
175 }
176177/// Updates the current dataflow state with the effect of evaluating a statement.
178fn apply_primary_statement_effect(
179&self,
180 state: &mut Self::Domain,
181 statement: &mir::Statement<'tcx>,
182 location: Location,
183 );
184185/// Updates the current dataflow state with an effect that occurs immediately *before* the
186 /// given terminator.
187 ///
188 /// This method is useful if the consumer of the results of this analysis needs only to observe
189 /// *part* of the effect of a terminator (e.g. for two-phase borrows). As a general rule,
190 /// analyses should not implement this without also implementing
191 /// `apply_primary_terminator_effect`.
192fn apply_early_terminator_effect(
193&self,
194 _state: &mut Self::Domain,
195 _terminator: &mir::Terminator<'tcx>,
196 _location: Location,
197 ) {
198 }
199200/// Updates the current dataflow state with the effect of evaluating a terminator.
201 ///
202 /// The effect of a successful return from a `Call` terminator should **not** be accounted for
203 /// in this function. That should go in `apply_call_return_effect`. For example, in the
204 /// `InitializedPlaces` analyses, the return place for a function call is not marked as
205 /// initialized here.
206fn apply_primary_terminator_effect<'mir>(
207&self,
208 _state: &mut Self::Domain,
209 terminator: &'mir mir::Terminator<'tcx>,
210 _location: Location,
211 ) -> TerminatorEdges<'mir, 'tcx> {
212terminator.edges()
213 }
214215/* Edge-specific effects */
216217/// Updates the current dataflow state with the effect of a successful return from a `Call`
218 /// terminator.
219 ///
220 /// This is separate from `apply_primary_terminator_effect` to properly track state across
221 /// unwind edges.
222fn apply_call_return_effect(
223&self,
224 _state: &mut Self::Domain,
225 _block: BasicBlock,
226 _return_places: CallReturnPlaces<'_, 'tcx>,
227 ) {
228 }
229230/// Used to update the current dataflow state with the effect of taking a particular branch in
231 /// a `SwitchInt` terminator.
232 ///
233 /// Unlike the other edge-specific effects, which are allowed to mutate `Self::Domain`
234 /// directly, overriders of this method must return a `Self::SwitchIntData` value (wrapped in
235 /// `Some`). The `apply_switch_int_edge_effect` method will then be called once for each
236 /// outgoing edge and will have access to the dataflow state that will be propagated along that
237 /// edge, and also the `Self::SwitchIntData` value.
238 ///
239 /// This interface is somewhat more complex than the other visitor-like "effect" methods.
240 /// However, it is both more ergonomic—callers don't need to recompute or cache information
241 /// about a given `SwitchInt` terminator for each one of its edges—and more efficient—the
242 /// engine doesn't need to clone the exit state for a block unless
243 /// `get_switch_int_data` is actually called.
244fn get_switch_int_data(
245&self,
246 _block: mir::BasicBlock,
247 _targets: &mir::SwitchTargets,
248 _discr: &mir::Operand<'tcx>,
249 ) -> Option<Self::SwitchIntData> {
250None251 }
252253/// See comments on `get_switch_int_data`.
254fn apply_switch_int_edge_effect(
255&self,
256 _state: &mut Self::Domain,
257 _data: &mut Self::SwitchIntData,
258 _target_idx: SwitchTargetIndex,
259 ) {
260::core::panicking::panic("internal error: entered unreachable code");unreachable!();
261 }
262263/* Extension methods */
264265/// Finds the fixpoint for this dataflow problem.
266 ///
267 /// You shouldn't need to override this. Its purpose is to enable method chaining like so:
268 ///
269 /// ```ignore (cross-crate-imports)
270 /// let results = MyAnalysis::new(tcx, body)
271 /// .iterate_to_fixpoint(tcx, body, None)
272 /// .into_results_cursor(body);
273 /// ```
274 /// You can optionally add a `pass_name` to the graphviz output for this particular run of a
275 /// dataflow analysis. Some analyses are run multiple times in the compilation pipeline.
276 /// Without a `pass_name` to differentiates them, only the results for the latest run will be
277 /// saved.
278fn iterate_to_fixpoint<'mir>(
279self,
280 tcx: TyCtxt<'tcx>,
281 body: &'mir mir::Body<'tcx>,
282 pass_name: Option<&'static str>,
283 ) -> Results<'tcx, Self>
284where
285Self: Sized,
286Self::Domain: DebugWithContext<Self>,
287 {
288let mut entry_states =
289IndexVec::from_fn_n(|_| self.bottom_value(body), body.basic_blocks.len());
290self.initialize_start_block(body, &mut entry_states[mir::START_BLOCK]);
291292if Self::Direction::IS_BACKWARD && entry_states[mir::START_BLOCK] != self.bottom_value(body)
293 {
294::rustc_middle::util::bug::bug_fmt(format_args!("`initialize_start_block` is not yet supported for backward dataflow analyses"));bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
295 }
296297let mut dirty_queue: WorkQueue<BasicBlock> = WorkQueue::with_none(body.basic_blocks.len());
298299if Self::Direction::IS_FORWARD {
300for (bb, _) in traversal::reverse_postorder(body) {
301 dirty_queue.insert(bb);
302 }
303 } else {
304// Reverse post-order on the reverse CFG may generate a better iteration order for
305 // backward dataflow analyses, but probably not enough to matter.
306for (bb, _) in traversal::postorder(body) {
307 dirty_queue.insert(bb);
308 }
309 }
310311// `state` is not actually used between iterations;
312 // this is just an optimization to avoid reallocating
313 // every iteration.
314let mut state = self.bottom_value(body);
315while let Some(bb) = dirty_queue.pop() {
316// Set the state to the entry state of the block. This is equivalent to `state =
317 // entry_states[bb].clone()`, but it saves an allocation, thus improving compile times.
318state.clone_from(&entry_states[bb]);
319320Self::Direction::apply_effects_in_block(
321&self,
322 body,
323&mut state,
324 bb,
325&body[bb],
326 |target: BasicBlock, state: &Self::Domain| {
327let set_changed = entry_states[target].join(state);
328if set_changed {
329 dirty_queue.insert(target);
330 }
331 },
332 );
333 }
334335let results = Results { analysis: self, entry_states };
336337if tcx.sess.opts.unstable_opts.dump_mir_dataflow {
338let res = write_graphviz_results(tcx, body, &results, pass_name);
339if let Err(e) = res {
340{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event compiler/rustc_mir_dataflow/src/framework/mod.rs:340",
"rustc_mir_dataflow::framework", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("compiler/rustc_mir_dataflow/src/framework/mod.rs"),
::tracing_core::__macro_support::Option::Some(340u32),
::tracing_core::__macro_support::Option::Some("rustc_mir_dataflow::framework"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Failed to write graphviz dataflow results: {0}",
e) as &dyn ::tracing::field::Value))])
});
} else { ; }
};error!("Failed to write graphviz dataflow results: {}", e);
341 }
342 }
343344results345 }
346}
347348#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SwitchTargetIndex {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
SwitchTargetIndex::Normal(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Normal",
&__self_0),
SwitchTargetIndex::Otherwise =>
::core::fmt::Formatter::write_str(f, "Otherwise"),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SwitchTargetIndex {
#[inline]
fn clone(&self) -> SwitchTargetIndex {
let _: ::core::clone::AssertParamIsClone<usize>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SwitchTargetIndex { }Copy)]
349pub enum SwitchTargetIndex {
350// Index of a normal switch target.
351Normal(usize),
352// The final "otherwise" fallback target.
353Otherwise,
354}
355356/// The legal operations for a transfer function in a gen/kill problem.
357pub trait GenKill<T> {
358/// Inserts `elem` into the state vector.
359fn gen_(&mut self, elem: T);
360361/// Removes `elem` from the state vector.
362fn kill(&mut self, elem: T);
363364/// Calls `gen` for each element in `elems`.
365fn gen_all(&mut self, elems: impl IntoIterator<Item = T>) {
366for elem in elems {
367self.gen_(elem);
368 }
369 }
370371/// Calls `kill` for each element in `elems`.
372fn kill_all(&mut self, elems: impl IntoIterator<Item = T>) {
373for elem in elems {
374self.kill(elem);
375 }
376 }
377}
378379impl<T: Idx> GenKill<T> for DenseBitSet<T> {
380fn gen_(&mut self, elem: T) {
381self.insert(elem);
382 }
383384fn kill(&mut self, elem: T) {
385self.remove(elem);
386 }
387}
388389impl<T: Idx> GenKill<T> for MixedBitSet<T> {
390fn gen_(&mut self, elem: T) {
391self.insert(elem);
392 }
393394fn kill(&mut self, elem: T) {
395self.remove(elem);
396 }
397}
398399impl<T, S: GenKill<T>> GenKill<T> for MaybeReachable<S> {
400fn gen_(&mut self, elem: T) {
401match self {
402// If the state is not reachable, adding an element does nothing.
403MaybeReachable::Unreachable => {}
404 MaybeReachable::Reachable(set) => set.gen_(elem),
405 }
406 }
407408fn kill(&mut self, elem: T) {
409match self {
410// If the state is not reachable, killing an element does nothing.
411MaybeReachable::Unreachable => {}
412 MaybeReachable::Reachable(set) => set.kill(elem),
413 }
414 }
415}
416417// NOTE: DO NOT CHANGE VARIANT ORDER. The derived `Ord` impls rely on the current order.
418#[derive(#[automatically_derived]
impl ::core::clone::Clone for Effect {
#[inline]
fn clone(&self) -> Effect { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Effect { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for Effect {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
Effect::Early => "Early",
Effect::Primary => "Primary",
})
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for Effect {
#[inline]
fn eq(&self, other: &Effect) -> bool {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
__self_discr == __arg1_discr
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for Effect {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {}
}Eq, #[automatically_derived]
impl ::core::cmp::PartialOrd for Effect {
#[inline]
fn partial_cmp(&self, other: &Effect)
-> ::core::option::Option<::core::cmp::Ordering> {
::core::option::Option::Some(::core::cmp::Ord::cmp(self, other))
}
}PartialOrd, #[automatically_derived]
impl ::core::cmp::Ord for Effect {
#[inline]
fn cmp(&self, other: &Effect) -> ::core::cmp::Ordering {
let __self_discr = ::core::intrinsics::discriminant_value(self);
let __arg1_discr = ::core::intrinsics::discriminant_value(other);
::core::cmp::Ord::cmp(&__self_discr, &__arg1_discr)
}
}Ord)]
419enum Effect {
420/// The "early" effect (e.g., `apply_early_statement_effect`) for a statement/terminator.
421Early,
422423/// The "primary" effect (e.g., `apply_primary_statement_effect`) for a statement/terminator.
424Primary,
425}
426427impl Effect {
428const fn at_index(self, statement_index: usize) -> EffectIndex {
429EffectIndex { effect: self, statement_index }
430 }
431}
432433#[derive(#[automatically_derived]
impl ::core::clone::Clone for EffectIndex {
#[inline]
fn clone(&self) -> EffectIndex {
let _: ::core::clone::AssertParamIsClone<usize>;
let _: ::core::clone::AssertParamIsClone<Effect>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for EffectIndex { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for EffectIndex {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f, "EffectIndex",
"statement_index", &self.statement_index, "effect", &&self.effect)
}
}Debug, #[automatically_derived]
impl ::core::cmp::PartialEq for EffectIndex {
#[inline]
fn eq(&self, other: &EffectIndex) -> bool {
self.statement_index == other.statement_index &&
self.effect == other.effect
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for EffectIndex {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<usize>;
let _: ::core::cmp::AssertParamIsEq<Effect>;
}
}Eq)]
434pub struct EffectIndex {
435 statement_index: usize,
436 effect: Effect,
437}
438439#[cfg(test)]
440mod tests;