use std::rc::Rc;
use crate::dataflow;
-use crate::dataflow::generic::{Analysis, BorrowckFlowState as Flows, BorrowckResults};
use crate::dataflow::indexes::{BorrowIndex, InitIndex, MoveOutIndex, MovePathIndex};
use crate::dataflow::move_paths::{InitLocation, LookupResult, MoveData, MoveError};
use crate::dataflow::Borrows;
use crate::dataflow::EverInitializedPlaces;
use crate::dataflow::MoveDataParamEnv;
+use crate::dataflow::{Analysis, BorrowckFlowState as Flows, BorrowckResults};
use crate::dataflow::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
use crate::transform::MirSource;
mbcx.report_move_errors(errors);
}
- dataflow::generic::visit_results(
+ dataflow::visit_results(
&*body,
traversal::reverse_postorder(&*body).map(|(bb, _)| bb),
&results,
// 2. loans made in overlapping scopes do not conflict
// 3. assignments do not affect things loaned out as immutable
// 4. moves do not affect things loaned out in any way
-impl<'cx, 'tcx> dataflow::generic::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tcx> {
+impl<'cx, 'tcx> dataflow::ResultsVisitor<'cx, 'tcx> for MirBorrowckCtxt<'cx, 'tcx> {
type FlowState = Flows<'cx, 'tcx>;
fn visit_statement(
use self::mir_util::PassWhere;
use polonius_engine::{Algorithm, Output};
-use crate::dataflow::generic::ResultsCursor;
use crate::dataflow::move_paths::{InitKind, InitLocation, MoveData};
use crate::dataflow::MaybeInitializedPlaces;
+use crate::dataflow::ResultsCursor;
use crate::transform::MirSource;
use crate::util as mir_util;
use crate::util::pretty;
use rustc_data_structures::fx::FxHashSet;
use std::rc::Rc;
-use crate::dataflow::generic::ResultsCursor;
use crate::dataflow::move_paths::MoveData;
use crate::dataflow::MaybeInitializedPlaces;
+use crate::dataflow::ResultsCursor;
use crate::borrow_check::{
constraints::OutlivesConstraintSet,
use rustc_trait_selection::traits::query::type_op::TypeOp;
use std::rc::Rc;
-use crate::dataflow::generic::ResultsCursor;
use crate::dataflow::indexes::MovePathIndex;
use crate::dataflow::move_paths::{HasMoveData, MoveData};
use crate::dataflow::MaybeInitializedPlaces;
+use crate::dataflow::ResultsCursor;
use crate::borrow_check::{
region_infer::values::{self, PointIndex, RegionValueElements},
use rustc_trait_selection::traits::query::{Fallible, NoSolution};
use rustc_trait_selection::traits::{self, ObligationCause, PredicateObligations};
-use crate::dataflow::generic::ResultsCursor;
use crate::dataflow::move_paths::MoveData;
use crate::dataflow::MaybeInitializedPlaces;
+use crate::dataflow::ResultsCursor;
use crate::transform::promote_consts::should_suggest_const_in_array_repeat_expressions_attribute;
use crate::borrow_check::{
+++ /dev/null
-//! A nice wrapper to consume dataflow results at several CFG
-//! locations.
-
-use rustc::mir::{BasicBlock, Location};
-use rustc_index::bit_set::{BitIter, BitSet, HybridBitSet};
-
-use crate::dataflow::{BitDenotation, DataflowResults, GenKillSet};
-
-use std::borrow::Borrow;
-use std::iter;
-
-/// A trait for "cartesian products" of multiple FlowAtLocation.
-///
-/// There's probably a way to auto-impl this, but I think
-/// it is cleaner to have manual visitor impls.
-pub trait FlowsAtLocation {
- /// Reset the state bitvector to represent the entry to block `bb`.
- fn reset_to_entry_of(&mut self, bb: BasicBlock);
-
- /// Reset the state bitvector to represent the exit of the
- /// terminator of block `bb`.
- ///
- /// **Important:** In the case of a `Call` terminator, these
- /// effects do *not* include the result of storing the destination
- /// of the call, since that is edge-dependent (in other words, the
- /// effects don't apply to the unwind edge).
- fn reset_to_exit_of(&mut self, bb: BasicBlock);
-
- /// Builds gen and kill sets for statement at `loc`.
- ///
- /// Note that invoking this method alone does not change the
- /// `curr_state` -- you must invoke `apply_local_effect`
- /// afterwards.
- fn reconstruct_statement_effect(&mut self, loc: Location);
-
- /// Builds gen and kill sets for terminator for `loc`.
- ///
- /// Note that invoking this method alone does not change the
- /// `curr_state` -- you must invoke `apply_local_effect`
- /// afterwards.
- fn reconstruct_terminator_effect(&mut self, loc: Location);
-
- /// Apply current gen + kill sets to `flow_state`.
- ///
- /// (`loc` parameters can be ignored if desired by
- /// client. For the terminator, the `stmt_idx` will be the number
- /// of statements in the block.)
- fn apply_local_effect(&mut self, loc: Location);
-}
-
-/// Represents the state of dataflow at a particular
-/// CFG location, both before and after it is
-/// executed.
-///
-/// Data flow results are typically computed only as basic block
-/// boundaries. A `FlowInProgress` allows you to reconstruct the
-/// effects at any point in the control-flow graph by starting with
-/// the state at the start of the basic block (`reset_to_entry_of`)
-/// and then replaying the effects of statements and terminators
-/// (e.g., via `reconstruct_statement_effect` and
-/// `reconstruct_terminator_effect`; don't forget to call
-/// `apply_local_effect`).
-pub struct FlowAtLocation<'tcx, BD, DR = DataflowResults<'tcx, BD>>
-where
- BD: BitDenotation<'tcx>,
- DR: Borrow<DataflowResults<'tcx, BD>>,
-{
- base_results: DR,
- curr_state: BitSet<BD::Idx>,
- stmt_trans: GenKillSet<BD::Idx>,
-}
-
-impl<'tcx, BD, DR> FlowAtLocation<'tcx, BD, DR>
-where
- BD: BitDenotation<'tcx>,
- DR: Borrow<DataflowResults<'tcx, BD>>,
-{
- /// Iterate over each bit set in the current state.
- pub fn each_state_bit<F>(&self, f: F)
- where
- F: FnMut(BD::Idx),
- {
- self.curr_state.iter().for_each(f)
- }
-
- /// Iterate over each `gen` bit in the current effect (invoke
- /// `reconstruct_statement_effect` or
- /// `reconstruct_terminator_effect` first).
- pub fn each_gen_bit<F>(&self, f: F)
- where
- F: FnMut(BD::Idx),
- {
- self.stmt_trans.gen_set.iter().for_each(f)
- }
-
- pub fn new(results: DR) -> Self {
- let bits_per_block = results.borrow().sets().bits_per_block();
- let curr_state = BitSet::new_empty(bits_per_block);
- let stmt_trans = GenKillSet::from_elem(HybridBitSet::new_empty(bits_per_block));
- FlowAtLocation { base_results: results, curr_state, stmt_trans }
- }
-
- /// Access the underlying operator.
- pub fn operator(&self) -> &BD {
- self.base_results.borrow().operator()
- }
-
- pub fn contains(&self, x: BD::Idx) -> bool {
- self.curr_state.contains(x)
- }
-
- /// Returns an iterator over the elements present in the current state.
- pub fn iter_incoming(&self) -> iter::Peekable<BitIter<'_, BD::Idx>> {
- self.curr_state.iter().peekable()
- }
-
- /// Creates a clone of the current state and applies the local
- /// effects to the clone (leaving the state of self intact).
- /// Invokes `f` with an iterator over the resulting state.
- pub fn with_iter_outgoing<F>(&self, f: F)
- where
- F: FnOnce(BitIter<'_, BD::Idx>),
- {
- let mut curr_state = self.curr_state.clone();
- self.stmt_trans.apply(&mut curr_state);
- f(curr_state.iter());
- }
-
- /// Returns a bitset of the elements present in the current state.
- pub fn as_dense(&self) -> &BitSet<BD::Idx> {
- &self.curr_state
- }
-}
-
-impl<'tcx, BD, DR> FlowsAtLocation for FlowAtLocation<'tcx, BD, DR>
-where
- BD: BitDenotation<'tcx>,
- DR: Borrow<DataflowResults<'tcx, BD>>,
-{
- fn reset_to_entry_of(&mut self, bb: BasicBlock) {
- self.curr_state.overwrite(self.base_results.borrow().sets().entry_set_for(bb.index()));
- }
-
- fn reset_to_exit_of(&mut self, bb: BasicBlock) {
- self.reset_to_entry_of(bb);
- let trans = self.base_results.borrow().sets().trans_for(bb.index());
- trans.apply(&mut self.curr_state)
- }
-
- fn reconstruct_statement_effect(&mut self, loc: Location) {
- self.stmt_trans.clear();
- self.base_results.borrow().operator().before_statement_effect(&mut self.stmt_trans, loc);
- self.stmt_trans.apply(&mut self.curr_state);
-
- self.base_results.borrow().operator().statement_effect(&mut self.stmt_trans, loc);
- }
-
- fn reconstruct_terminator_effect(&mut self, loc: Location) {
- self.stmt_trans.clear();
- self.base_results.borrow().operator().before_terminator_effect(&mut self.stmt_trans, loc);
- self.stmt_trans.apply(&mut self.curr_state);
-
- self.base_results.borrow().operator().terminator_effect(&mut self.stmt_trans, loc);
- }
-
- fn apply_local_effect(&mut self, _loc: Location) {
- self.stmt_trans.apply(&mut self.curr_state)
- }
-}
--- /dev/null
+//! Random access inspection of the results of a dataflow analysis.
+
+use std::borrow::Borrow;
+
+use rustc::mir::{self, BasicBlock, Location, TerminatorKind};
+use rustc_index::bit_set::BitSet;
+
+use super::{Analysis, Results};
+
+/// A `ResultsCursor` that borrows the underlying `Results`.
+pub type ResultsRefCursor<'a, 'mir, 'tcx, A> = ResultsCursor<'mir, 'tcx, A, &'a Results<'tcx, A>>;
+
+/// Allows random access inspection of the results of a dataflow analysis.
+///
+/// This cursor only has linear performance within a basic block when its statements are visited in
+/// order. In the worst case—when statements are visited in *reverse* order—performance will be
+/// quadratic in the number of statements in the block. The order in which basic blocks are
+/// inspected has no impact on performance.
+///
+/// A `ResultsCursor` can either own (the default) or borrow the dataflow results it inspects. The
+/// type of ownership is determined by `R` (see `ResultsRefCursor` above).
+pub struct ResultsCursor<'mir, 'tcx, A, R = Results<'tcx, A>>
+where
+ A: Analysis<'tcx>,
+{
+ body: &'mir mir::Body<'tcx>,
+ results: R,
+ state: BitSet<A::Idx>,
+
+ pos: CursorPosition,
+
+ /// When this flag is set, the cursor is pointing at a `Call` or `Yield` terminator whose call
+ /// return or resume effect has been applied to `state`.
+ ///
+ /// This flag helps to ensure that multiple calls to `seek_after_assume_success` with the
+ /// same target will result in exactly one invocation of `apply_call_return_effect`. It is
+ /// sufficient to clear this only in `seek_to_block_start`, since seeking away from a
+ /// terminator will always require a cursor reset.
+ success_effect_applied: bool,
+}
+
+impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
+where
+ A: Analysis<'tcx>,
+ R: Borrow<Results<'tcx, A>>,
+{
+ /// Returns a new cursor for `results` that points to the start of the `START_BLOCK`.
+ pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
+ ResultsCursor {
+ body,
+ pos: CursorPosition::BlockStart(mir::START_BLOCK),
+ state: results.borrow().entry_sets[mir::START_BLOCK].clone(),
+ success_effect_applied: false,
+ results,
+ }
+ }
+
+ /// Returns the `Analysis` used to generate the underlying results.
+ pub fn analysis(&self) -> &A {
+ &self.results.borrow().analysis
+ }
+
+ /// Returns the dataflow state at the current location.
+ pub fn get(&self) -> &BitSet<A::Idx> {
+ &self.state
+ }
+
+ /// Returns `true` if the dataflow state at the current location contains the given element.
+ ///
+ /// Shorthand for `self.get().contains(elem)`
+ pub fn contains(&self, elem: A::Idx) -> bool {
+ self.state.contains(elem)
+ }
+
+ /// Resets the cursor to the start of the given basic block.
+ pub fn seek_to_block_start(&mut self, block: BasicBlock) {
+ self.state.overwrite(&self.results.borrow().entry_sets[block]);
+ self.pos = CursorPosition::BlockStart(block);
+ self.success_effect_applied = false;
+ }
+
+ /// Advances the cursor to hold all effects up to and including to the "before" effect of the
+ /// statement (or terminator) at the given location.
+ ///
+ /// If you wish to observe the full effect of a statement or terminator, not just the "before"
+ /// effect, use `seek_after` or `seek_after_assume_success`.
+ pub fn seek_before(&mut self, target: Location) {
+ assert!(target <= self.body.terminator_loc(target.block));
+ self.seek_(target, false);
+ }
+
+ /// Advances the cursor to hold the full effect of all statements (and possibly closing
+ /// terminators) up to and including the `target`.
+ ///
+ /// If the `target` is a `Call` terminator, any call return effect for that terminator will
+ /// **not** be observed. Use `seek_after_assume_success` if you wish to observe the call
+ /// return effect.
+ pub fn seek_after(&mut self, target: Location) {
+ assert!(target <= self.body.terminator_loc(target.block));
+
+ // If we have already applied the call return effect, we are currently pointing at a `Call`
+ // terminator. Unconditionally reset the dataflow cursor, since there is no way to "undo"
+ // the call return effect.
+ if self.success_effect_applied {
+ self.seek_to_block_start(target.block);
+ }
+
+ self.seek_(target, true);
+ }
+
+ /// Advances the cursor to hold all effects up to and including of the statement (or
+ /// terminator) at the given location.
+ ///
+ /// If the `target` is a `Call` or `Yield` terminator, any call return or resume effect for that
+ /// terminator will be observed. Use `seek_after` if you do **not** wish to observe the
+ /// "success" effect.
+ pub fn seek_after_assume_success(&mut self, target: Location) {
+ let terminator_loc = self.body.terminator_loc(target.block);
+ assert!(target.statement_index <= terminator_loc.statement_index);
+
+ self.seek_(target, true);
+
+ if target != terminator_loc || self.success_effect_applied {
+ return;
+ }
+
+ // Apply the effect of the "success" path of the terminator.
+
+ self.success_effect_applied = true;
+ let terminator = self.body.basic_blocks()[target.block].terminator();
+ match &terminator.kind {
+ TerminatorKind::Call { destination: Some((return_place, _)), func, args, .. } => {
+ self.results.borrow().analysis.apply_call_return_effect(
+ &mut self.state,
+ target.block,
+ func,
+ args,
+ return_place,
+ );
+ }
+ TerminatorKind::Yield { resume, resume_arg, .. } => {
+ self.results.borrow().analysis.apply_yield_resume_effect(
+ &mut self.state,
+ *resume,
+ resume_arg,
+ );
+ }
+ _ => {}
+ }
+ }
+
+ fn seek_(&mut self, target: Location, apply_after_effect_at_target: bool) {
+ use CursorPosition::*;
+
+ match self.pos {
+ // Return early if we are already at the target location.
+ Before(curr) if curr == target && !apply_after_effect_at_target => return,
+ After(curr) if curr == target && apply_after_effect_at_target => return,
+
+ // Otherwise, we must reset to the start of the target block if...
+
+ // we are in a different block entirely.
+ BlockStart(block) | Before(Location { block, .. }) | After(Location { block, .. })
+ if block != target.block =>
+ {
+ self.seek_to_block_start(target.block)
+ }
+
+ // we are in the same block but have advanced past the target statement.
+ Before(curr) | After(curr) if curr.statement_index > target.statement_index => {
+ self.seek_to_block_start(target.block)
+ }
+
+ // we have already applied the entire effect of a statement but only wish to observe
+ // its "before" effect.
+ After(curr)
+ if curr.statement_index == target.statement_index
+ && !apply_after_effect_at_target =>
+ {
+ self.seek_to_block_start(target.block)
+ }
+
+ // N.B., `success_effect_applied` is checked in `seek_after`, not here.
+ _ => (),
+ }
+
+ let analysis = &self.results.borrow().analysis;
+ let block_data = &self.body.basic_blocks()[target.block];
+
+ // At this point, the cursor is in the same block as the target location at an earlier
+ // statement.
+ debug_assert_eq!(target.block, self.pos.block());
+
+ // Find the first statement whose transfer function has not yet been applied.
+ let first_unapplied_statement = match self.pos {
+ BlockStart(_) => 0,
+ After(Location { statement_index, .. }) => statement_index + 1,
+
+ // If we have only applied the "before" effect for the current statement, apply the
+ // remainder before continuing.
+ Before(curr) => {
+ if curr.statement_index == block_data.statements.len() {
+ let terminator = block_data.terminator();
+ analysis.apply_terminator_effect(&mut self.state, terminator, curr);
+ } else {
+ let statement = &block_data.statements[curr.statement_index];
+ analysis.apply_statement_effect(&mut self.state, statement, curr);
+ }
+
+ // If all we needed to do was go from `Before` to `After` in the same statement,
+ // we are now done.
+ if curr.statement_index == target.statement_index {
+ debug_assert!(apply_after_effect_at_target);
+ self.pos = After(target);
+ return;
+ }
+
+ curr.statement_index + 1
+ }
+ };
+
+ // We have now applied all effects prior to `first_unapplied_statement`.
+
+ // Apply the effects of all statements before `target`.
+ let mut location = Location { block: target.block, statement_index: 0 };
+ for statement_index in first_unapplied_statement..target.statement_index {
+ location.statement_index = statement_index;
+ let statement = &block_data.statements[statement_index];
+ analysis.apply_before_statement_effect(&mut self.state, statement, location);
+ analysis.apply_statement_effect(&mut self.state, statement, location);
+ }
+
+ // Apply the effect of the statement (or terminator) at `target`.
+ location.statement_index = target.statement_index;
+ if target.statement_index == block_data.statements.len() {
+ let terminator = &block_data.terminator();
+ analysis.apply_before_terminator_effect(&mut self.state, terminator, location);
+
+ if apply_after_effect_at_target {
+ analysis.apply_terminator_effect(&mut self.state, terminator, location);
+ self.pos = After(target);
+ } else {
+ self.pos = Before(target);
+ }
+ } else {
+ let statement = &block_data.statements[target.statement_index];
+ analysis.apply_before_statement_effect(&mut self.state, statement, location);
+
+ if apply_after_effect_at_target {
+ analysis.apply_statement_effect(&mut self.state, statement, location);
+ self.pos = After(target)
+ } else {
+ self.pos = Before(target);
+ }
+ }
+ }
+}
+
+#[derive(Clone, Copy, Debug)]
+enum CursorPosition {
+ /// No effects within this block have been applied.
+ BlockStart(BasicBlock),
+
+ /// Only the "before" effect of the statement (or terminator) at this location has been
+ /// applied (along with the effects of all previous statements).
+ Before(Location),
+
+ /// The effects of all statements up to and including the one at this location have been
+ /// applied.
+ After(Location),
+}
+
+impl CursorPosition {
+ fn block(&self) -> BasicBlock {
+ match *self {
+ Self::BlockStart(block) => block,
+ Self::Before(loc) | Self::After(loc) => loc.block,
+ }
+ }
+}
--- /dev/null
+//! A solver for dataflow problems.
+
+use std::ffi::OsString;
+use std::fs;
+use std::path::PathBuf;
+
+use rustc::mir::{self, traversal, BasicBlock, Location};
+use rustc::ty::{self, TyCtxt};
+use rustc_ast::ast;
+use rustc_data_structures::work_queue::WorkQueue;
+use rustc_hir::def_id::DefId;
+use rustc_index::bit_set::BitSet;
+use rustc_index::vec::IndexVec;
+use rustc_span::symbol::{sym, Symbol};
+
+use super::graphviz;
+use super::{Analysis, GenKillAnalysis, GenKillSet, Results};
+
+/// A solver for dataflow problems.
+pub struct Engine<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ bits_per_block: usize,
+ tcx: TyCtxt<'tcx>,
+ body: &'a mir::Body<'tcx>,
+ def_id: DefId,
+ dead_unwinds: Option<&'a BitSet<BasicBlock>>,
+ entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
+ analysis: A,
+
+ /// Cached, cumulative transfer functions for each block.
+ trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+}
+
+impl<A> Engine<'a, 'tcx, A>
+where
+ A: GenKillAnalysis<'tcx>,
+{
+ /// Creates a new `Engine` to solve a gen-kill dataflow problem.
+ pub fn new_gen_kill(
+ tcx: TyCtxt<'tcx>,
+ body: &'a mir::Body<'tcx>,
+ def_id: DefId,
+ analysis: A,
+ ) -> Self {
+ // If there are no back-edges in the control-flow graph, we only ever need to apply the
+ // transfer function for each block exactly once (assuming that we process blocks in RPO).
+ //
+ // In this case, there's no need to compute the block transfer functions ahead of time.
+ if !body.is_cfg_cyclic() {
+ return Self::new(tcx, body, def_id, analysis, None);
+ }
+
+ // Otherwise, compute and store the cumulative transfer function for each block.
+
+ let bits_per_block = analysis.bits_per_block(body);
+ let mut trans_for_block =
+ IndexVec::from_elem(GenKillSet::identity(bits_per_block), body.basic_blocks());
+
+ for (block, block_data) in body.basic_blocks().iter_enumerated() {
+ let trans = &mut trans_for_block[block];
+
+ for (i, statement) in block_data.statements.iter().enumerate() {
+ let loc = Location { block, statement_index: i };
+ analysis.before_statement_effect(trans, statement, loc);
+ analysis.statement_effect(trans, statement, loc);
+ }
+
+ let terminator = block_data.terminator();
+ let loc = Location { block, statement_index: block_data.statements.len() };
+ analysis.before_terminator_effect(trans, terminator, loc);
+ analysis.terminator_effect(trans, terminator, loc);
+ }
+
+ Self::new(tcx, body, def_id, analysis, Some(trans_for_block))
+ }
+}
+
+impl<A> Engine<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
+ /// function.
+ ///
+ /// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
+ /// better performance.
+ pub fn new_generic(
+ tcx: TyCtxt<'tcx>,
+ body: &'a mir::Body<'tcx>,
+ def_id: DefId,
+ analysis: A,
+ ) -> Self {
+ Self::new(tcx, body, def_id, analysis, None)
+ }
+
+ fn new(
+ tcx: TyCtxt<'tcx>,
+ body: &'a mir::Body<'tcx>,
+ def_id: DefId,
+ analysis: A,
+ trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+ ) -> Self {
+ let bits_per_block = analysis.bits_per_block(body);
+
+ let bottom_value_set = if A::BOTTOM_VALUE {
+ BitSet::new_filled(bits_per_block)
+ } else {
+ BitSet::new_empty(bits_per_block)
+ };
+
+ let mut entry_sets = IndexVec::from_elem(bottom_value_set, body.basic_blocks());
+ analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
+
+ Engine {
+ analysis,
+ bits_per_block,
+ tcx,
+ body,
+ def_id,
+ dead_unwinds: None,
+ entry_sets,
+ trans_for_block,
+ }
+ }
+
+ /// Signals that we do not want dataflow state to propagate across unwind edges for these
+ /// `BasicBlock`s.
+ ///
+ /// You must take care that `dead_unwinds` does not contain a `BasicBlock` that *can* actually
+ /// unwind during execution. Otherwise, your dataflow results will not be correct.
+ pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
+ self.dead_unwinds = Some(dead_unwinds);
+ self
+ }
+
+ /// Computes the fixpoint for this dataflow problem and returns it.
+ pub fn iterate_to_fixpoint(mut self) -> Results<'tcx, A> {
+ let mut temp_state = BitSet::new_empty(self.bits_per_block);
+
+ let mut dirty_queue: WorkQueue<BasicBlock> =
+ WorkQueue::with_none(self.body.basic_blocks().len());
+
+ for (bb, _) in traversal::reverse_postorder(self.body) {
+ dirty_queue.insert(bb);
+ }
+
+ // Add blocks that are not reachable from START_BLOCK to the work queue. These blocks will
+ // be processed after the ones added above.
+ for bb in self.body.basic_blocks().indices() {
+ dirty_queue.insert(bb);
+ }
+
+ while let Some(bb) = dirty_queue.pop() {
+ let bb_data = &self.body[bb];
+ let on_entry = &self.entry_sets[bb];
+
+ temp_state.overwrite(on_entry);
+ self.apply_whole_block_effect(&mut temp_state, bb, bb_data);
+
+ self.propagate_bits_into_graph_successors_of(
+ &mut temp_state,
+ (bb, bb_data),
+ &mut dirty_queue,
+ );
+ }
+
+ let Engine { tcx, body, def_id, trans_for_block, entry_sets, analysis, .. } = self;
+ let results = Results { analysis, entry_sets };
+
+ let res = write_graphviz_results(tcx, def_id, body, &results, trans_for_block);
+ if let Err(e) = res {
+ warn!("Failed to write graphviz dataflow results: {}", e);
+ }
+
+ results
+ }
+
+ /// Applies the cumulative effect of an entire block, excluding the call return effect if one
+ /// exists.
+ fn apply_whole_block_effect(
+ &self,
+ state: &mut BitSet<A::Idx>,
+ block: BasicBlock,
+ block_data: &mir::BasicBlockData<'tcx>,
+ ) {
+ // Use the cached block transfer function if available.
+ if let Some(trans_for_block) = &self.trans_for_block {
+ trans_for_block[block].apply(state);
+ return;
+ }
+
+ // Otherwise apply effects one-by-one.
+
+ for (statement_index, statement) in block_data.statements.iter().enumerate() {
+ let location = Location { block, statement_index };
+ self.analysis.apply_before_statement_effect(state, statement, location);
+ self.analysis.apply_statement_effect(state, statement, location);
+ }
+
+ let terminator = block_data.terminator();
+ let location = Location { block, statement_index: block_data.statements.len() };
+ self.analysis.apply_before_terminator_effect(state, terminator, location);
+ self.analysis.apply_terminator_effect(state, terminator, location);
+ }
+
+ fn propagate_bits_into_graph_successors_of(
+ &mut self,
+ in_out: &mut BitSet<A::Idx>,
+ (bb, bb_data): (BasicBlock, &'a mir::BasicBlockData<'tcx>),
+ dirty_list: &mut WorkQueue<BasicBlock>,
+ ) {
+ use mir::TerminatorKind::*;
+
+ match bb_data.terminator().kind {
+ Return | Resume | Abort | GeneratorDrop | Unreachable => {}
+
+ Goto { target }
+ | Assert { target, cleanup: None, .. }
+ | Drop { target, location: _, unwind: None }
+ | DropAndReplace { target, value: _, location: _, unwind: None } => {
+ self.propagate_bits_into_entry_set_for(in_out, target, dirty_list)
+ }
+
+ Yield { resume: target, drop, resume_arg, .. } => {
+ if let Some(drop) = drop {
+ self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
+ }
+
+ self.analysis.apply_yield_resume_effect(in_out, target, &resume_arg);
+ self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
+ }
+
+ Assert { target, cleanup: Some(unwind), .. }
+ | Drop { target, location: _, unwind: Some(unwind) }
+ | DropAndReplace { target, value: _, location: _, unwind: Some(unwind) } => {
+ self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
+ if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
+ self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
+ }
+ }
+
+ SwitchInt { ref targets, ref values, ref discr, .. } => {
+ let Engine { tcx, body, .. } = *self;
+ let enum_ = discr
+ .place()
+ .and_then(|discr| switch_on_enum_discriminant(tcx, body, bb_data, discr));
+ match enum_ {
+ // If this is a switch on an enum discriminant, a custom effect may be applied
+ // along each outgoing edge.
+ Some((enum_place, enum_def)) => {
+ self.propagate_bits_into_enum_discriminant_switch_successors(
+ in_out, bb, enum_def, enum_place, dirty_list, &*values, &*targets,
+ );
+ }
+
+ // Otherwise, it's just a normal `SwitchInt`, and every successor sees the same
+ // exit state.
+ None => {
+ for target in targets.iter().copied() {
+ self.propagate_bits_into_entry_set_for(&in_out, target, dirty_list);
+ }
+ }
+ }
+ }
+
+ Call { cleanup, ref destination, ref func, ref args, .. } => {
+ if let Some(unwind) = cleanup {
+ if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
+ self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
+ }
+ }
+
+ if let Some((ref dest_place, dest_bb)) = *destination {
+ // N.B.: This must be done *last*, otherwise the unwind path will see the call
+ // return effect.
+ self.analysis.apply_call_return_effect(in_out, bb, func, args, dest_place);
+ self.propagate_bits_into_entry_set_for(in_out, dest_bb, dirty_list);
+ }
+ }
+
+ FalseEdges { real_target, imaginary_target } => {
+ self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
+ self.propagate_bits_into_entry_set_for(in_out, imaginary_target, dirty_list);
+ }
+
+ FalseUnwind { real_target, unwind } => {
+ self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
+ if let Some(unwind) = unwind {
+ if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
+ self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
+ }
+ }
+ }
+ }
+ }
+
+ fn propagate_bits_into_entry_set_for(
+ &mut self,
+ in_out: &BitSet<A::Idx>,
+ bb: BasicBlock,
+ dirty_queue: &mut WorkQueue<BasicBlock>,
+ ) {
+ let entry_set = &mut self.entry_sets[bb];
+ let set_changed = self.analysis.join(entry_set, &in_out);
+ if set_changed {
+ dirty_queue.insert(bb);
+ }
+ }
+
+ fn propagate_bits_into_enum_discriminant_switch_successors(
+ &mut self,
+ in_out: &mut BitSet<A::Idx>,
+ bb: BasicBlock,
+ enum_def: &'tcx ty::AdtDef,
+ enum_place: &mir::Place<'tcx>,
+ dirty_list: &mut WorkQueue<BasicBlock>,
+ values: &[u128],
+ targets: &[BasicBlock],
+ ) {
+ // MIR building adds discriminants to the `values` array in the same order as they
+ // are yielded by `AdtDef::discriminants`. We rely on this to match each
+ // discriminant in `values` to its corresponding variant in linear time.
+ let mut tmp = BitSet::new_empty(in_out.domain_size());
+ let mut discriminants = enum_def.discriminants(self.tcx);
+ for (value, target) in values.iter().zip(targets.iter().copied()) {
+ let (variant_idx, _) = discriminants.find(|&(_, discr)| discr.val == *value).expect(
+ "Order of `AdtDef::discriminants` differed from that of `SwitchInt::values`",
+ );
+
+ tmp.overwrite(in_out);
+ self.analysis.apply_discriminant_switch_effect(
+ &mut tmp,
+ bb,
+ enum_place,
+ enum_def,
+ variant_idx,
+ );
+ self.propagate_bits_into_entry_set_for(&tmp, target, dirty_list);
+ }
+
+ std::mem::drop(tmp);
+
+ // Propagate dataflow state along the "otherwise" edge.
+ let otherwise = targets.last().copied().unwrap();
+ self.propagate_bits_into_entry_set_for(&in_out, otherwise, dirty_list);
+ }
+}
+
+/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
+/// an enum discriminant.
+///
+/// We expect such blocks to have a call to `discriminant` as their last statement like so:
+/// _42 = discriminant(_1)
+/// SwitchInt(_42, ..)
+///
+/// If the basic block matches this pattern, this function returns the place corresponding to the
+/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
+fn switch_on_enum_discriminant(
+ tcx: TyCtxt<'tcx>,
+ body: &'mir mir::Body<'tcx>,
+ block: &'mir mir::BasicBlockData<'tcx>,
+ switch_on: &mir::Place<'tcx>,
+) -> Option<(&'mir mir::Place<'tcx>, &'tcx ty::AdtDef)> {
+ match block.statements.last().map(|stmt| &stmt.kind) {
+ Some(mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated))))
+ if lhs == switch_on =>
+ {
+ match &discriminated.ty(body, tcx).ty.kind {
+ ty::Adt(def, _) => Some((discriminated, def)),
+
+ // `Rvalue::Discriminant` is also used to get the active yield point for a
+ // generator, but we do not need edge-specific effects in that case. This may
+ // change in the future.
+ ty::Generator(..) => None,
+
+ t => bug!("`discriminant` called on unexpected type {:?}", t),
+ }
+ }
+
+ _ => None,
+ }
+}
+
+// Graphviz
+
+/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
+/// `rustc_mir` attributes.
+fn write_graphviz_results<A>(
+ tcx: TyCtxt<'tcx>,
+ def_id: DefId,
+ body: &mir::Body<'tcx>,
+ results: &Results<'tcx, A>,
+ block_transfer_functions: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
+) -> std::io::Result<()>
+where
+ A: Analysis<'tcx>,
+{
+ let attrs = match RustcMirAttrs::parse(tcx, def_id) {
+ Ok(attrs) => attrs,
+
+ // Invalid `rustc_mir` attrs will be reported using `span_err`.
+ Err(()) => return Ok(()),
+ };
+
+ let path = match attrs.output_path(A::NAME) {
+ Some(path) => path,
+ None => return Ok(()),
+ };
+
+ let bits_per_block = results.analysis.bits_per_block(body);
+
+ let mut formatter: Box<dyn graphviz::StateFormatter<'tcx, _>> = match attrs.formatter {
+ Some(sym::two_phase) => Box::new(graphviz::TwoPhaseDiff::new(bits_per_block)),
+ Some(sym::gen_kill) => {
+ if let Some(trans_for_block) = block_transfer_functions {
+ Box::new(graphviz::BlockTransferFunc::new(body, trans_for_block))
+ } else {
+ Box::new(graphviz::SimpleDiff::new(bits_per_block))
+ }
+ }
+
+ // Default to the `SimpleDiff` output style.
+ _ => Box::new(graphviz::SimpleDiff::new(bits_per_block)),
+ };
+
+ debug!("printing dataflow results for {:?} to {}", def_id, path.display());
+ let mut buf = Vec::new();
+
+ let graphviz = graphviz::Formatter::new(body, def_id, results, &mut *formatter);
+ dot::render_opts(&graphviz, &mut buf, &[dot::RenderOption::Monospace])?;
+ fs::write(&path, buf)?;
+ Ok(())
+}
+
+#[derive(Default)]
+struct RustcMirAttrs {
+ basename_and_suffix: Option<PathBuf>,
+ formatter: Option<Symbol>,
+}
+
+impl RustcMirAttrs {
+ fn parse(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<Self, ()> {
+ let attrs = tcx.get_attrs(def_id);
+
+ let mut result = Ok(());
+ let mut ret = RustcMirAttrs::default();
+
+ let rustc_mir_attrs = attrs
+ .iter()
+ .filter(|attr| attr.check_name(sym::rustc_mir))
+ .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
+
+ for attr in rustc_mir_attrs {
+ let attr_result = if attr.check_name(sym::borrowck_graphviz_postflow) {
+ Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| {
+ let path = PathBuf::from(s.to_string());
+ match path.file_name() {
+ Some(_) => Ok(path),
+ None => {
+ tcx.sess.span_err(attr.span(), "path must end in a filename");
+ Err(())
+ }
+ }
+ })
+ } else if attr.check_name(sym::borrowck_graphviz_format) {
+ Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s {
+ sym::gen_kill | sym::two_phase => Ok(s),
+ _ => {
+ tcx.sess.span_err(attr.span(), "unknown formatter");
+ Err(())
+ }
+ })
+ } else {
+ Ok(())
+ };
+
+ result = result.and(attr_result);
+ }
+
+ result.map(|()| ret)
+ }
+
+ fn set_field<T>(
+ field: &mut Option<T>,
+ tcx: TyCtxt<'tcx>,
+ attr: &ast::NestedMetaItem,
+ mapper: impl FnOnce(Symbol) -> Result<T, ()>,
+ ) -> Result<(), ()> {
+ if field.is_some() {
+ tcx.sess
+ .span_err(attr.span(), &format!("duplicate values for `{}`", attr.name_or_empty()));
+
+ return Err(());
+ }
+
+ if let Some(s) = attr.value_str() {
+ *field = Some(mapper(s)?);
+ Ok(())
+ } else {
+ tcx.sess
+ .span_err(attr.span(), &format!("`{}` requires an argument", attr.name_or_empty()));
+ Err(())
+ }
+ }
+
+ /// Returns the path where dataflow results should be written, or `None`
+ /// `borrowck_graphviz_postflow` was not specified.
+ ///
+ /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
+ ///
+ /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
+ fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
+ let mut ret = self.basename_and_suffix.as_ref().cloned()?;
+ let suffix = ret.file_name().unwrap(); // Checked when parsing attrs
+
+ let mut file_name: OsString = analysis_name.into();
+ file_name.push("_");
+ file_name.push(suffix);
+ ret.set_file_name(file_name);
+
+ Some(ret)
+ }
+}
--- /dev/null
+//! A helpful diagram for debugging dataflow problems.
+
+use std::cell::RefCell;
+use std::{io, ops, str};
+
+use rustc::mir::{self, BasicBlock, Body, Location};
+use rustc_hir::def_id::DefId;
+use rustc_index::bit_set::{BitSet, HybridBitSet};
+use rustc_index::vec::{Idx, IndexVec};
+
+use super::{Analysis, GenKillSet, Results, ResultsRefCursor};
+use crate::util::graphviz_safe_def_name;
+
+pub struct Formatter<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ body: &'a Body<'tcx>,
+ def_id: DefId,
+
+ // This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
+ block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
+}
+
+impl<A> Formatter<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ pub fn new(
+ body: &'a Body<'tcx>,
+ def_id: DefId,
+ results: &'a Results<'tcx, A>,
+ state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
+ ) -> Self {
+ let block_formatter = BlockFormatter {
+ bg: Background::Light,
+ results: ResultsRefCursor::new(body, results),
+ state_formatter,
+ };
+
+ Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
+ }
+}
+
+/// A pair of a basic block and an index into that basic blocks `successors`.
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub struct CfgEdge {
+ source: BasicBlock,
+ index: usize,
+}
+
+fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
+ body[bb]
+ .terminator()
+ .successors()
+ .enumerate()
+ .map(|(index, _)| CfgEdge { source: bb, index })
+ .collect()
+}
+
+impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ type Node = BasicBlock;
+ type Edge = CfgEdge;
+
+ fn graph_id(&self) -> dot::Id<'_> {
+ let name = graphviz_safe_def_name(self.def_id);
+ dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
+ }
+
+ fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
+ dot::Id::new(format!("bb_{}", n.index())).unwrap()
+ }
+
+ fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
+ let mut label = Vec::new();
+ self.block_formatter.borrow_mut().write_node_label(&mut label, self.body, *block).unwrap();
+ dot::LabelText::html(String::from_utf8(label).unwrap())
+ }
+
+ fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
+ Some(dot::LabelText::label("none"))
+ }
+
+ fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
+ let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
+ dot::LabelText::label(label.clone())
+ }
+}
+
+impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ type Node = BasicBlock;
+ type Edge = CfgEdge;
+
+ fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
+ self.body.basic_blocks().indices().collect::<Vec<_>>().into()
+ }
+
+ fn edges(&self) -> dot::Edges<'_, Self::Edge> {
+ self.body
+ .basic_blocks()
+ .indices()
+ .flat_map(|bb| outgoing_edges(self.body, bb))
+ .collect::<Vec<_>>()
+ .into()
+ }
+
+ fn source(&self, edge: &Self::Edge) -> Self::Node {
+ edge.source
+ }
+
+ fn target(&self, edge: &Self::Edge) -> Self::Node {
+ self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap()
+ }
+}
+
+struct BlockFormatter<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ results: ResultsRefCursor<'a, 'a, 'tcx, A>,
+ bg: Background,
+ state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
+}
+
+impl<A> BlockFormatter<'a, 'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ const HEADER_COLOR: &'static str = "#a0a0a0";
+
+ fn num_state_columns(&self) -> usize {
+ std::cmp::max(1, self.state_formatter.column_names().len())
+ }
+
+ fn toggle_background(&mut self) -> Background {
+ let bg = self.bg;
+ self.bg = !bg;
+ bg
+ }
+
+ fn write_node_label(
+ &mut self,
+ w: &mut impl io::Write,
+ body: &'a Body<'tcx>,
+ block: BasicBlock,
+ ) -> io::Result<()> {
+ // Sample output:
+ // +-+-----------------------------------------------+
+ // A | bb4 |
+ // +-+----------------------------------+------------+
+ // B | MIR | STATE |
+ // +-+----------------------------------+------------+
+ // C | | (on entry) | {_0,_2,_3} |
+ // +-+----------------------------------+------------+
+ // D |0| StorageLive(_7) | |
+ // +-+----------------------------------+------------+
+ // |1| StorageLive(_8) | |
+ // +-+----------------------------------+------------+
+ // |2| _8 = &mut _1 | +_8 |
+ // +-+----------------------------------+------------+
+ // E |T| _4 = const Foo::twiddle(move _2) | -_2 |
+ // +-+----------------------------------+------------+
+ // F | | (on unwind) | {_0,_3,_8} |
+ // +-+----------------------------------+------------+
+ // | | (on successful return) | +_4 |
+ // +-+----------------------------------+------------+
+
+ // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
+ // children. This is because `xdot` seemed to have a hard time correctly propagating
+ // attributes. Make sure to test the output before trying to remove the redundancy.
+ // Notably, `align` was found to have no effect when applied only to <table>.
+
+ let table_fmt = concat!(
+ " border=\"1\"",
+ " cellborder=\"1\"",
+ " cellspacing=\"0\"",
+ " cellpadding=\"3\"",
+ " sides=\"rb\"",
+ );
+ write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
+
+ // A + B: Block header
+ if self.state_formatter.column_names().is_empty() {
+ self.write_block_header_simple(w, block)?;
+ } else {
+ self.write_block_header_with_state_columns(w, block)?;
+ }
+
+ // C: Entry state
+ self.bg = Background::Light;
+ self.results.seek_to_block_start(block);
+ let block_entry_state = self.results.get().clone();
+
+ self.write_row_with_full_state(w, "", "(on entry)")?;
+
+ // D: Statement transfer functions
+ for (i, statement) in body[block].statements.iter().enumerate() {
+ let location = Location { block, statement_index: i };
+ let statement_str = format!("{:?}", statement);
+ self.write_row_for_location(w, &i.to_string(), &statement_str, location)?;
+ }
+
+ // E: Terminator transfer function
+ let terminator = body[block].terminator();
+ let terminator_loc = body.terminator_loc(block);
+ let mut terminator_str = String::new();
+ terminator.kind.fmt_head(&mut terminator_str).unwrap();
+
+ self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?;
+
+ // F: Exit state
+
+ // Write the full dataflow state immediately after the terminator if it differs from the
+ // state at block entry.
+ self.results.seek_after(terminator_loc);
+ if self.results.get() != &block_entry_state {
+ let after_terminator_name = match terminator.kind {
+ mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
+ _ => "(on exit)",
+ };
+
+ self.write_row_with_full_state(w, "", after_terminator_name)?;
+ }
+
+ // Write any changes caused by terminator-specific effects
+ match terminator.kind {
+ mir::TerminatorKind::Call { destination: Some(_), .. } => {
+ let num_state_columns = self.num_state_columns();
+ self.write_row(w, "", "(on successful return)", |this, w, fmt| {
+ write!(
+ w,
+ r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
+ colspan = num_state_columns,
+ fmt = fmt,
+ )?;
+
+ let state_on_unwind = this.results.get().clone();
+ this.results.seek_after_assume_success(terminator_loc);
+ write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
+
+ write!(w, "</td>")
+ })?;
+ }
+
+ _ => {}
+ };
+
+ write!(w, "</table>")
+ }
+
+ fn write_block_header_simple(
+ &mut self,
+ w: &mut impl io::Write,
+ block: BasicBlock,
+ ) -> io::Result<()> {
+ // +-------------------------------------------------+
+ // A | bb4 |
+ // +-----------------------------------+-------------+
+ // B | MIR | STATE |
+ // +-+---------------------------------+-------------+
+ // | | ... | |
+
+ // A
+ write!(
+ w,
+ concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
+ block_id = block.index(),
+ )?;
+
+ // B
+ write!(
+ w,
+ concat!(
+ "<tr>",
+ r#"<td colspan="2" {fmt}>MIR</td>"#,
+ r#"<td {fmt}>STATE</td>"#,
+ "</tr>",
+ ),
+ fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
+ )
+ }
+
+ fn write_block_header_with_state_columns(
+ &mut self,
+ w: &mut impl io::Write,
+ block: BasicBlock,
+ ) -> io::Result<()> {
+ // +------------------------------------+-------------+
+ // A | bb4 | STATE |
+ // +------------------------------------+------+------+
+ // B | MIR | GEN | KILL |
+ // +-+----------------------------------+------+------+
+ // | | ... | | |
+
+ let state_column_names = self.state_formatter.column_names();
+
+ // A
+ write!(
+ w,
+ concat!(
+ "<tr>",
+ r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
+ r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
+ "</tr>",
+ ),
+ fmt = "sides=\"tl\"",
+ num_state_cols = state_column_names.len(),
+ block_id = block.index(),
+ )?;
+
+ // B
+ let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
+ write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
+
+ for name in state_column_names {
+ write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
+ }
+
+ write!(w, "</tr>")
+ }
+
+ /// Write a row with the given index and MIR, using the function argument to fill in the
+ /// "STATE" column(s).
+ fn write_row<W: io::Write>(
+ &mut self,
+ w: &mut W,
+ i: &str,
+ mir: &str,
+ f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
+ ) -> io::Result<()> {
+ let bg = self.toggle_background();
+ let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
+
+ let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
+
+ write!(
+ w,
+ concat!(
+ "<tr>",
+ r#"<td {fmt} align="right">{i}</td>"#,
+ r#"<td {fmt} align="left">{mir}</td>"#,
+ ),
+ i = i,
+ fmt = fmt,
+ mir = dot::escape_html(mir),
+ )?;
+
+ f(self, w, &fmt)?;
+ write!(w, "</tr>")
+ }
+
+ fn write_row_with_full_state(
+ &mut self,
+ w: &mut impl io::Write,
+ i: &str,
+ mir: &str,
+ ) -> io::Result<()> {
+ self.write_row(w, i, mir, |this, w, fmt| {
+ let state = this.results.get();
+ let analysis = this.results.analysis();
+
+ write!(
+ w,
+ r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
+ colspan = this.num_state_columns(),
+ fmt = fmt,
+ )?;
+ pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
+ write!(w, "}}</td>")
+ })
+ }
+
+ fn write_row_for_location(
+ &mut self,
+ w: &mut impl io::Write,
+ i: &str,
+ mir: &str,
+ location: Location,
+ ) -> io::Result<()> {
+ self.write_row(w, i, mir, |this, w, fmt| {
+ this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
+ })
+ }
+}
+
+/// Controls what gets printed under the `STATE` header.
+pub trait StateFormatter<'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ /// The columns that will get printed under `STATE`.
+ fn column_names(&self) -> &[&str];
+
+ fn write_state_for_location(
+ &mut self,
+ w: &mut dyn io::Write,
+ fmt: &str,
+ results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
+ location: Location,
+ ) -> io::Result<()>;
+}
+
+/// Prints a single column containing the state vector immediately *after* each statement.
+pub struct SimpleDiff<T: Idx> {
+ prev_state: BitSet<T>,
+ prev_loc: Location,
+}
+
+impl<T: Idx> SimpleDiff<T> {
+ pub fn new(bits_per_block: usize) -> Self {
+ SimpleDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
+ }
+}
+
+impl<A> StateFormatter<'tcx, A> for SimpleDiff<A::Idx>
+where
+ A: Analysis<'tcx>,
+{
+ fn column_names(&self) -> &[&str] {
+ &[]
+ }
+
+ fn write_state_for_location(
+ &mut self,
+ mut w: &mut dyn io::Write,
+ fmt: &str,
+ results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
+ location: Location,
+ ) -> io::Result<()> {
+ if location.statement_index == 0 {
+ results.seek_to_block_start(location.block);
+ self.prev_state.overwrite(results.get());
+ } else {
+ // Ensure that we are visiting statements in order, so `prev_state` is correct.
+ assert_eq!(self.prev_loc.successor_within_block(), location);
+ }
+
+ self.prev_loc = location;
+ write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
+ results.seek_after(location);
+ let curr_state = results.get();
+ write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
+ self.prev_state.overwrite(curr_state);
+ write!(w, "</td>")
+ }
+}
+
+/// Prints two state columns, one containing only the "before" effect of each statement and one
+/// containing the full effect.
+pub struct TwoPhaseDiff<T: Idx> {
+ prev_state: BitSet<T>,
+ prev_loc: Location,
+}
+
+impl<T: Idx> TwoPhaseDiff<T> {
+ pub fn new(bits_per_block: usize) -> Self {
+ TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
+ }
+}
+
+impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
+where
+ A: Analysis<'tcx>,
+{
+ fn column_names(&self) -> &[&str] {
+ &["BEFORE", " AFTER"]
+ }
+
+ fn write_state_for_location(
+ &mut self,
+ mut w: &mut dyn io::Write,
+ fmt: &str,
+ results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
+ location: Location,
+ ) -> io::Result<()> {
+ if location.statement_index == 0 {
+ results.seek_to_block_start(location.block);
+ self.prev_state.overwrite(results.get());
+ } else {
+ // Ensure that we are visiting statements in order, so `prev_state` is correct.
+ assert_eq!(self.prev_loc.successor_within_block(), location);
+ }
+
+ self.prev_loc = location;
+
+ // Before
+
+ write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
+ results.seek_before(location);
+ let curr_state = results.get();
+ write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
+ self.prev_state.overwrite(curr_state);
+ write!(w, "</td>")?;
+
+ // After
+
+ write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
+ results.seek_after(location);
+ let curr_state = results.get();
+ write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
+ self.prev_state.overwrite(curr_state);
+ write!(w, "</td>")
+ }
+}
+
+/// Prints the gen/kill set for the entire block.
+pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
+ body: &'a mir::Body<'tcx>,
+ trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
+}
+
+impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
+ pub fn new(
+ body: &'mir mir::Body<'tcx>,
+ trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
+ ) -> Self {
+ BlockTransferFunc { body, trans_for_block }
+ }
+}
+
+impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
+where
+ A: Analysis<'tcx>,
+{
+ fn column_names(&self) -> &[&str] {
+ &["GEN", "KILL"]
+ }
+
+ fn write_state_for_location(
+ &mut self,
+ mut w: &mut dyn io::Write,
+ fmt: &str,
+ results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
+ location: Location,
+ ) -> io::Result<()> {
+ // Only print a single row.
+ if location.statement_index != 0 {
+ return Ok(());
+ }
+
+ let block_trans = &self.trans_for_block[location.block];
+ let rowspan = self.body.basic_blocks()[location.block].statements.len();
+
+ for set in &[&block_trans.gen, &block_trans.kill] {
+ write!(
+ w,
+ r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
+ fmt = fmt,
+ rowspan = rowspan
+ )?;
+
+ pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
+ write!(w, "</td>")?;
+ }
+
+ Ok(())
+ }
+}
+
+/// Writes two lines, one containing the added bits and one the removed bits.
+fn write_diff<A: Analysis<'tcx>>(
+ w: &mut impl io::Write,
+ analysis: &A,
+ from: &BitSet<A::Idx>,
+ to: &BitSet<A::Idx>,
+) -> io::Result<()> {
+ assert_eq!(from.domain_size(), to.domain_size());
+ let len = from.domain_size();
+
+ let mut set = HybridBitSet::new_empty(len);
+ let mut clear = HybridBitSet::new_empty(len);
+
+ // FIXME: Implement a lazy iterator over the symmetric difference of two bitsets.
+ for i in (0..len).map(A::Idx::new) {
+ match (from.contains(i), to.contains(i)) {
+ (false, true) => set.insert(i),
+ (true, false) => clear.insert(i),
+ _ => continue,
+ };
+ }
+
+ if !set.is_empty() {
+ write!(w, r#"<font color="darkgreen">+"#)?;
+ pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
+ write!(w, r#"</font>"#)?;
+ }
+
+ if !set.is_empty() && !clear.is_empty() {
+ write!(w, "{}", BR_LEFT)?;
+ }
+
+ if !clear.is_empty() {
+ write!(w, r#"<font color="red">-"#)?;
+ pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
+ write!(w, r#"</font>"#)?;
+ }
+
+ Ok(())
+}
+
+const BR_LEFT: &str = r#"<br align="left"/>"#;
+const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
+
+/// Line break policy that breaks at 40 characters and starts the next line with a single space.
+const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
+
+struct LineBreak {
+ sequence: &'static str,
+ limit: usize,
+}
+
+/// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
+/// separator (`sep`).
+///
+/// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
+/// character limit.
+fn pretty_print_state_elems<A>(
+ w: &mut impl io::Write,
+ analysis: &A,
+ elems: impl Iterator<Item = A::Idx>,
+ sep: &str,
+ line_break: Option<LineBreak>,
+) -> io::Result<bool>
+where
+ A: Analysis<'tcx>,
+{
+ let sep_width = sep.chars().count();
+
+ let mut buf = Vec::new();
+
+ let mut first = true;
+ let mut curr_line_width = 0;
+ let mut line_break_inserted = false;
+
+ for idx in elems {
+ buf.clear();
+ analysis.pretty_print_idx(&mut buf, idx)?;
+ let idx_str =
+ str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
+ let escaped = dot::escape_html(idx_str);
+ let escaped_width = escaped.chars().count();
+
+ if first {
+ first = false;
+ } else {
+ write!(w, "{}", sep)?;
+ curr_line_width += sep_width;
+
+ if let Some(line_break) = &line_break {
+ if curr_line_width + sep_width + escaped_width > line_break.limit {
+ write!(w, "{}", line_break.sequence)?;
+ line_break_inserted = true;
+ curr_line_width = 0;
+ }
+ }
+ }
+
+ write!(w, "{}", escaped)?;
+ curr_line_width += escaped_width;
+ }
+
+ Ok(line_break_inserted)
+}
+
+/// The background color used for zebra-striping the table.
+#[derive(Clone, Copy)]
+enum Background {
+ Light,
+ Dark,
+}
+
+impl Background {
+ fn attr(self) -> &'static str {
+ match self {
+ Self::Dark => "bgcolor=\"#f0f0f0\"",
+ Self::Light => "",
+ }
+ }
+}
+
+impl ops::Not for Background {
+ type Output = Self;
+
+ fn not(self) -> Self {
+ match self {
+ Self::Light => Self::Dark,
+ Self::Dark => Self::Light,
+ }
+ }
+}
--- /dev/null
+//! A framework that can express both [gen-kill] and generic dataflow problems.
+//!
+//! To actually use this framework, you must implement either the `Analysis` or the
+//! `GenKillAnalysis` trait. If your transfer function can be expressed with only gen/kill
+//! operations, prefer `GenKillAnalysis` since it will run faster while iterating to fixpoint. The
+//! `impls` module contains several examples of gen/kill dataflow analyses.
+//!
+//! Create an `Engine` for your analysis using the `into_engine` method on the `Analysis` trait,
+//! then call `iterate_to_fixpoint`. From there, you can use a `ResultsCursor` to inspect the
+//! fixpoint solution to your dataflow problem, or implement the `ResultsVisitor` interface and use
+//! `visit_results`. The following example uses the `ResultsCursor` approach.
+//!
+//! ```ignore(cross-crate-imports)
+//! use rustc_mir::dataflow::Analysis; // Makes `into_engine` available.
+//!
+//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>, did: DefId) {
+//! let analysis = MyAnalysis::new()
+//! .into_engine(tcx, body, did)
+//! .iterate_to_fixpoint()
+//! .into_results_cursor(body);
+//!
+//! // Print the dataflow state *after* each statement in the start block.
+//! for (_, statement_index) in body.block_data[START_BLOCK].statements.iter_enumerated() {
+//! cursor.seek_after(Location { block: START_BLOCK, statement_index });
+//! let state = cursor.get();
+//! println!("{:?}", state);
+//! }
+//! }
+//! ```
+//!
+//! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems
+
+use std::io;
+
+use rustc::mir::{self, BasicBlock, Location};
+use rustc::ty::layout::VariantIdx;
+use rustc::ty::{self, TyCtxt};
+use rustc_hir::def_id::DefId;
+use rustc_index::bit_set::{BitSet, HybridBitSet};
+use rustc_index::vec::{Idx, IndexVec};
+
+mod cursor;
+mod engine;
+mod graphviz;
+mod visitor;
+
+pub use self::cursor::{ResultsCursor, ResultsRefCursor};
+pub use self::engine::Engine;
+pub use self::visitor::{visit_results, ResultsVisitor};
+pub use self::visitor::{BorrowckFlowState, BorrowckResults};
+
+/// A dataflow analysis that has converged to fixpoint.
+pub struct Results<'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ pub analysis: A,
+ entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
+}
+
+impl<A> Results<'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ /// Creates a `ResultsCursor` that can inspect these `Results`.
+ pub fn into_results_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
+ ResultsCursor::new(body, self)
+ }
+
+ /// Gets the entry set for the given block.
+ pub fn entry_set_for_block(&self, block: BasicBlock) -> &BitSet<A::Idx> {
+ &self.entry_sets[block]
+ }
+
+ pub fn visit_with(
+ &self,
+ body: &'mir mir::Body<'tcx>,
+ blocks: impl IntoIterator<Item = BasicBlock>,
+ vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
+ ) {
+ visit_results(body, blocks, self, vis)
+ }
+
+ pub fn visit_in_rpo_with(
+ &self,
+ body: &'mir mir::Body<'tcx>,
+ vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
+ ) {
+ let blocks = mir::traversal::reverse_postorder(body);
+ visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
+ }
+}
+
+/// Parameterization for the precise form of data flow that is used.
+///
+/// `BottomValue` determines whether the initial entry set for each basic block is empty or full.
+/// This also determines the semantics of the lattice `join` operator used to merge dataflow
+/// results, since dataflow works by starting at the bottom and moving monotonically to a fixed
+/// point.
+///
+/// This means, for propagation across the graph, that you either want to start at all-zeroes and
+/// then use Union as your merge when propagating, or you start at all-ones and then use Intersect
+/// as your merge when propagating.
+pub trait BottomValue {
+ /// Specifies the initial value for each bit in the entry set for each basic block.
+ const BOTTOM_VALUE: bool;
+
+ /// Merges `in_set` into `inout_set`, returning `true` if `inout_set` changed.
+ ///
+ /// It is almost certainly wrong to override this, since it automatically applies
+ /// * `inout_set & in_set` if `BOTTOM_VALUE == true`
+ /// * `inout_set | in_set` if `BOTTOM_VALUE == false`
+ ///
+ /// This means that if a bit is not `BOTTOM_VALUE`, it is propagated into all target blocks.
+ /// For clarity, the above statement again from a different perspective:
+ /// A bit in the block's entry set is `!BOTTOM_VALUE` if *any* predecessor block's bit value is
+ /// `!BOTTOM_VALUE`.
+ ///
+ /// There are situations where you want the opposite behaviour: propagate only if *all*
+ /// predecessor blocks's value is `!BOTTOM_VALUE`.
+ /// E.g. if you want to know whether a bit is *definitely* set at a specific location. This
+ /// means that all code paths leading to the location must have set the bit, instead of any
+ /// code path leading there.
+ ///
+ /// If you want this kind of "definitely set" analysis, you need to
+ /// 1. Invert `BOTTOM_VALUE`
+ /// 2. Reset the `entry_set` in `start_block_effect` to `!BOTTOM_VALUE`
+ /// 3. Override `join` to do the opposite from what it's doing now.
+ #[inline]
+ fn join<T: Idx>(&self, inout_set: &mut BitSet<T>, in_set: &BitSet<T>) -> bool {
+ if !Self::BOTTOM_VALUE { inout_set.union(in_set) } else { inout_set.intersect(in_set) }
+ }
+}
+
+/// Define the domain of a dataflow problem.
+///
+/// This trait specifies the lattice on which this analysis operates. For now, this must be a
+/// powerset of values of type `Idx`. The elements of this lattice are represented with a `BitSet`
+/// and referred to as the state vector.
+///
+/// This trait also defines the initial value for the dataflow state upon entry to the
+/// `START_BLOCK`, as well as some names used to refer to this analysis when debugging.
+pub trait AnalysisDomain<'tcx>: BottomValue {
+ /// The type of the elements in the state vector.
+ type Idx: Idx;
+
+ /// A descriptive name for this analysis. Used only for debugging.
+ ///
+ /// This name should be brief and contain no spaces, periods or other characters that are not
+ /// suitable as part of a filename.
+ const NAME: &'static str;
+
+ /// The size of the state vector.
+ fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize;
+
+ /// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow
+ /// analysis.
+ fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>);
+
+ /// Prints an element in the state vector for debugging.
+ fn pretty_print_idx(&self, w: &mut impl io::Write, idx: Self::Idx) -> io::Result<()> {
+ write!(w, "{:?}", idx)
+ }
+}
+
+/// A dataflow problem with an arbitrarily complex transfer function.
+pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
+ /// Updates the current dataflow state with the effect of evaluating a statement.
+ fn apply_statement_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ );
+
+ /// Updates the current dataflow state with an effect that occurs immediately *before* the
+ /// given statement.
+ ///
+ /// This method is useful if the consumer of the results of this analysis needs only to observe
+ /// *part* of the effect of a statement (e.g. for two-phase borrows). As a general rule,
+ /// analyses should not implement this without implementing `apply_statement_effect`.
+ fn apply_before_statement_effect(
+ &self,
+ _state: &mut BitSet<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// Updates the current dataflow state with the effect of evaluating a terminator.
+ ///
+ /// The effect of a successful return from a `Call` terminator should **not** be accounted for
+ /// in this function. That should go in `apply_call_return_effect`. For example, in the
+ /// `InitializedPlaces` analyses, the return place for a function call is not marked as
+ /// initialized here.
+ fn apply_terminator_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ );
+
+ /// Updates the current dataflow state with an effect that occurs immediately *before* the
+ /// given terminator.
+ ///
+ /// This method is useful if the consumer of the results of this analysis needs only to observe
+ /// *part* of the effect of a terminator (e.g. for two-phase borrows). As a general rule,
+ /// analyses should not implement this without implementing `apply_terminator_effect`.
+ fn apply_before_terminator_effect(
+ &self,
+ _state: &mut BitSet<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// Updates the current dataflow state with the effect of a successful return from a `Call`
+ /// terminator.
+ ///
+ /// This is separate from `apply_terminator_effect` to properly track state across unwind
+ /// edges.
+ fn apply_call_return_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ block: BasicBlock,
+ func: &mir::Operand<'tcx>,
+ args: &[mir::Operand<'tcx>],
+ return_place: &mir::Place<'tcx>,
+ );
+
+ /// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
+ ///
+ /// This is similar to `apply_call_return_effect` in that it only takes place after the
+ /// generator is resumed, not when it is dropped.
+ ///
+ /// By default, no effects happen.
+ fn apply_yield_resume_effect(
+ &self,
+ _state: &mut BitSet<Self::Idx>,
+ _resume_block: BasicBlock,
+ _resume_place: &mir::Place<'tcx>,
+ ) {
+ }
+
+ /// Updates the current dataflow state with the effect of taking a particular branch in a
+ /// `SwitchInt` terminator.
+ ///
+ /// Much like `apply_call_return_effect`, this effect is only propagated along a single
+ /// outgoing edge from this basic block.
+ fn apply_discriminant_switch_effect(
+ &self,
+ _state: &mut BitSet<Self::Idx>,
+ _block: BasicBlock,
+ _enum_place: &mir::Place<'tcx>,
+ _adt: &ty::AdtDef,
+ _variant: VariantIdx,
+ ) {
+ }
+
+ /// Creates an `Engine` to find the fixpoint for this dataflow problem.
+ ///
+ /// You shouldn't need to override this outside this module, since the combination of the
+ /// default impl and the one for all `A: GenKillAnalysis` will do the right thing.
+ /// Its purpose is to enable method chaining like so:
+ ///
+ /// ```ignore(cross-crate-imports)
+ /// let results = MyAnalysis::new(tcx, body)
+ /// .into_engine(tcx, body, def_id)
+ /// .iterate_to_fixpoint()
+ /// .into_results_cursor(body);
+ /// ```
+ fn into_engine(
+ self,
+ tcx: TyCtxt<'tcx>,
+ body: &'mir mir::Body<'tcx>,
+ def_id: DefId,
+ ) -> Engine<'mir, 'tcx, Self>
+ where
+ Self: Sized,
+ {
+ Engine::new_generic(tcx, body, def_id, self)
+ }
+}
+
+/// A gen/kill dataflow problem.
+///
+/// Each method in this trait has a corresponding one in `Analysis`. However, these methods only
+/// allow modification of the dataflow state via "gen" and "kill" operations. By defining transfer
+/// functions for each statement in this way, the transfer function for an entire basic block can
+/// be computed efficiently.
+///
+/// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`.
+pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
+ /// See `Analysis::apply_statement_effect`.
+ fn statement_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ );
+
+ /// See `Analysis::apply_before_statement_effect`.
+ fn before_statement_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// See `Analysis::apply_terminator_effect`.
+ fn terminator_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ );
+
+ /// See `Analysis::apply_before_terminator_effect`.
+ fn before_terminator_effect(
+ &self,
+ _trans: &mut impl GenKill<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// See `Analysis::apply_call_return_effect`.
+ fn call_return_effect(
+ &self,
+ trans: &mut impl GenKill<Self::Idx>,
+ block: BasicBlock,
+ func: &mir::Operand<'tcx>,
+ args: &[mir::Operand<'tcx>],
+ return_place: &mir::Place<'tcx>,
+ );
+
+ /// See `Analysis::apply_yield_resume_effect`.
+ fn yield_resume_effect(
+ &self,
+ _trans: &mut BitSet<Self::Idx>,
+ _resume_block: BasicBlock,
+ _resume_place: &mir::Place<'tcx>,
+ ) {
+ }
+
+ /// See `Analysis::apply_discriminant_switch_effect`.
+ fn discriminant_switch_effect(
+ &self,
+ _state: &mut impl GenKill<Self::Idx>,
+ _block: BasicBlock,
+ _enum_place: &mir::Place<'tcx>,
+ _adt: &ty::AdtDef,
+ _variant: VariantIdx,
+ ) {
+ }
+}
+
+impl<A> Analysis<'tcx> for A
+where
+ A: GenKillAnalysis<'tcx>,
+{
+ fn apply_statement_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ self.statement_effect(state, statement, location);
+ }
+
+ fn apply_before_statement_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ self.before_statement_effect(state, statement, location);
+ }
+
+ fn apply_terminator_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ ) {
+ self.terminator_effect(state, terminator, location);
+ }
+
+ fn apply_before_terminator_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ ) {
+ self.before_terminator_effect(state, terminator, location);
+ }
+
+ fn apply_call_return_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ block: BasicBlock,
+ func: &mir::Operand<'tcx>,
+ args: &[mir::Operand<'tcx>],
+ return_place: &mir::Place<'tcx>,
+ ) {
+ self.call_return_effect(state, block, func, args, return_place);
+ }
+
+ fn apply_yield_resume_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ resume_block: BasicBlock,
+ resume_place: &mir::Place<'tcx>,
+ ) {
+ self.yield_resume_effect(state, resume_block, resume_place);
+ }
+
+ fn apply_discriminant_switch_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ block: BasicBlock,
+ enum_place: &mir::Place<'tcx>,
+ adt: &ty::AdtDef,
+ variant: VariantIdx,
+ ) {
+ self.discriminant_switch_effect(state, block, enum_place, adt, variant);
+ }
+
+ fn into_engine(
+ self,
+ tcx: TyCtxt<'tcx>,
+ body: &'mir mir::Body<'tcx>,
+ def_id: DefId,
+ ) -> Engine<'mir, 'tcx, Self>
+ where
+ Self: Sized,
+ {
+ Engine::new_gen_kill(tcx, body, def_id, self)
+ }
+}
+
+/// The legal operations for a transfer function in a gen/kill problem.
+///
+/// This abstraction exists because there are two different contexts in which we call the methods in
+/// `GenKillAnalysis`. Sometimes we need to store a single transfer function that can be efficiently
+/// applied multiple times, such as when computing the cumulative transfer function for each block.
+/// These cases require a `GenKillSet`, which in turn requires two `BitSet`s of storage. Oftentimes,
+/// however, we only need to apply an effect once. In *these* cases, it is more efficient to pass the
+/// `BitSet` representing the state vector directly into the `*_effect` methods as opposed to
+/// building up a `GenKillSet` and then throwing it away.
+pub trait GenKill<T> {
+ /// Inserts `elem` into the state vector.
+ fn gen(&mut self, elem: T);
+
+ /// Removes `elem` from the state vector.
+ fn kill(&mut self, elem: T);
+
+ /// Calls `gen` for each element in `elems`.
+ fn gen_all(&mut self, elems: impl IntoIterator<Item = T>) {
+ for elem in elems {
+ self.gen(elem);
+ }
+ }
+
+ /// Calls `kill` for each element in `elems`.
+ fn kill_all(&mut self, elems: impl IntoIterator<Item = T>) {
+ for elem in elems {
+ self.kill(elem);
+ }
+ }
+}
+
+/// Stores a transfer function for a gen/kill problem.
+///
+/// Calling `gen`/`kill` on a `GenKillSet` will "build up" a transfer function so that it can be
+/// applied multiple times efficiently. When there are multiple calls to `gen` and/or `kill` for
+/// the same element, the most recent one takes precedence.
+#[derive(Clone)]
+pub struct GenKillSet<T: Idx> {
+ gen: HybridBitSet<T>,
+ kill: HybridBitSet<T>,
+}
+
+impl<T: Idx> GenKillSet<T> {
+ /// Creates a new transfer function that will leave the dataflow state unchanged.
+ pub fn identity(universe: usize) -> Self {
+ GenKillSet {
+ gen: HybridBitSet::new_empty(universe),
+ kill: HybridBitSet::new_empty(universe),
+ }
+ }
+
+ /// Applies this transfer function to the given state vector.
+ pub fn apply(&self, state: &mut BitSet<T>) {
+ state.union(&self.gen);
+ state.subtract(&self.kill);
+ }
+}
+
+impl<T: Idx> GenKill<T> for GenKillSet<T> {
+ fn gen(&mut self, elem: T) {
+ self.gen.insert(elem);
+ self.kill.remove(elem);
+ }
+
+ fn kill(&mut self, elem: T) {
+ self.kill.insert(elem);
+ self.gen.remove(elem);
+ }
+}
+
+impl<T: Idx> GenKill<T> for BitSet<T> {
+ fn gen(&mut self, elem: T) {
+ self.insert(elem);
+ }
+
+ fn kill(&mut self, elem: T) {
+ self.remove(elem);
+ }
+}
+
+#[cfg(test)]
+mod tests;
--- /dev/null
+//! A test for the logic that updates the state in a `ResultsCursor` during seek.
+
+use rustc::mir::{self, BasicBlock, Location};
+use rustc::ty;
+use rustc_index::bit_set::BitSet;
+use rustc_index::vec::IndexVec;
+use rustc_span::DUMMY_SP;
+
+use super::*;
+use crate::dataflow::BottomValue;
+
+/// Returns `true` if the given location points to a `Call` terminator that can return
+/// successfully.
+fn is_call_terminator_non_diverging(body: &mir::Body<'_>, loc: Location) -> bool {
+ loc == body.terminator_loc(loc.block)
+ && matches!(
+ body[loc.block].terminator().kind,
+ mir::TerminatorKind::Call { destination: Some(_), .. }
+ )
+}
+
+/// Creates a `mir::Body` with a few disconnected basic blocks.
+///
+/// This is the `Body` that will be used by the `MockAnalysis` below. The shape of its CFG is not
+/// important.
+fn mock_body() -> mir::Body<'static> {
+ let source_info = mir::SourceInfo { scope: mir::OUTERMOST_SOURCE_SCOPE, span: DUMMY_SP };
+
+ let mut blocks = IndexVec::new();
+ let mut block = |n, kind| {
+ let nop = mir::Statement { source_info, kind: mir::StatementKind::Nop };
+
+ blocks.push(mir::BasicBlockData {
+ statements: std::iter::repeat(&nop).cloned().take(n).collect(),
+ terminator: Some(mir::Terminator { source_info, kind }),
+ is_cleanup: false,
+ })
+ };
+
+ let dummy_place = mir::Place { local: mir::RETURN_PLACE, projection: ty::List::empty() };
+
+ block(4, mir::TerminatorKind::Return);
+ block(1, mir::TerminatorKind::Return);
+ block(
+ 2,
+ mir::TerminatorKind::Call {
+ func: mir::Operand::Copy(dummy_place.clone()),
+ args: vec![],
+ destination: Some((dummy_place.clone(), mir::START_BLOCK)),
+ cleanup: None,
+ from_hir_call: false,
+ },
+ );
+ block(3, mir::TerminatorKind::Return);
+ block(0, mir::TerminatorKind::Return);
+ block(
+ 4,
+ mir::TerminatorKind::Call {
+ func: mir::Operand::Copy(dummy_place.clone()),
+ args: vec![],
+ destination: Some((dummy_place.clone(), mir::START_BLOCK)),
+ cleanup: None,
+ from_hir_call: false,
+ },
+ );
+
+ mir::Body::new_cfg_only(blocks)
+}
+
+/// A dataflow analysis whose state is unique at every possible `SeekTarget`.
+///
+/// Uniqueness is achieved by having a *locally* unique effect before and after each statement and
+/// terminator (see `effect_at_target`) while ensuring that the entry set for each block is
+/// *globally* unique (see `mock_entry_set`).
+///
+/// For example, a `BasicBlock` with ID `2` and a `Call` terminator has the following state at each
+/// location ("+x" indicates that "x" is added to the state).
+///
+/// | Location | Before | After |
+/// |------------------------|-------------------|--------|
+/// | (on_entry) | {102} ||
+/// | Statement 0 | +0 | +1 |
+/// | statement 1 | +2 | +3 |
+/// | `Call` terminator | +4 | +5 |
+/// | (on unwind) | {102,0,1,2,3,4,5} ||
+/// | (on successful return) | +6 ||
+///
+/// The `102` in the block's entry set is derived from the basic block index and ensures that the
+/// expected state is unique across all basic blocks. Remember, it is generated by
+/// `mock_entry_sets`, not from actually running `MockAnalysis` to fixpoint.
+struct MockAnalysis<'tcx> {
+ body: &'tcx mir::Body<'tcx>,
+}
+
+impl MockAnalysis<'tcx> {
+ const BASIC_BLOCK_OFFSET: usize = 100;
+
+ /// The entry set for each `BasicBlock` is the ID of that block offset by a fixed amount to
+ /// avoid colliding with the statement/terminator effects.
+ fn mock_entry_set(&self, bb: BasicBlock) -> BitSet<usize> {
+ let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
+ ret.insert(Self::BASIC_BLOCK_OFFSET + bb.index());
+ ret
+ }
+
+ fn mock_entry_sets(&self) -> IndexVec<BasicBlock, BitSet<usize>> {
+ let empty = BitSet::new_empty(self.bits_per_block(self.body));
+ let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks());
+
+ for (bb, _) in self.body.basic_blocks().iter_enumerated() {
+ ret[bb] = self.mock_entry_set(bb);
+ }
+
+ ret
+ }
+
+ /// Returns the index that should be added to the dataflow state at the given target.
+ ///
+ /// This index is only unique within a given basic block. `SeekAfter` and
+ /// `SeekAfterAssumeCallReturns` have the same effect unless `target` is a `Call` terminator.
+ fn effect_at_target(&self, target: SeekTarget) -> Option<usize> {
+ use SeekTarget::*;
+
+ let idx = match target {
+ BlockStart(_) => return None,
+
+ AfterAssumeCallReturns(loc) if is_call_terminator_non_diverging(self.body, loc) => {
+ loc.statement_index * 2 + 2
+ }
+
+ Before(loc) => loc.statement_index * 2,
+ After(loc) | AfterAssumeCallReturns(loc) => loc.statement_index * 2 + 1,
+ };
+
+ assert!(idx < Self::BASIC_BLOCK_OFFSET, "Too many statements in basic block");
+ Some(idx)
+ }
+
+ /// Returns the expected state at the given `SeekTarget`.
+ ///
+ /// This is the union of index of the target basic block, the index assigned to the
+ /// target statement or terminator, and the indices of all preceding statements in the target
+ /// basic block.
+ ///
+ /// For example, the expected state when calling
+ /// `seek_before(Location { block: 2, statement_index: 2 })` would be `[102, 0, 1, 2, 3, 4]`.
+ fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> {
+ let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
+ ret.insert(Self::BASIC_BLOCK_OFFSET + target.block().index());
+
+ if let Some(target_effect) = self.effect_at_target(target) {
+ for i in 0..=target_effect {
+ ret.insert(i);
+ }
+ }
+
+ ret
+ }
+}
+
+impl BottomValue for MockAnalysis<'tcx> {
+ const BOTTOM_VALUE: bool = false;
+}
+
+impl AnalysisDomain<'tcx> for MockAnalysis<'tcx> {
+ type Idx = usize;
+
+ const NAME: &'static str = "mock";
+
+ fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
+ Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len()
+ }
+
+ fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
+ unimplemented!("This is never called since `MockAnalysis` is never iterated to fixpoint");
+ }
+}
+
+impl Analysis<'tcx> for MockAnalysis<'tcx> {
+ fn apply_statement_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ let idx = self.effect_at_target(SeekTarget::After(location)).unwrap();
+ assert!(state.insert(idx));
+ }
+
+ fn apply_before_statement_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ _statement: &mir::Statement<'tcx>,
+ location: Location,
+ ) {
+ let idx = self.effect_at_target(SeekTarget::Before(location)).unwrap();
+ assert!(state.insert(idx));
+ }
+
+ fn apply_terminator_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ ) {
+ let idx = self.effect_at_target(SeekTarget::After(location)).unwrap();
+ assert!(state.insert(idx));
+ }
+
+ fn apply_before_terminator_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ _terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ ) {
+ let idx = self.effect_at_target(SeekTarget::Before(location)).unwrap();
+ assert!(state.insert(idx));
+ }
+
+ fn apply_call_return_effect(
+ &self,
+ state: &mut BitSet<Self::Idx>,
+ block: BasicBlock,
+ _func: &mir::Operand<'tcx>,
+ _args: &[mir::Operand<'tcx>],
+ _return_place: &mir::Place<'tcx>,
+ ) {
+ let location = self.body.terminator_loc(block);
+ let idx = self.effect_at_target(SeekTarget::AfterAssumeCallReturns(location)).unwrap();
+ assert!(state.insert(idx));
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+enum SeekTarget {
+ BlockStart(BasicBlock),
+ Before(Location),
+ After(Location),
+ AfterAssumeCallReturns(Location),
+}
+
+impl SeekTarget {
+ fn block(&self) -> BasicBlock {
+ use SeekTarget::*;
+
+ match *self {
+ BlockStart(block) => block,
+ Before(loc) | After(loc) | AfterAssumeCallReturns(loc) => loc.block,
+ }
+ }
+
+ /// An iterator over all possible `SeekTarget`s in a given block in order, starting with
+ /// `BlockStart`.
+ ///
+ /// This includes both `After` and `AfterAssumeCallReturns` for every `Location`.
+ fn iter_in_block(body: &mir::Body<'_>, block: BasicBlock) -> impl Iterator<Item = Self> {
+ let statements_and_terminator = (0..=body[block].statements.len())
+ .flat_map(|i| (0..3).map(move |j| (i, j)))
+ .map(move |(i, kind)| {
+ let loc = Location { block, statement_index: i };
+ match kind {
+ 0 => SeekTarget::Before(loc),
+ 1 => SeekTarget::After(loc),
+ 2 => SeekTarget::AfterAssumeCallReturns(loc),
+ _ => unreachable!(),
+ }
+ });
+
+ std::iter::once(SeekTarget::BlockStart(block)).chain(statements_and_terminator)
+ }
+}
+
+#[test]
+fn cursor_seek() {
+ let body = mock_body();
+ let body = &body;
+ let analysis = MockAnalysis { body };
+
+ let mut cursor =
+ Results { entry_sets: analysis.mock_entry_sets(), analysis }.into_results_cursor(body);
+
+ // Sanity check: the mock call return effect is unique and actually being applied.
+ let call_terminator_loc = Location { block: BasicBlock::from_usize(2), statement_index: 2 };
+ assert!(is_call_terminator_non_diverging(body, call_terminator_loc));
+
+ let call_return_effect = cursor
+ .analysis()
+ .effect_at_target(SeekTarget::AfterAssumeCallReturns(call_terminator_loc))
+ .unwrap();
+ assert_ne!(
+ call_return_effect,
+ cursor.analysis().effect_at_target(SeekTarget::After(call_terminator_loc)).unwrap()
+ );
+
+ cursor.seek_after(call_terminator_loc);
+ assert!(!cursor.get().contains(call_return_effect));
+ cursor.seek_after_assume_success(call_terminator_loc);
+ assert!(cursor.get().contains(call_return_effect));
+
+ let every_target = || {
+ body.basic_blocks()
+ .iter_enumerated()
+ .flat_map(|(bb, _)| SeekTarget::iter_in_block(body, bb))
+ };
+
+ let mut seek_to_target = |targ| {
+ use SeekTarget::*;
+
+ match targ {
+ BlockStart(block) => cursor.seek_to_block_start(block),
+ Before(loc) => cursor.seek_before(loc),
+ After(loc) => cursor.seek_after(loc),
+ AfterAssumeCallReturns(loc) => cursor.seek_after_assume_success(loc),
+ }
+
+ assert_eq!(cursor.get(), &cursor.analysis().expected_state_at_target(targ));
+ };
+
+ // Seek *to* every possible `SeekTarget` *from* every possible `SeekTarget`.
+ //
+ // By resetting the cursor to `from` each time it changes, we end up checking some edges twice.
+ // What we really want is an Eulerian cycle for the complete digraph over all possible
+ // `SeekTarget`s, but it's not worth spending the time to compute it.
+ for from in every_target() {
+ seek_to_target(from);
+
+ for to in every_target() {
+ seek_to_target(to);
+ seek_to_target(from);
+ }
+ }
+}
--- /dev/null
+use rustc::mir::{self, BasicBlock, Location};
+use rustc_index::bit_set::BitSet;
+
+use super::{Analysis, Results};
+use crate::dataflow::impls::{borrows::Borrows, EverInitializedPlaces, MaybeUninitializedPlaces};
+
+/// Calls the corresponding method in `ResultsVisitor` for every location in a `mir::Body` with the
+/// dataflow state at that location.
+pub fn visit_results<F>(
+ body: &'mir mir::Body<'tcx>,
+ blocks: impl IntoIterator<Item = BasicBlock>,
+ results: &impl ResultsVisitable<'tcx, FlowState = F>,
+ vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>,
+) {
+ let mut state = results.new_flow_state(body);
+
+ for block in blocks {
+ let block_data = &body[block];
+ results.reset_to_block_start(&mut state, block);
+
+ for (statement_index, stmt) in block_data.statements.iter().enumerate() {
+ let loc = Location { block, statement_index };
+
+ results.reconstruct_before_statement_effect(&mut state, stmt, loc);
+ vis.visit_statement(&state, stmt, loc);
+
+ results.reconstruct_statement_effect(&mut state, stmt, loc);
+ vis.visit_statement_exit(&state, stmt, loc);
+ }
+
+ let loc = body.terminator_loc(block);
+ let term = block_data.terminator();
+
+ results.reconstruct_before_terminator_effect(&mut state, term, loc);
+ vis.visit_terminator(&state, term, loc);
+
+ results.reconstruct_terminator_effect(&mut state, term, loc);
+ vis.visit_terminator_exit(&state, term, loc);
+ }
+}
+
+pub trait ResultsVisitor<'mir, 'tcx> {
+ type FlowState;
+
+ /// Called with the `before_statement_effect` of the given statement applied to `state` but not
+ /// its `statement_effect`.
+ fn visit_statement(
+ &mut self,
+ _state: &Self::FlowState,
+ _statement: &'mir mir::Statement<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// Called with both the `before_statement_effect` and the `statement_effect` of the given
+ /// statement applied to `state`.
+ fn visit_statement_exit(
+ &mut self,
+ _state: &Self::FlowState,
+ _statement: &'mir mir::Statement<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// Called with the `before_terminator_effect` of the given terminator applied to `state` but not
+ /// its `terminator_effect`.
+ fn visit_terminator(
+ &mut self,
+ _state: &Self::FlowState,
+ _terminator: &'mir mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ }
+
+ /// Called with both the `before_terminator_effect` and the `terminator_effect` of the given
+ /// terminator applied to `state`.
+ ///
+ /// The `call_return_effect` (if one exists) will *not* be applied to `state`.
+ fn visit_terminator_exit(
+ &mut self,
+ _state: &Self::FlowState,
+ _terminator: &'mir mir::Terminator<'tcx>,
+ _location: Location,
+ ) {
+ }
+}
+
+/// Things that can be visited by a `ResultsVisitor`.
+///
+/// This trait exists so that we can visit the results of multiple dataflow analyses simultaneously.
+/// DO NOT IMPLEMENT MANUALLY. Instead, use the `impl_visitable` macro below.
+pub trait ResultsVisitable<'tcx> {
+ type FlowState;
+
+ /// Creates an empty `FlowState` to hold the transient state for these dataflow results.
+ ///
+ /// The value of the newly created `FlowState` will be overwritten by `reset_to_block_start`
+ /// before it can be observed by a `ResultsVisitor`.
+ fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState;
+
+ fn reset_to_block_start(&self, state: &mut Self::FlowState, block: BasicBlock);
+
+ fn reconstruct_before_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ );
+
+ fn reconstruct_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ statement: &mir::Statement<'tcx>,
+ location: Location,
+ );
+
+ fn reconstruct_before_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ );
+
+ fn reconstruct_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ terminator: &mir::Terminator<'tcx>,
+ location: Location,
+ );
+}
+
+impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A>
+where
+ A: Analysis<'tcx>,
+{
+ type FlowState = BitSet<A::Idx>;
+
+ fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
+ BitSet::new_empty(self.analysis.bits_per_block(body))
+ }
+
+ fn reset_to_block_start(&self, state: &mut Self::FlowState, block: BasicBlock) {
+ state.overwrite(&self.entry_set_for_block(block));
+ }
+
+ fn reconstruct_before_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ stmt: &mir::Statement<'tcx>,
+ loc: Location,
+ ) {
+ self.analysis.apply_before_statement_effect(state, stmt, loc);
+ }
+
+ fn reconstruct_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ stmt: &mir::Statement<'tcx>,
+ loc: Location,
+ ) {
+ self.analysis.apply_statement_effect(state, stmt, loc);
+ }
+
+ fn reconstruct_before_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ term: &mir::Terminator<'tcx>,
+ loc: Location,
+ ) {
+ self.analysis.apply_before_terminator_effect(state, term, loc);
+ }
+
+ fn reconstruct_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ term: &mir::Terminator<'tcx>,
+ loc: Location,
+ ) {
+ self.analysis.apply_terminator_effect(state, term, loc);
+ }
+}
+
+/// A tuple with named fields that can hold either the results or the transient state of the
+/// dataflow analyses used by the borrow checker.
+#[derive(Debug)]
+pub struct BorrowckAnalyses<B, U, E> {
+ pub borrows: B,
+ pub uninits: U,
+ pub ever_inits: E,
+}
+
+/// The results of the dataflow analyses used by the borrow checker.
+pub type BorrowckResults<'mir, 'tcx> = BorrowckAnalyses<
+ Results<'tcx, Borrows<'mir, 'tcx>>,
+ Results<'tcx, MaybeUninitializedPlaces<'mir, 'tcx>>,
+ Results<'tcx, EverInitializedPlaces<'mir, 'tcx>>,
+>;
+
+/// The transient state of the dataflow analyses used by the borrow checker.
+pub type BorrowckFlowState<'mir, 'tcx> =
+ <BorrowckResults<'mir, 'tcx> as ResultsVisitable<'tcx>>::FlowState;
+
+macro_rules! impl_visitable {
+ ( $(
+ $T:ident { $( $field:ident : $A:ident ),* $(,)? }
+ )* ) => { $(
+ impl<'tcx, $($A),*> ResultsVisitable<'tcx> for $T<$( Results<'tcx, $A> ),*>
+ where
+ $( $A: Analysis<'tcx>, )*
+ {
+ type FlowState = $T<$( BitSet<$A::Idx> ),*>;
+
+ fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
+ $T {
+ $( $field: BitSet::new_empty(self.$field.analysis.bits_per_block(body)) ),*
+ }
+ }
+
+ fn reset_to_block_start(
+ &self,
+ state: &mut Self::FlowState,
+ block: BasicBlock,
+ ) {
+ $( state.$field.overwrite(&self.$field.entry_sets[block]); )*
+ }
+
+ fn reconstruct_before_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ stmt: &mir::Statement<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_before_statement_effect(&mut state.$field, stmt, loc); )*
+ }
+
+ fn reconstruct_statement_effect(
+ &self,
+ state: &mut Self::FlowState,
+ stmt: &mir::Statement<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_statement_effect(&mut state.$field, stmt, loc); )*
+ }
+
+ fn reconstruct_before_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ term: &mir::Terminator<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_before_terminator_effect(&mut state.$field, term, loc); )*
+ }
+
+ fn reconstruct_terminator_effect(
+ &self,
+ state: &mut Self::FlowState,
+ term: &mir::Terminator<'tcx>,
+ loc: Location,
+ ) {
+ $( self.$field.analysis
+ .apply_terminator_effect(&mut state.$field, term, loc); )*
+ }
+ }
+ )* }
+}
+
+impl_visitable! {
+ BorrowckAnalyses { borrows: B, uninits: U, ever_inits: E }
+}
+++ /dev/null
-//! Random access inspection of the results of a dataflow analysis.
-
-use std::borrow::Borrow;
-
-use rustc::mir::{self, BasicBlock, Location, TerminatorKind};
-use rustc_index::bit_set::BitSet;
-
-use super::{Analysis, Results};
-
-/// A `ResultsCursor` that borrows the underlying `Results`.
-pub type ResultsRefCursor<'a, 'mir, 'tcx, A> = ResultsCursor<'mir, 'tcx, A, &'a Results<'tcx, A>>;
-
-/// Allows random access inspection of the results of a dataflow analysis.
-///
-/// This cursor only has linear performance within a basic block when its statements are visited in
-/// order. In the worst case—when statements are visited in *reverse* order—performance will be
-/// quadratic in the number of statements in the block. The order in which basic blocks are
-/// inspected has no impact on performance.
-///
-/// A `ResultsCursor` can either own (the default) or borrow the dataflow results it inspects. The
-/// type of ownership is determined by `R` (see `ResultsRefCursor` above).
-pub struct ResultsCursor<'mir, 'tcx, A, R = Results<'tcx, A>>
-where
- A: Analysis<'tcx>,
-{
- body: &'mir mir::Body<'tcx>,
- results: R,
- state: BitSet<A::Idx>,
-
- pos: CursorPosition,
-
- /// When this flag is set, the cursor is pointing at a `Call` or `Yield` terminator whose call
- /// return or resume effect has been applied to `state`.
- ///
- /// This flag helps to ensure that multiple calls to `seek_after_assume_success` with the
- /// same target will result in exactly one invocation of `apply_call_return_effect`. It is
- /// sufficient to clear this only in `seek_to_block_start`, since seeking away from a
- /// terminator will always require a cursor reset.
- success_effect_applied: bool,
-}
-
-impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
-where
- A: Analysis<'tcx>,
- R: Borrow<Results<'tcx, A>>,
-{
- /// Returns a new cursor for `results` that points to the start of the `START_BLOCK`.
- pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
- ResultsCursor {
- body,
- pos: CursorPosition::BlockStart(mir::START_BLOCK),
- state: results.borrow().entry_sets[mir::START_BLOCK].clone(),
- success_effect_applied: false,
- results,
- }
- }
-
- /// Returns the `Analysis` used to generate the underlying results.
- pub fn analysis(&self) -> &A {
- &self.results.borrow().analysis
- }
-
- /// Returns the dataflow state at the current location.
- pub fn get(&self) -> &BitSet<A::Idx> {
- &self.state
- }
-
- /// Returns `true` if the dataflow state at the current location contains the given element.
- ///
- /// Shorthand for `self.get().contains(elem)`
- pub fn contains(&self, elem: A::Idx) -> bool {
- self.state.contains(elem)
- }
-
- /// Resets the cursor to the start of the given basic block.
- pub fn seek_to_block_start(&mut self, block: BasicBlock) {
- self.state.overwrite(&self.results.borrow().entry_sets[block]);
- self.pos = CursorPosition::BlockStart(block);
- self.success_effect_applied = false;
- }
-
- /// Advances the cursor to hold all effects up to and including to the "before" effect of the
- /// statement (or terminator) at the given location.
- ///
- /// If you wish to observe the full effect of a statement or terminator, not just the "before"
- /// effect, use `seek_after` or `seek_after_assume_success`.
- pub fn seek_before(&mut self, target: Location) {
- assert!(target <= self.body.terminator_loc(target.block));
- self.seek_(target, false);
- }
-
- /// Advances the cursor to hold the full effect of all statements (and possibly closing
- /// terminators) up to and including the `target`.
- ///
- /// If the `target` is a `Call` terminator, any call return effect for that terminator will
- /// **not** be observed. Use `seek_after_assume_success` if you wish to observe the call
- /// return effect.
- pub fn seek_after(&mut self, target: Location) {
- assert!(target <= self.body.terminator_loc(target.block));
-
- // If we have already applied the call return effect, we are currently pointing at a `Call`
- // terminator. Unconditionally reset the dataflow cursor, since there is no way to "undo"
- // the call return effect.
- if self.success_effect_applied {
- self.seek_to_block_start(target.block);
- }
-
- self.seek_(target, true);
- }
-
- /// Advances the cursor to hold all effects up to and including of the statement (or
- /// terminator) at the given location.
- ///
- /// If the `target` is a `Call` or `Yield` terminator, any call return or resume effect for that
- /// terminator will be observed. Use `seek_after` if you do **not** wish to observe the
- /// "success" effect.
- pub fn seek_after_assume_success(&mut self, target: Location) {
- let terminator_loc = self.body.terminator_loc(target.block);
- assert!(target.statement_index <= terminator_loc.statement_index);
-
- self.seek_(target, true);
-
- if target != terminator_loc || self.success_effect_applied {
- return;
- }
-
- // Apply the effect of the "success" path of the terminator.
-
- self.success_effect_applied = true;
- let terminator = self.body.basic_blocks()[target.block].terminator();
- match &terminator.kind {
- TerminatorKind::Call { destination: Some((return_place, _)), func, args, .. } => {
- self.results.borrow().analysis.apply_call_return_effect(
- &mut self.state,
- target.block,
- func,
- args,
- return_place,
- );
- }
- TerminatorKind::Yield { resume, resume_arg, .. } => {
- self.results.borrow().analysis.apply_yield_resume_effect(
- &mut self.state,
- *resume,
- resume_arg,
- );
- }
- _ => {}
- }
- }
-
- fn seek_(&mut self, target: Location, apply_after_effect_at_target: bool) {
- use CursorPosition::*;
-
- match self.pos {
- // Return early if we are already at the target location.
- Before(curr) if curr == target && !apply_after_effect_at_target => return,
- After(curr) if curr == target && apply_after_effect_at_target => return,
-
- // Otherwise, we must reset to the start of the target block if...
-
- // we are in a different block entirely.
- BlockStart(block) | Before(Location { block, .. }) | After(Location { block, .. })
- if block != target.block =>
- {
- self.seek_to_block_start(target.block)
- }
-
- // we are in the same block but have advanced past the target statement.
- Before(curr) | After(curr) if curr.statement_index > target.statement_index => {
- self.seek_to_block_start(target.block)
- }
-
- // we have already applied the entire effect of a statement but only wish to observe
- // its "before" effect.
- After(curr)
- if curr.statement_index == target.statement_index
- && !apply_after_effect_at_target =>
- {
- self.seek_to_block_start(target.block)
- }
-
- // N.B., `success_effect_applied` is checked in `seek_after`, not here.
- _ => (),
- }
-
- let analysis = &self.results.borrow().analysis;
- let block_data = &self.body.basic_blocks()[target.block];
-
- // At this point, the cursor is in the same block as the target location at an earlier
- // statement.
- debug_assert_eq!(target.block, self.pos.block());
-
- // Find the first statement whose transfer function has not yet been applied.
- let first_unapplied_statement = match self.pos {
- BlockStart(_) => 0,
- After(Location { statement_index, .. }) => statement_index + 1,
-
- // If we have only applied the "before" effect for the current statement, apply the
- // remainder before continuing.
- Before(curr) => {
- if curr.statement_index == block_data.statements.len() {
- let terminator = block_data.terminator();
- analysis.apply_terminator_effect(&mut self.state, terminator, curr);
- } else {
- let statement = &block_data.statements[curr.statement_index];
- analysis.apply_statement_effect(&mut self.state, statement, curr);
- }
-
- // If all we needed to do was go from `Before` to `After` in the same statement,
- // we are now done.
- if curr.statement_index == target.statement_index {
- debug_assert!(apply_after_effect_at_target);
- self.pos = After(target);
- return;
- }
-
- curr.statement_index + 1
- }
- };
-
- // We have now applied all effects prior to `first_unapplied_statement`.
-
- // Apply the effects of all statements before `target`.
- let mut location = Location { block: target.block, statement_index: 0 };
- for statement_index in first_unapplied_statement..target.statement_index {
- location.statement_index = statement_index;
- let statement = &block_data.statements[statement_index];
- analysis.apply_before_statement_effect(&mut self.state, statement, location);
- analysis.apply_statement_effect(&mut self.state, statement, location);
- }
-
- // Apply the effect of the statement (or terminator) at `target`.
- location.statement_index = target.statement_index;
- if target.statement_index == block_data.statements.len() {
- let terminator = &block_data.terminator();
- analysis.apply_before_terminator_effect(&mut self.state, terminator, location);
-
- if apply_after_effect_at_target {
- analysis.apply_terminator_effect(&mut self.state, terminator, location);
- self.pos = After(target);
- } else {
- self.pos = Before(target);
- }
- } else {
- let statement = &block_data.statements[target.statement_index];
- analysis.apply_before_statement_effect(&mut self.state, statement, location);
-
- if apply_after_effect_at_target {
- analysis.apply_statement_effect(&mut self.state, statement, location);
- self.pos = After(target)
- } else {
- self.pos = Before(target);
- }
- }
- }
-}
-
-#[derive(Clone, Copy, Debug)]
-enum CursorPosition {
- /// No effects within this block have been applied.
- BlockStart(BasicBlock),
-
- /// Only the "before" effect of the statement (or terminator) at this location has been
- /// applied (along with the effects of all previous statements).
- Before(Location),
-
- /// The effects of all statements up to and including the one at this location have been
- /// applied.
- After(Location),
-}
-
-impl CursorPosition {
- fn block(&self) -> BasicBlock {
- match *self {
- Self::BlockStart(block) => block,
- Self::Before(loc) | Self::After(loc) => loc.block,
- }
- }
-}
+++ /dev/null
-//! A solver for dataflow problems.
-
-use std::ffi::OsString;
-use std::fs;
-use std::path::PathBuf;
-
-use rustc::mir::{self, traversal, BasicBlock, Location};
-use rustc::ty::{self, TyCtxt};
-use rustc_ast::ast;
-use rustc_data_structures::work_queue::WorkQueue;
-use rustc_hir::def_id::DefId;
-use rustc_index::bit_set::BitSet;
-use rustc_index::vec::IndexVec;
-use rustc_span::symbol::{sym, Symbol};
-
-use super::graphviz;
-use super::{Analysis, GenKillAnalysis, GenKillSet, Results};
-
-/// A solver for dataflow problems.
-pub struct Engine<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- bits_per_block: usize,
- tcx: TyCtxt<'tcx>,
- body: &'a mir::Body<'tcx>,
- def_id: DefId,
- dead_unwinds: Option<&'a BitSet<BasicBlock>>,
- entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
- analysis: A,
-
- /// Cached, cumulative transfer functions for each block.
- trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
-}
-
-impl<A> Engine<'a, 'tcx, A>
-where
- A: GenKillAnalysis<'tcx>,
-{
- /// Creates a new `Engine` to solve a gen-kill dataflow problem.
- pub fn new_gen_kill(
- tcx: TyCtxt<'tcx>,
- body: &'a mir::Body<'tcx>,
- def_id: DefId,
- analysis: A,
- ) -> Self {
- // If there are no back-edges in the control-flow graph, we only ever need to apply the
- // transfer function for each block exactly once (assuming that we process blocks in RPO).
- //
- // In this case, there's no need to compute the block transfer functions ahead of time.
- if !body.is_cfg_cyclic() {
- return Self::new(tcx, body, def_id, analysis, None);
- }
-
- // Otherwise, compute and store the cumulative transfer function for each block.
-
- let bits_per_block = analysis.bits_per_block(body);
- let mut trans_for_block =
- IndexVec::from_elem(GenKillSet::identity(bits_per_block), body.basic_blocks());
-
- for (block, block_data) in body.basic_blocks().iter_enumerated() {
- let trans = &mut trans_for_block[block];
-
- for (i, statement) in block_data.statements.iter().enumerate() {
- let loc = Location { block, statement_index: i };
- analysis.before_statement_effect(trans, statement, loc);
- analysis.statement_effect(trans, statement, loc);
- }
-
- let terminator = block_data.terminator();
- let loc = Location { block, statement_index: block_data.statements.len() };
- analysis.before_terminator_effect(trans, terminator, loc);
- analysis.terminator_effect(trans, terminator, loc);
- }
-
- Self::new(tcx, body, def_id, analysis, Some(trans_for_block))
- }
-}
-
-impl<A> Engine<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- /// Creates a new `Engine` to solve a dataflow problem with an arbitrary transfer
- /// function.
- ///
- /// Gen-kill problems should use `new_gen_kill`, which will coalesce transfer functions for
- /// better performance.
- pub fn new_generic(
- tcx: TyCtxt<'tcx>,
- body: &'a mir::Body<'tcx>,
- def_id: DefId,
- analysis: A,
- ) -> Self {
- Self::new(tcx, body, def_id, analysis, None)
- }
-
- fn new(
- tcx: TyCtxt<'tcx>,
- body: &'a mir::Body<'tcx>,
- def_id: DefId,
- analysis: A,
- trans_for_block: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
- ) -> Self {
- let bits_per_block = analysis.bits_per_block(body);
-
- let bottom_value_set = if A::BOTTOM_VALUE {
- BitSet::new_filled(bits_per_block)
- } else {
- BitSet::new_empty(bits_per_block)
- };
-
- let mut entry_sets = IndexVec::from_elem(bottom_value_set, body.basic_blocks());
- analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
-
- Engine {
- analysis,
- bits_per_block,
- tcx,
- body,
- def_id,
- dead_unwinds: None,
- entry_sets,
- trans_for_block,
- }
- }
-
- /// Signals that we do not want dataflow state to propagate across unwind edges for these
- /// `BasicBlock`s.
- ///
- /// You must take care that `dead_unwinds` does not contain a `BasicBlock` that *can* actually
- /// unwind during execution. Otherwise, your dataflow results will not be correct.
- pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
- self.dead_unwinds = Some(dead_unwinds);
- self
- }
-
- /// Computes the fixpoint for this dataflow problem and returns it.
- pub fn iterate_to_fixpoint(mut self) -> Results<'tcx, A> {
- let mut temp_state = BitSet::new_empty(self.bits_per_block);
-
- let mut dirty_queue: WorkQueue<BasicBlock> =
- WorkQueue::with_none(self.body.basic_blocks().len());
-
- for (bb, _) in traversal::reverse_postorder(self.body) {
- dirty_queue.insert(bb);
- }
-
- // Add blocks that are not reachable from START_BLOCK to the work queue. These blocks will
- // be processed after the ones added above.
- for bb in self.body.basic_blocks().indices() {
- dirty_queue.insert(bb);
- }
-
- while let Some(bb) = dirty_queue.pop() {
- let bb_data = &self.body[bb];
- let on_entry = &self.entry_sets[bb];
-
- temp_state.overwrite(on_entry);
- self.apply_whole_block_effect(&mut temp_state, bb, bb_data);
-
- self.propagate_bits_into_graph_successors_of(
- &mut temp_state,
- (bb, bb_data),
- &mut dirty_queue,
- );
- }
-
- let Engine { tcx, body, def_id, trans_for_block, entry_sets, analysis, .. } = self;
- let results = Results { analysis, entry_sets };
-
- let res = write_graphviz_results(tcx, def_id, body, &results, trans_for_block);
- if let Err(e) = res {
- warn!("Failed to write graphviz dataflow results: {}", e);
- }
-
- results
- }
-
- /// Applies the cumulative effect of an entire block, excluding the call return effect if one
- /// exists.
- fn apply_whole_block_effect(
- &self,
- state: &mut BitSet<A::Idx>,
- block: BasicBlock,
- block_data: &mir::BasicBlockData<'tcx>,
- ) {
- // Use the cached block transfer function if available.
- if let Some(trans_for_block) = &self.trans_for_block {
- trans_for_block[block].apply(state);
- return;
- }
-
- // Otherwise apply effects one-by-one.
-
- for (statement_index, statement) in block_data.statements.iter().enumerate() {
- let location = Location { block, statement_index };
- self.analysis.apply_before_statement_effect(state, statement, location);
- self.analysis.apply_statement_effect(state, statement, location);
- }
-
- let terminator = block_data.terminator();
- let location = Location { block, statement_index: block_data.statements.len() };
- self.analysis.apply_before_terminator_effect(state, terminator, location);
- self.analysis.apply_terminator_effect(state, terminator, location);
- }
-
- fn propagate_bits_into_graph_successors_of(
- &mut self,
- in_out: &mut BitSet<A::Idx>,
- (bb, bb_data): (BasicBlock, &'a mir::BasicBlockData<'tcx>),
- dirty_list: &mut WorkQueue<BasicBlock>,
- ) {
- use mir::TerminatorKind::*;
-
- match bb_data.terminator().kind {
- Return | Resume | Abort | GeneratorDrop | Unreachable => {}
-
- Goto { target }
- | Assert { target, cleanup: None, .. }
- | Drop { target, location: _, unwind: None }
- | DropAndReplace { target, value: _, location: _, unwind: None } => {
- self.propagate_bits_into_entry_set_for(in_out, target, dirty_list)
- }
-
- Yield { resume: target, drop, resume_arg, .. } => {
- if let Some(drop) = drop {
- self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
- }
-
- self.analysis.apply_yield_resume_effect(in_out, target, &resume_arg);
- self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
- }
-
- Assert { target, cleanup: Some(unwind), .. }
- | Drop { target, location: _, unwind: Some(unwind) }
- | DropAndReplace { target, value: _, location: _, unwind: Some(unwind) } => {
- self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
- if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
- self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
- }
- }
-
- SwitchInt { ref targets, ref values, ref discr, .. } => {
- let Engine { tcx, body, .. } = *self;
- let enum_ = discr
- .place()
- .and_then(|discr| switch_on_enum_discriminant(tcx, body, bb_data, discr));
- match enum_ {
- // If this is a switch on an enum discriminant, a custom effect may be applied
- // along each outgoing edge.
- Some((enum_place, enum_def)) => {
- self.propagate_bits_into_enum_discriminant_switch_successors(
- in_out, bb, enum_def, enum_place, dirty_list, &*values, &*targets,
- );
- }
-
- // Otherwise, it's just a normal `SwitchInt`, and every successor sees the same
- // exit state.
- None => {
- for target in targets.iter().copied() {
- self.propagate_bits_into_entry_set_for(&in_out, target, dirty_list);
- }
- }
- }
- }
-
- Call { cleanup, ref destination, ref func, ref args, .. } => {
- if let Some(unwind) = cleanup {
- if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
- self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
- }
- }
-
- if let Some((ref dest_place, dest_bb)) = *destination {
- // N.B.: This must be done *last*, otherwise the unwind path will see the call
- // return effect.
- self.analysis.apply_call_return_effect(in_out, bb, func, args, dest_place);
- self.propagate_bits_into_entry_set_for(in_out, dest_bb, dirty_list);
- }
- }
-
- FalseEdges { real_target, imaginary_target } => {
- self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
- self.propagate_bits_into_entry_set_for(in_out, imaginary_target, dirty_list);
- }
-
- FalseUnwind { real_target, unwind } => {
- self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
- if let Some(unwind) = unwind {
- if self.dead_unwinds.map_or(true, |bbs| !bbs.contains(bb)) {
- self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
- }
- }
- }
- }
- }
-
- fn propagate_bits_into_entry_set_for(
- &mut self,
- in_out: &BitSet<A::Idx>,
- bb: BasicBlock,
- dirty_queue: &mut WorkQueue<BasicBlock>,
- ) {
- let entry_set = &mut self.entry_sets[bb];
- let set_changed = self.analysis.join(entry_set, &in_out);
- if set_changed {
- dirty_queue.insert(bb);
- }
- }
-
- fn propagate_bits_into_enum_discriminant_switch_successors(
- &mut self,
- in_out: &mut BitSet<A::Idx>,
- bb: BasicBlock,
- enum_def: &'tcx ty::AdtDef,
- enum_place: &mir::Place<'tcx>,
- dirty_list: &mut WorkQueue<BasicBlock>,
- values: &[u128],
- targets: &[BasicBlock],
- ) {
- // MIR building adds discriminants to the `values` array in the same order as they
- // are yielded by `AdtDef::discriminants`. We rely on this to match each
- // discriminant in `values` to its corresponding variant in linear time.
- let mut tmp = BitSet::new_empty(in_out.domain_size());
- let mut discriminants = enum_def.discriminants(self.tcx);
- for (value, target) in values.iter().zip(targets.iter().copied()) {
- let (variant_idx, _) = discriminants.find(|&(_, discr)| discr.val == *value).expect(
- "Order of `AdtDef::discriminants` differed from that of `SwitchInt::values`",
- );
-
- tmp.overwrite(in_out);
- self.analysis.apply_discriminant_switch_effect(
- &mut tmp,
- bb,
- enum_place,
- enum_def,
- variant_idx,
- );
- self.propagate_bits_into_entry_set_for(&tmp, target, dirty_list);
- }
-
- std::mem::drop(tmp);
-
- // Propagate dataflow state along the "otherwise" edge.
- let otherwise = targets.last().copied().unwrap();
- self.propagate_bits_into_entry_set_for(&in_out, otherwise, dirty_list);
- }
-}
-
-/// Inspect a `SwitchInt`-terminated basic block to see if the condition of that `SwitchInt` is
-/// an enum discriminant.
-///
-/// We expect such blocks to have a call to `discriminant` as their last statement like so:
-/// _42 = discriminant(_1)
-/// SwitchInt(_42, ..)
-///
-/// If the basic block matches this pattern, this function returns the place corresponding to the
-/// enum (`_1` in the example above) as well as the `AdtDef` of that enum.
-fn switch_on_enum_discriminant(
- tcx: TyCtxt<'tcx>,
- body: &'mir mir::Body<'tcx>,
- block: &'mir mir::BasicBlockData<'tcx>,
- switch_on: &mir::Place<'tcx>,
-) -> Option<(&'mir mir::Place<'tcx>, &'tcx ty::AdtDef)> {
- match block.statements.last().map(|stmt| &stmt.kind) {
- Some(mir::StatementKind::Assign(box (lhs, mir::Rvalue::Discriminant(discriminated))))
- if lhs == switch_on =>
- {
- match &discriminated.ty(body, tcx).ty.kind {
- ty::Adt(def, _) => Some((discriminated, def)),
-
- // `Rvalue::Discriminant` is also used to get the active yield point for a
- // generator, but we do not need edge-specific effects in that case. This may
- // change in the future.
- ty::Generator(..) => None,
-
- t => bug!("`discriminant` called on unexpected type {:?}", t),
- }
- }
-
- _ => None,
- }
-}
-
-// Graphviz
-
-/// Writes a DOT file containing the results of a dataflow analysis if the user requested it via
-/// `rustc_mir` attributes.
-fn write_graphviz_results<A>(
- tcx: TyCtxt<'tcx>,
- def_id: DefId,
- body: &mir::Body<'tcx>,
- results: &Results<'tcx, A>,
- block_transfer_functions: Option<IndexVec<BasicBlock, GenKillSet<A::Idx>>>,
-) -> std::io::Result<()>
-where
- A: Analysis<'tcx>,
-{
- let attrs = match RustcMirAttrs::parse(tcx, def_id) {
- Ok(attrs) => attrs,
-
- // Invalid `rustc_mir` attrs will be reported using `span_err`.
- Err(()) => return Ok(()),
- };
-
- let path = match attrs.output_path(A::NAME) {
- Some(path) => path,
- None => return Ok(()),
- };
-
- let bits_per_block = results.analysis.bits_per_block(body);
-
- let mut formatter: Box<dyn graphviz::StateFormatter<'tcx, _>> = match attrs.formatter {
- Some(sym::two_phase) => Box::new(graphviz::TwoPhaseDiff::new(bits_per_block)),
- Some(sym::gen_kill) => {
- if let Some(trans_for_block) = block_transfer_functions {
- Box::new(graphviz::BlockTransferFunc::new(body, trans_for_block))
- } else {
- Box::new(graphviz::SimpleDiff::new(bits_per_block))
- }
- }
-
- // Default to the `SimpleDiff` output style.
- _ => Box::new(graphviz::SimpleDiff::new(bits_per_block)),
- };
-
- debug!("printing dataflow results for {:?} to {}", def_id, path.display());
- let mut buf = Vec::new();
-
- let graphviz = graphviz::Formatter::new(body, def_id, results, &mut *formatter);
- dot::render_opts(&graphviz, &mut buf, &[dot::RenderOption::Monospace])?;
- fs::write(&path, buf)?;
- Ok(())
-}
-
-#[derive(Default)]
-struct RustcMirAttrs {
- basename_and_suffix: Option<PathBuf>,
- formatter: Option<Symbol>,
-}
-
-impl RustcMirAttrs {
- fn parse(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result<Self, ()> {
- let attrs = tcx.get_attrs(def_id);
-
- let mut result = Ok(());
- let mut ret = RustcMirAttrs::default();
-
- let rustc_mir_attrs = attrs
- .iter()
- .filter(|attr| attr.check_name(sym::rustc_mir))
- .flat_map(|attr| attr.meta_item_list().into_iter().flat_map(|v| v.into_iter()));
-
- for attr in rustc_mir_attrs {
- let attr_result = if attr.check_name(sym::borrowck_graphviz_postflow) {
- Self::set_field(&mut ret.basename_and_suffix, tcx, &attr, |s| {
- let path = PathBuf::from(s.to_string());
- match path.file_name() {
- Some(_) => Ok(path),
- None => {
- tcx.sess.span_err(attr.span(), "path must end in a filename");
- Err(())
- }
- }
- })
- } else if attr.check_name(sym::borrowck_graphviz_format) {
- Self::set_field(&mut ret.formatter, tcx, &attr, |s| match s {
- sym::gen_kill | sym::two_phase => Ok(s),
- _ => {
- tcx.sess.span_err(attr.span(), "unknown formatter");
- Err(())
- }
- })
- } else {
- Ok(())
- };
-
- result = result.and(attr_result);
- }
-
- result.map(|()| ret)
- }
-
- fn set_field<T>(
- field: &mut Option<T>,
- tcx: TyCtxt<'tcx>,
- attr: &ast::NestedMetaItem,
- mapper: impl FnOnce(Symbol) -> Result<T, ()>,
- ) -> Result<(), ()> {
- if field.is_some() {
- tcx.sess
- .span_err(attr.span(), &format!("duplicate values for `{}`", attr.name_or_empty()));
-
- return Err(());
- }
-
- if let Some(s) = attr.value_str() {
- *field = Some(mapper(s)?);
- Ok(())
- } else {
- tcx.sess
- .span_err(attr.span(), &format!("`{}` requires an argument", attr.name_or_empty()));
- Err(())
- }
- }
-
- /// Returns the path where dataflow results should be written, or `None`
- /// `borrowck_graphviz_postflow` was not specified.
- ///
- /// This performs the following transformation to the argument of `borrowck_graphviz_postflow`:
- ///
- /// "path/suffix.dot" -> "path/analysis_name_suffix.dot"
- fn output_path(&self, analysis_name: &str) -> Option<PathBuf> {
- let mut ret = self.basename_and_suffix.as_ref().cloned()?;
- let suffix = ret.file_name().unwrap(); // Checked when parsing attrs
-
- let mut file_name: OsString = analysis_name.into();
- file_name.push("_");
- file_name.push(suffix);
- ret.set_file_name(file_name);
-
- Some(ret)
- }
-}
+++ /dev/null
-//! A helpful diagram for debugging dataflow problems.
-
-use std::cell::RefCell;
-use std::{io, ops, str};
-
-use rustc::mir::{self, BasicBlock, Body, Location};
-use rustc_hir::def_id::DefId;
-use rustc_index::bit_set::{BitSet, HybridBitSet};
-use rustc_index::vec::{Idx, IndexVec};
-
-use super::{Analysis, GenKillSet, Results, ResultsRefCursor};
-use crate::util::graphviz_safe_def_name;
-
-pub struct Formatter<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- body: &'a Body<'tcx>,
- def_id: DefId,
-
- // This must be behind a `RefCell` because `dot::Labeller` takes `&self`.
- block_formatter: RefCell<BlockFormatter<'a, 'tcx, A>>,
-}
-
-impl<A> Formatter<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- pub fn new(
- body: &'a Body<'tcx>,
- def_id: DefId,
- results: &'a Results<'tcx, A>,
- state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
- ) -> Self {
- let block_formatter = BlockFormatter {
- bg: Background::Light,
- results: ResultsRefCursor::new(body, results),
- state_formatter,
- };
-
- Formatter { body, def_id, block_formatter: RefCell::new(block_formatter) }
- }
-}
-
-/// A pair of a basic block and an index into that basic blocks `successors`.
-#[derive(Copy, Clone, PartialEq, Eq, Debug)]
-pub struct CfgEdge {
- source: BasicBlock,
- index: usize,
-}
-
-fn outgoing_edges(body: &Body<'_>, bb: BasicBlock) -> Vec<CfgEdge> {
- body[bb]
- .terminator()
- .successors()
- .enumerate()
- .map(|(index, _)| CfgEdge { source: bb, index })
- .collect()
-}
-
-impl<A> dot::Labeller<'_> for Formatter<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- type Node = BasicBlock;
- type Edge = CfgEdge;
-
- fn graph_id(&self) -> dot::Id<'_> {
- let name = graphviz_safe_def_name(self.def_id);
- dot::Id::new(format!("graph_for_def_id_{}", name)).unwrap()
- }
-
- fn node_id(&self, n: &Self::Node) -> dot::Id<'_> {
- dot::Id::new(format!("bb_{}", n.index())).unwrap()
- }
-
- fn node_label(&self, block: &Self::Node) -> dot::LabelText<'_> {
- let mut label = Vec::new();
- self.block_formatter.borrow_mut().write_node_label(&mut label, self.body, *block).unwrap();
- dot::LabelText::html(String::from_utf8(label).unwrap())
- }
-
- fn node_shape(&self, _n: &Self::Node) -> Option<dot::LabelText<'_>> {
- Some(dot::LabelText::label("none"))
- }
-
- fn edge_label(&self, e: &Self::Edge) -> dot::LabelText<'_> {
- let label = &self.body[e.source].terminator().kind.fmt_successor_labels()[e.index];
- dot::LabelText::label(label.clone())
- }
-}
-
-impl<A> dot::GraphWalk<'a> for Formatter<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- type Node = BasicBlock;
- type Edge = CfgEdge;
-
- fn nodes(&self) -> dot::Nodes<'_, Self::Node> {
- self.body.basic_blocks().indices().collect::<Vec<_>>().into()
- }
-
- fn edges(&self) -> dot::Edges<'_, Self::Edge> {
- self.body
- .basic_blocks()
- .indices()
- .flat_map(|bb| outgoing_edges(self.body, bb))
- .collect::<Vec<_>>()
- .into()
- }
-
- fn source(&self, edge: &Self::Edge) -> Self::Node {
- edge.source
- }
-
- fn target(&self, edge: &Self::Edge) -> Self::Node {
- self.body[edge.source].terminator().successors().nth(edge.index).copied().unwrap()
- }
-}
-
-struct BlockFormatter<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- results: ResultsRefCursor<'a, 'a, 'tcx, A>,
- bg: Background,
- state_formatter: &'a mut dyn StateFormatter<'tcx, A>,
-}
-
-impl<A> BlockFormatter<'a, 'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- const HEADER_COLOR: &'static str = "#a0a0a0";
-
- fn num_state_columns(&self) -> usize {
- std::cmp::max(1, self.state_formatter.column_names().len())
- }
-
- fn toggle_background(&mut self) -> Background {
- let bg = self.bg;
- self.bg = !bg;
- bg
- }
-
- fn write_node_label(
- &mut self,
- w: &mut impl io::Write,
- body: &'a Body<'tcx>,
- block: BasicBlock,
- ) -> io::Result<()> {
- // Sample output:
- // +-+-----------------------------------------------+
- // A | bb4 |
- // +-+----------------------------------+------------+
- // B | MIR | STATE |
- // +-+----------------------------------+------------+
- // C | | (on entry) | {_0,_2,_3} |
- // +-+----------------------------------+------------+
- // D |0| StorageLive(_7) | |
- // +-+----------------------------------+------------+
- // |1| StorageLive(_8) | |
- // +-+----------------------------------+------------+
- // |2| _8 = &mut _1 | +_8 |
- // +-+----------------------------------+------------+
- // E |T| _4 = const Foo::twiddle(move _2) | -_2 |
- // +-+----------------------------------+------------+
- // F | | (on unwind) | {_0,_3,_8} |
- // +-+----------------------------------+------------+
- // | | (on successful return) | +_4 |
- // +-+----------------------------------+------------+
-
- // N.B., Some attributes (`align`, `balign`) are repeated on parent elements and their
- // children. This is because `xdot` seemed to have a hard time correctly propagating
- // attributes. Make sure to test the output before trying to remove the redundancy.
- // Notably, `align` was found to have no effect when applied only to <table>.
-
- let table_fmt = concat!(
- " border=\"1\"",
- " cellborder=\"1\"",
- " cellspacing=\"0\"",
- " cellpadding=\"3\"",
- " sides=\"rb\"",
- );
- write!(w, r#"<table{fmt}>"#, fmt = table_fmt)?;
-
- // A + B: Block header
- if self.state_formatter.column_names().is_empty() {
- self.write_block_header_simple(w, block)?;
- } else {
- self.write_block_header_with_state_columns(w, block)?;
- }
-
- // C: Entry state
- self.bg = Background::Light;
- self.results.seek_to_block_start(block);
- let block_entry_state = self.results.get().clone();
-
- self.write_row_with_full_state(w, "", "(on entry)")?;
-
- // D: Statement transfer functions
- for (i, statement) in body[block].statements.iter().enumerate() {
- let location = Location { block, statement_index: i };
- let statement_str = format!("{:?}", statement);
- self.write_row_for_location(w, &i.to_string(), &statement_str, location)?;
- }
-
- // E: Terminator transfer function
- let terminator = body[block].terminator();
- let terminator_loc = body.terminator_loc(block);
- let mut terminator_str = String::new();
- terminator.kind.fmt_head(&mut terminator_str).unwrap();
-
- self.write_row_for_location(w, "T", &terminator_str, terminator_loc)?;
-
- // F: Exit state
-
- // Write the full dataflow state immediately after the terminator if it differs from the
- // state at block entry.
- self.results.seek_after(terminator_loc);
- if self.results.get() != &block_entry_state {
- let after_terminator_name = match terminator.kind {
- mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
- _ => "(on exit)",
- };
-
- self.write_row_with_full_state(w, "", after_terminator_name)?;
- }
-
- // Write any changes caused by terminator-specific effects
- match terminator.kind {
- mir::TerminatorKind::Call { destination: Some(_), .. } => {
- let num_state_columns = self.num_state_columns();
- self.write_row(w, "", "(on successful return)", |this, w, fmt| {
- write!(
- w,
- r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
- colspan = num_state_columns,
- fmt = fmt,
- )?;
-
- let state_on_unwind = this.results.get().clone();
- this.results.seek_after_assume_success(terminator_loc);
- write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
-
- write!(w, "</td>")
- })?;
- }
-
- _ => {}
- };
-
- write!(w, "</table>")
- }
-
- fn write_block_header_simple(
- &mut self,
- w: &mut impl io::Write,
- block: BasicBlock,
- ) -> io::Result<()> {
- // +-------------------------------------------------+
- // A | bb4 |
- // +-----------------------------------+-------------+
- // B | MIR | STATE |
- // +-+---------------------------------+-------------+
- // | | ... | |
-
- // A
- write!(
- w,
- concat!("<tr>", r#"<td colspan="3" sides="tl">bb{block_id}</td>"#, "</tr>",),
- block_id = block.index(),
- )?;
-
- // B
- write!(
- w,
- concat!(
- "<tr>",
- r#"<td colspan="2" {fmt}>MIR</td>"#,
- r#"<td {fmt}>STATE</td>"#,
- "</tr>",
- ),
- fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR),
- )
- }
-
- fn write_block_header_with_state_columns(
- &mut self,
- w: &mut impl io::Write,
- block: BasicBlock,
- ) -> io::Result<()> {
- // +------------------------------------+-------------+
- // A | bb4 | STATE |
- // +------------------------------------+------+------+
- // B | MIR | GEN | KILL |
- // +-+----------------------------------+------+------+
- // | | ... | | |
-
- let state_column_names = self.state_formatter.column_names();
-
- // A
- write!(
- w,
- concat!(
- "<tr>",
- r#"<td {fmt} colspan="2">bb{block_id}</td>"#,
- r#"<td {fmt} colspan="{num_state_cols}">STATE</td>"#,
- "</tr>",
- ),
- fmt = "sides=\"tl\"",
- num_state_cols = state_column_names.len(),
- block_id = block.index(),
- )?;
-
- // B
- let fmt = format!("bgcolor=\"{}\" sides=\"tl\"", Self::HEADER_COLOR);
- write!(w, concat!("<tr>", r#"<td colspan="2" {fmt}>MIR</td>"#,), fmt = fmt,)?;
-
- for name in state_column_names {
- write!(w, "<td {fmt}>{name}</td>", fmt = fmt, name = name)?;
- }
-
- write!(w, "</tr>")
- }
-
- /// Write a row with the given index and MIR, using the function argument to fill in the
- /// "STATE" column(s).
- fn write_row<W: io::Write>(
- &mut self,
- w: &mut W,
- i: &str,
- mir: &str,
- f: impl FnOnce(&mut Self, &mut W, &str) -> io::Result<()>,
- ) -> io::Result<()> {
- let bg = self.toggle_background();
- let valign = if mir.starts_with("(on ") && mir != "(on entry)" { "bottom" } else { "top" };
-
- let fmt = format!("valign=\"{}\" sides=\"tl\" {}", valign, bg.attr());
-
- write!(
- w,
- concat!(
- "<tr>",
- r#"<td {fmt} align="right">{i}</td>"#,
- r#"<td {fmt} align="left">{mir}</td>"#,
- ),
- i = i,
- fmt = fmt,
- mir = dot::escape_html(mir),
- )?;
-
- f(self, w, &fmt)?;
- write!(w, "</tr>")
- }
-
- fn write_row_with_full_state(
- &mut self,
- w: &mut impl io::Write,
- i: &str,
- mir: &str,
- ) -> io::Result<()> {
- self.write_row(w, i, mir, |this, w, fmt| {
- let state = this.results.get();
- let analysis = this.results.analysis();
-
- write!(
- w,
- r#"<td colspan="{colspan}" {fmt} align="left">{{"#,
- colspan = this.num_state_columns(),
- fmt = fmt,
- )?;
- pretty_print_state_elems(w, analysis, state.iter(), ", ", LIMIT_30_ALIGN_1)?;
- write!(w, "}}</td>")
- })
- }
-
- fn write_row_for_location(
- &mut self,
- w: &mut impl io::Write,
- i: &str,
- mir: &str,
- location: Location,
- ) -> io::Result<()> {
- self.write_row(w, i, mir, |this, w, fmt| {
- this.state_formatter.write_state_for_location(w, fmt, &mut this.results, location)
- })
- }
-}
-
-/// Controls what gets printed under the `STATE` header.
-pub trait StateFormatter<'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- /// The columns that will get printed under `STATE`.
- fn column_names(&self) -> &[&str];
-
- fn write_state_for_location(
- &mut self,
- w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()>;
-}
-
-/// Prints a single column containing the state vector immediately *after* each statement.
-pub struct SimpleDiff<T: Idx> {
- prev_state: BitSet<T>,
- prev_loc: Location,
-}
-
-impl<T: Idx> SimpleDiff<T> {
- pub fn new(bits_per_block: usize) -> Self {
- SimpleDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
- }
-}
-
-impl<A> StateFormatter<'tcx, A> for SimpleDiff<A::Idx>
-where
- A: Analysis<'tcx>,
-{
- fn column_names(&self) -> &[&str] {
- &[]
- }
-
- fn write_state_for_location(
- &mut self,
- mut w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()> {
- if location.statement_index == 0 {
- results.seek_to_block_start(location.block);
- self.prev_state.overwrite(results.get());
- } else {
- // Ensure that we are visiting statements in order, so `prev_state` is correct.
- assert_eq!(self.prev_loc.successor_within_block(), location);
- }
-
- self.prev_loc = location;
- write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
- results.seek_after(location);
- let curr_state = results.get();
- write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
- self.prev_state.overwrite(curr_state);
- write!(w, "</td>")
- }
-}
-
-/// Prints two state columns, one containing only the "before" effect of each statement and one
-/// containing the full effect.
-pub struct TwoPhaseDiff<T: Idx> {
- prev_state: BitSet<T>,
- prev_loc: Location,
-}
-
-impl<T: Idx> TwoPhaseDiff<T> {
- pub fn new(bits_per_block: usize) -> Self {
- TwoPhaseDiff { prev_state: BitSet::new_empty(bits_per_block), prev_loc: Location::START }
- }
-}
-
-impl<A> StateFormatter<'tcx, A> for TwoPhaseDiff<A::Idx>
-where
- A: Analysis<'tcx>,
-{
- fn column_names(&self) -> &[&str] {
- &["BEFORE", " AFTER"]
- }
-
- fn write_state_for_location(
- &mut self,
- mut w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()> {
- if location.statement_index == 0 {
- results.seek_to_block_start(location.block);
- self.prev_state.overwrite(results.get());
- } else {
- // Ensure that we are visiting statements in order, so `prev_state` is correct.
- assert_eq!(self.prev_loc.successor_within_block(), location);
- }
-
- self.prev_loc = location;
-
- // Before
-
- write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
- results.seek_before(location);
- let curr_state = results.get();
- write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
- self.prev_state.overwrite(curr_state);
- write!(w, "</td>")?;
-
- // After
-
- write!(w, r#"<td {fmt} align="left">"#, fmt = fmt)?;
- results.seek_after(location);
- let curr_state = results.get();
- write_diff(&mut w, results.analysis(), &self.prev_state, curr_state)?;
- self.prev_state.overwrite(curr_state);
- write!(w, "</td>")
- }
-}
-
-/// Prints the gen/kill set for the entire block.
-pub struct BlockTransferFunc<'a, 'tcx, T: Idx> {
- body: &'a mir::Body<'tcx>,
- trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
-}
-
-impl<T: Idx> BlockTransferFunc<'mir, 'tcx, T> {
- pub fn new(
- body: &'mir mir::Body<'tcx>,
- trans_for_block: IndexVec<BasicBlock, GenKillSet<T>>,
- ) -> Self {
- BlockTransferFunc { body, trans_for_block }
- }
-}
-
-impl<A> StateFormatter<'tcx, A> for BlockTransferFunc<'mir, 'tcx, A::Idx>
-where
- A: Analysis<'tcx>,
-{
- fn column_names(&self) -> &[&str] {
- &["GEN", "KILL"]
- }
-
- fn write_state_for_location(
- &mut self,
- mut w: &mut dyn io::Write,
- fmt: &str,
- results: &mut ResultsRefCursor<'_, '_, 'tcx, A>,
- location: Location,
- ) -> io::Result<()> {
- // Only print a single row.
- if location.statement_index != 0 {
- return Ok(());
- }
-
- let block_trans = &self.trans_for_block[location.block];
- let rowspan = self.body.basic_blocks()[location.block].statements.len();
-
- for set in &[&block_trans.gen, &block_trans.kill] {
- write!(
- w,
- r#"<td {fmt} rowspan="{rowspan}" balign="left" align="left">"#,
- fmt = fmt,
- rowspan = rowspan
- )?;
-
- pretty_print_state_elems(&mut w, results.analysis(), set.iter(), BR_LEFT, None)?;
- write!(w, "</td>")?;
- }
-
- Ok(())
- }
-}
-
-/// Writes two lines, one containing the added bits and one the removed bits.
-fn write_diff<A: Analysis<'tcx>>(
- w: &mut impl io::Write,
- analysis: &A,
- from: &BitSet<A::Idx>,
- to: &BitSet<A::Idx>,
-) -> io::Result<()> {
- assert_eq!(from.domain_size(), to.domain_size());
- let len = from.domain_size();
-
- let mut set = HybridBitSet::new_empty(len);
- let mut clear = HybridBitSet::new_empty(len);
-
- // FIXME: Implement a lazy iterator over the symmetric difference of two bitsets.
- for i in (0..len).map(A::Idx::new) {
- match (from.contains(i), to.contains(i)) {
- (false, true) => set.insert(i),
- (true, false) => clear.insert(i),
- _ => continue,
- };
- }
-
- if !set.is_empty() {
- write!(w, r#"<font color="darkgreen">+"#)?;
- pretty_print_state_elems(w, analysis, set.iter(), ", ", LIMIT_30_ALIGN_1)?;
- write!(w, r#"</font>"#)?;
- }
-
- if !set.is_empty() && !clear.is_empty() {
- write!(w, "{}", BR_LEFT)?;
- }
-
- if !clear.is_empty() {
- write!(w, r#"<font color="red">-"#)?;
- pretty_print_state_elems(w, analysis, clear.iter(), ", ", LIMIT_30_ALIGN_1)?;
- write!(w, r#"</font>"#)?;
- }
-
- Ok(())
-}
-
-const BR_LEFT: &str = r#"<br align="left"/>"#;
-const BR_LEFT_SPACE: &str = r#"<br align="left"/> "#;
-
-/// Line break policy that breaks at 40 characters and starts the next line with a single space.
-const LIMIT_30_ALIGN_1: Option<LineBreak> = Some(LineBreak { sequence: BR_LEFT_SPACE, limit: 30 });
-
-struct LineBreak {
- sequence: &'static str,
- limit: usize,
-}
-
-/// Formats each `elem` using the pretty printer provided by `analysis` into a list with the given
-/// separator (`sep`).
-///
-/// Optionally, it will break lines using the given character sequence (usually `<br/>`) and
-/// character limit.
-fn pretty_print_state_elems<A>(
- w: &mut impl io::Write,
- analysis: &A,
- elems: impl Iterator<Item = A::Idx>,
- sep: &str,
- line_break: Option<LineBreak>,
-) -> io::Result<bool>
-where
- A: Analysis<'tcx>,
-{
- let sep_width = sep.chars().count();
-
- let mut buf = Vec::new();
-
- let mut first = true;
- let mut curr_line_width = 0;
- let mut line_break_inserted = false;
-
- for idx in elems {
- buf.clear();
- analysis.pretty_print_idx(&mut buf, idx)?;
- let idx_str =
- str::from_utf8(&buf).expect("Output of `pretty_print_idx` must be valid UTF-8");
- let escaped = dot::escape_html(idx_str);
- let escaped_width = escaped.chars().count();
-
- if first {
- first = false;
- } else {
- write!(w, "{}", sep)?;
- curr_line_width += sep_width;
-
- if let Some(line_break) = &line_break {
- if curr_line_width + sep_width + escaped_width > line_break.limit {
- write!(w, "{}", line_break.sequence)?;
- line_break_inserted = true;
- curr_line_width = 0;
- }
- }
- }
-
- write!(w, "{}", escaped)?;
- curr_line_width += escaped_width;
- }
-
- Ok(line_break_inserted)
-}
-
-/// The background color used for zebra-striping the table.
-#[derive(Clone, Copy)]
-enum Background {
- Light,
- Dark,
-}
-
-impl Background {
- fn attr(self) -> &'static str {
- match self {
- Self::Dark => "bgcolor=\"#f0f0f0\"",
- Self::Light => "",
- }
- }
-}
-
-impl ops::Not for Background {
- type Output = Self;
-
- fn not(self) -> Self {
- match self {
- Self::Light => Self::Dark,
- Self::Dark => Self::Light,
- }
- }
-}
+++ /dev/null
-//! A framework that can express both [gen-kill] and generic dataflow problems.
-//!
-//! There is another interface for dataflow in the compiler in `librustc_mir/dataflow/mod.rs`. The
-//! interface in this module will eventually [replace that one][design-meeting].
-//!
-//! To actually use this framework, you must implement either the `Analysis` or the `GenKillAnalysis`
-//! trait. If your transfer function can be expressed with only gen/kill operations, prefer
-//! `GenKillAnalysis` since it will run faster while iterating to fixpoint. Create an `Engine` using
-//! the appropriate constructor and call `iterate_to_fixpoint`. You can use a `ResultsCursor` to
-//! inspect the fixpoint solution to your dataflow problem.
-//!
-//! ```ignore(cross-crate-imports)
-//! fn do_my_analysis(tcx: TyCtxt<'tcx>, body: &mir::Body<'tcx>, did: DefId) {
-//! let analysis = MyAnalysis::new();
-//!
-//! // If `MyAnalysis` implements `GenKillAnalysis`.
-//! let results = Engine::new_gen_kill(tcx, body, did, analysis).iterate_to_fixpoint();
-//!
-//! // If `MyAnalysis` implements `Analysis`.
-//! // let results = Engine::new_generic(tcx, body, did, analysis).iterate_to_fixpoint();
-//!
-//! let mut cursor = ResultsCursor::new(body, results);
-//!
-//! for (_, statement_index) in body.block_data[START_BLOCK].statements.iter_enumerated() {
-//! cursor.seek_after(Location { block: START_BLOCK, statement_index });
-//! let state = cursor.get();
-//! println!("{:?}", state);
-//! }
-//! }
-//! ```
-//!
-//! [gen-kill]: https://en.wikipedia.org/wiki/Data-flow_analysis#Bit_vector_problems
-//! [design-meeting]https://github.com/rust-lang/compiler-team/issues/202
-
-use std::io;
-
-use rustc::mir::{self, BasicBlock, Location};
-use rustc::ty::layout::VariantIdx;
-use rustc::ty::{self, TyCtxt};
-use rustc_hir::def_id::DefId;
-use rustc_index::bit_set::{BitSet, HybridBitSet};
-use rustc_index::vec::{Idx, IndexVec};
-
-use crate::dataflow::BottomValue;
-
-mod cursor;
-mod engine;
-mod graphviz;
-mod visitor;
-
-pub use self::cursor::{ResultsCursor, ResultsRefCursor};
-pub use self::engine::Engine;
-pub use self::visitor::{visit_results, ResultsVisitor};
-pub use self::visitor::{BorrowckFlowState, BorrowckResults};
-
-/// A dataflow analysis that has converged to fixpoint.
-pub struct Results<'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- pub analysis: A,
- entry_sets: IndexVec<BasicBlock, BitSet<A::Idx>>,
-}
-
-impl<A> Results<'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- /// Creates a `ResultsCursor` that can inspect these `Results`.
- pub fn into_results_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
- ResultsCursor::new(body, self)
- }
-
- /// Gets the entry set for the given block.
- pub fn entry_set_for_block(&self, block: BasicBlock) -> &BitSet<A::Idx> {
- &self.entry_sets[block]
- }
-
- pub fn visit_with(
- &self,
- body: &'mir mir::Body<'tcx>,
- blocks: impl IntoIterator<Item = BasicBlock>,
- vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
- ) {
- visit_results(body, blocks, self, vis)
- }
-
- pub fn visit_in_rpo_with(
- &self,
- body: &'mir mir::Body<'tcx>,
- vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
- ) {
- let blocks = mir::traversal::reverse_postorder(body);
- visit_results(body, blocks.map(|(bb, _)| bb), self, vis)
- }
-}
-
-/// Define the domain of a dataflow problem.
-///
-/// This trait specifies the lattice on which this analysis operates. For now, this must be a
-/// powerset of values of type `Idx`. The elements of this lattice are represented with a `BitSet`
-/// and referred to as the state vector.
-///
-/// This trait also defines the initial value for the dataflow state upon entry to the
-/// `START_BLOCK`, as well as some names used to refer to this analysis when debugging.
-pub trait AnalysisDomain<'tcx>: BottomValue {
- /// The type of the elements in the state vector.
- type Idx: Idx;
-
- /// A descriptive name for this analysis. Used only for debugging.
- ///
- /// This name should be brief and contain no spaces, periods or other characters that are not
- /// suitable as part of a filename.
- const NAME: &'static str;
-
- /// The size of the state vector.
- fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize;
-
- /// Mutates the entry set of the `START_BLOCK` to contain the initial state for dataflow
- /// analysis.
- fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>);
-
- /// Prints an element in the state vector for debugging.
- fn pretty_print_idx(&self, w: &mut impl io::Write, idx: Self::Idx) -> io::Result<()> {
- write!(w, "{:?}", idx)
- }
-}
-
-/// A dataflow problem with an arbitrarily complex transfer function.
-pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
- /// Updates the current dataflow state with the effect of evaluating a statement.
- fn apply_statement_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- statement: &mir::Statement<'tcx>,
- location: Location,
- );
-
- /// Updates the current dataflow state with an effect that occurs immediately *before* the
- /// given statement.
- ///
- /// This method is useful if the consumer of the results of this analysis needs only to observe
- /// *part* of the effect of a statement (e.g. for two-phase borrows). As a general rule,
- /// analyses should not implement this without implementing `apply_statement_effect`.
- fn apply_before_statement_effect(
- &self,
- _state: &mut BitSet<Self::Idx>,
- _statement: &mir::Statement<'tcx>,
- _location: Location,
- ) {
- }
-
- /// Updates the current dataflow state with the effect of evaluating a terminator.
- ///
- /// The effect of a successful return from a `Call` terminator should **not** be accounted for
- /// in this function. That should go in `apply_call_return_effect`. For example, in the
- /// `InitializedPlaces` analyses, the return place for a function call is not marked as
- /// initialized here.
- fn apply_terminator_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- );
-
- /// Updates the current dataflow state with an effect that occurs immediately *before* the
- /// given terminator.
- ///
- /// This method is useful if the consumer of the results of this analysis needs only to observe
- /// *part* of the effect of a terminator (e.g. for two-phase borrows). As a general rule,
- /// analyses should not implement this without implementing `apply_terminator_effect`.
- fn apply_before_terminator_effect(
- &self,
- _state: &mut BitSet<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- _location: Location,
- ) {
- }
-
- /// Updates the current dataflow state with the effect of a successful return from a `Call`
- /// terminator.
- ///
- /// This is separate from `apply_terminator_effect` to properly track state across unwind
- /// edges.
- fn apply_call_return_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- block: BasicBlock,
- func: &mir::Operand<'tcx>,
- args: &[mir::Operand<'tcx>],
- return_place: &mir::Place<'tcx>,
- );
-
- /// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
- ///
- /// This is similar to `apply_call_return_effect` in that it only takes place after the
- /// generator is resumed, not when it is dropped.
- ///
- /// By default, no effects happen.
- fn apply_yield_resume_effect(
- &self,
- _state: &mut BitSet<Self::Idx>,
- _resume_block: BasicBlock,
- _resume_place: &mir::Place<'tcx>,
- ) {
- }
-
- /// Updates the current dataflow state with the effect of taking a particular branch in a
- /// `SwitchInt` terminator.
- ///
- /// Much like `apply_call_return_effect`, this effect is only propagated along a single
- /// outgoing edge from this basic block.
- fn apply_discriminant_switch_effect(
- &self,
- _state: &mut BitSet<Self::Idx>,
- _block: BasicBlock,
- _enum_place: &mir::Place<'tcx>,
- _adt: &ty::AdtDef,
- _variant: VariantIdx,
- ) {
- }
-
- /// Creates an `Engine` to find the fixpoint for this dataflow problem.
- ///
- /// You shouldn't need to override this outside this module, since the combination of the
- /// default impl and the one for all `A: GenKillAnalysis` will do the right thing.
- /// Its purpose is to enable method chaining like so:
- ///
- /// ```ignore(cross-crate-imports)
- /// let results = MyAnalysis::new(tcx, body)
- /// .into_engine(tcx, body, def_id)
- /// .iterate_to_fixpoint()
- /// .into_results_cursor(body);
- /// ```
- fn into_engine(
- self,
- tcx: TyCtxt<'tcx>,
- body: &'mir mir::Body<'tcx>,
- def_id: DefId,
- ) -> Engine<'mir, 'tcx, Self>
- where
- Self: Sized,
- {
- Engine::new_generic(tcx, body, def_id, self)
- }
-}
-
-/// A gen/kill dataflow problem.
-///
-/// Each method in this trait has a corresponding one in `Analysis`. However, these methods only
-/// allow modification of the dataflow state via "gen" and "kill" operations. By defining transfer
-/// functions for each statement in this way, the transfer function for an entire basic block can
-/// be computed efficiently.
-///
-/// `Analysis` is automatically implemented for all implementers of `GenKillAnalysis`.
-pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
- /// See `Analysis::apply_statement_effect`.
- fn statement_effect(
- &self,
- trans: &mut impl GenKill<Self::Idx>,
- statement: &mir::Statement<'tcx>,
- location: Location,
- );
-
- /// See `Analysis::apply_before_statement_effect`.
- fn before_statement_effect(
- &self,
- _trans: &mut impl GenKill<Self::Idx>,
- _statement: &mir::Statement<'tcx>,
- _location: Location,
- ) {
- }
-
- /// See `Analysis::apply_terminator_effect`.
- fn terminator_effect(
- &self,
- trans: &mut impl GenKill<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- );
-
- /// See `Analysis::apply_before_terminator_effect`.
- fn before_terminator_effect(
- &self,
- _trans: &mut impl GenKill<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- _location: Location,
- ) {
- }
-
- /// See `Analysis::apply_call_return_effect`.
- fn call_return_effect(
- &self,
- trans: &mut impl GenKill<Self::Idx>,
- block: BasicBlock,
- func: &mir::Operand<'tcx>,
- args: &[mir::Operand<'tcx>],
- return_place: &mir::Place<'tcx>,
- );
-
- /// See `Analysis::apply_yield_resume_effect`.
- fn yield_resume_effect(
- &self,
- _trans: &mut BitSet<Self::Idx>,
- _resume_block: BasicBlock,
- _resume_place: &mir::Place<'tcx>,
- ) {
- }
-
- /// See `Analysis::apply_discriminant_switch_effect`.
- fn discriminant_switch_effect(
- &self,
- _state: &mut impl GenKill<Self::Idx>,
- _block: BasicBlock,
- _enum_place: &mir::Place<'tcx>,
- _adt: &ty::AdtDef,
- _variant: VariantIdx,
- ) {
- }
-}
-
-impl<A> Analysis<'tcx> for A
-where
- A: GenKillAnalysis<'tcx>,
-{
- fn apply_statement_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- self.statement_effect(state, statement, location);
- }
-
- fn apply_before_statement_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- self.before_statement_effect(state, statement, location);
- }
-
- fn apply_terminator_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- self.terminator_effect(state, terminator, location);
- }
-
- fn apply_before_terminator_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- self.before_terminator_effect(state, terminator, location);
- }
-
- fn apply_call_return_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- block: BasicBlock,
- func: &mir::Operand<'tcx>,
- args: &[mir::Operand<'tcx>],
- return_place: &mir::Place<'tcx>,
- ) {
- self.call_return_effect(state, block, func, args, return_place);
- }
-
- fn apply_yield_resume_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- resume_block: BasicBlock,
- resume_place: &mir::Place<'tcx>,
- ) {
- self.yield_resume_effect(state, resume_block, resume_place);
- }
-
- fn apply_discriminant_switch_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- block: BasicBlock,
- enum_place: &mir::Place<'tcx>,
- adt: &ty::AdtDef,
- variant: VariantIdx,
- ) {
- self.discriminant_switch_effect(state, block, enum_place, adt, variant);
- }
-
- fn into_engine(
- self,
- tcx: TyCtxt<'tcx>,
- body: &'mir mir::Body<'tcx>,
- def_id: DefId,
- ) -> Engine<'mir, 'tcx, Self>
- where
- Self: Sized,
- {
- Engine::new_gen_kill(tcx, body, def_id, self)
- }
-}
-
-/// The legal operations for a transfer function in a gen/kill problem.
-///
-/// This abstraction exists because there are two different contexts in which we call the methods in
-/// `GenKillAnalysis`. Sometimes we need to store a single transfer function that can be efficiently
-/// applied multiple times, such as when computing the cumulative transfer function for each block.
-/// These cases require a `GenKillSet`, which in turn requires two `BitSet`s of storage. Oftentimes,
-/// however, we only need to apply an effect once. In *these* cases, it is more efficient to pass the
-/// `BitSet` representing the state vector directly into the `*_effect` methods as opposed to
-/// building up a `GenKillSet` and then throwing it away.
-pub trait GenKill<T> {
- /// Inserts `elem` into the state vector.
- fn gen(&mut self, elem: T);
-
- /// Removes `elem` from the state vector.
- fn kill(&mut self, elem: T);
-
- /// Calls `gen` for each element in `elems`.
- fn gen_all(&mut self, elems: impl IntoIterator<Item = T>) {
- for elem in elems {
- self.gen(elem);
- }
- }
-
- /// Calls `kill` for each element in `elems`.
- fn kill_all(&mut self, elems: impl IntoIterator<Item = T>) {
- for elem in elems {
- self.kill(elem);
- }
- }
-}
-
-/// Stores a transfer function for a gen/kill problem.
-///
-/// Calling `gen`/`kill` on a `GenKillSet` will "build up" a transfer function so that it can be
-/// applied multiple times efficiently. When there are multiple calls to `gen` and/or `kill` for
-/// the same element, the most recent one takes precedence.
-#[derive(Clone)]
-pub struct GenKillSet<T: Idx> {
- gen: HybridBitSet<T>,
- kill: HybridBitSet<T>,
-}
-
-impl<T: Idx> GenKillSet<T> {
- /// Creates a new transfer function that will leave the dataflow state unchanged.
- pub fn identity(universe: usize) -> Self {
- GenKillSet {
- gen: HybridBitSet::new_empty(universe),
- kill: HybridBitSet::new_empty(universe),
- }
- }
-
- /// Applies this transfer function to the given state vector.
- pub fn apply(&self, state: &mut BitSet<T>) {
- state.union(&self.gen);
- state.subtract(&self.kill);
- }
-}
-
-impl<T: Idx> GenKill<T> for GenKillSet<T> {
- fn gen(&mut self, elem: T) {
- self.gen.insert(elem);
- self.kill.remove(elem);
- }
-
- fn kill(&mut self, elem: T) {
- self.kill.insert(elem);
- self.gen.remove(elem);
- }
-}
-
-impl<T: Idx> GenKill<T> for BitSet<T> {
- fn gen(&mut self, elem: T) {
- self.insert(elem);
- }
-
- fn kill(&mut self, elem: T) {
- self.remove(elem);
- }
-}
-
-#[cfg(test)]
-mod tests;
+++ /dev/null
-//! A test for the logic that updates the state in a `ResultsCursor` during seek.
-
-use rustc::mir::{self, BasicBlock, Location};
-use rustc::ty;
-use rustc_index::bit_set::BitSet;
-use rustc_index::vec::IndexVec;
-use rustc_span::DUMMY_SP;
-
-use super::*;
-use crate::dataflow::BottomValue;
-
-/// Returns `true` if the given location points to a `Call` terminator that can return
-/// successfully.
-fn is_call_terminator_non_diverging(body: &mir::Body<'_>, loc: Location) -> bool {
- loc == body.terminator_loc(loc.block)
- && matches!(
- body[loc.block].terminator().kind,
- mir::TerminatorKind::Call { destination: Some(_), .. }
- )
-}
-
-/// Creates a `mir::Body` with a few disconnected basic blocks.
-///
-/// This is the `Body` that will be used by the `MockAnalysis` below. The shape of its CFG is not
-/// important.
-fn mock_body() -> mir::Body<'static> {
- let source_info = mir::SourceInfo { scope: mir::OUTERMOST_SOURCE_SCOPE, span: DUMMY_SP };
-
- let mut blocks = IndexVec::new();
- let mut block = |n, kind| {
- let nop = mir::Statement { source_info, kind: mir::StatementKind::Nop };
-
- blocks.push(mir::BasicBlockData {
- statements: std::iter::repeat(&nop).cloned().take(n).collect(),
- terminator: Some(mir::Terminator { source_info, kind }),
- is_cleanup: false,
- })
- };
-
- let dummy_place = mir::Place { local: mir::RETURN_PLACE, projection: ty::List::empty() };
-
- block(4, mir::TerminatorKind::Return);
- block(1, mir::TerminatorKind::Return);
- block(
- 2,
- mir::TerminatorKind::Call {
- func: mir::Operand::Copy(dummy_place.clone()),
- args: vec![],
- destination: Some((dummy_place.clone(), mir::START_BLOCK)),
- cleanup: None,
- from_hir_call: false,
- },
- );
- block(3, mir::TerminatorKind::Return);
- block(0, mir::TerminatorKind::Return);
- block(
- 4,
- mir::TerminatorKind::Call {
- func: mir::Operand::Copy(dummy_place.clone()),
- args: vec![],
- destination: Some((dummy_place.clone(), mir::START_BLOCK)),
- cleanup: None,
- from_hir_call: false,
- },
- );
-
- mir::Body::new_cfg_only(blocks)
-}
-
-/// A dataflow analysis whose state is unique at every possible `SeekTarget`.
-///
-/// Uniqueness is achieved by having a *locally* unique effect before and after each statement and
-/// terminator (see `effect_at_target`) while ensuring that the entry set for each block is
-/// *globally* unique (see `mock_entry_set`).
-///
-/// For example, a `BasicBlock` with ID `2` and a `Call` terminator has the following state at each
-/// location ("+x" indicates that "x" is added to the state).
-///
-/// | Location | Before | After |
-/// |------------------------|-------------------|--------|
-/// | (on_entry) | {102} ||
-/// | Statement 0 | +0 | +1 |
-/// | statement 1 | +2 | +3 |
-/// | `Call` terminator | +4 | +5 |
-/// | (on unwind) | {102,0,1,2,3,4,5} ||
-/// | (on successful return) | +6 ||
-///
-/// The `102` in the block's entry set is derived from the basic block index and ensures that the
-/// expected state is unique across all basic blocks. Remember, it is generated by
-/// `mock_entry_sets`, not from actually running `MockAnalysis` to fixpoint.
-struct MockAnalysis<'tcx> {
- body: &'tcx mir::Body<'tcx>,
-}
-
-impl MockAnalysis<'tcx> {
- const BASIC_BLOCK_OFFSET: usize = 100;
-
- /// The entry set for each `BasicBlock` is the ID of that block offset by a fixed amount to
- /// avoid colliding with the statement/terminator effects.
- fn mock_entry_set(&self, bb: BasicBlock) -> BitSet<usize> {
- let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
- ret.insert(Self::BASIC_BLOCK_OFFSET + bb.index());
- ret
- }
-
- fn mock_entry_sets(&self) -> IndexVec<BasicBlock, BitSet<usize>> {
- let empty = BitSet::new_empty(self.bits_per_block(self.body));
- let mut ret = IndexVec::from_elem(empty, &self.body.basic_blocks());
-
- for (bb, _) in self.body.basic_blocks().iter_enumerated() {
- ret[bb] = self.mock_entry_set(bb);
- }
-
- ret
- }
-
- /// Returns the index that should be added to the dataflow state at the given target.
- ///
- /// This index is only unique within a given basic block. `SeekAfter` and
- /// `SeekAfterAssumeCallReturns` have the same effect unless `target` is a `Call` terminator.
- fn effect_at_target(&self, target: SeekTarget) -> Option<usize> {
- use SeekTarget::*;
-
- let idx = match target {
- BlockStart(_) => return None,
-
- AfterAssumeCallReturns(loc) if is_call_terminator_non_diverging(self.body, loc) => {
- loc.statement_index * 2 + 2
- }
-
- Before(loc) => loc.statement_index * 2,
- After(loc) | AfterAssumeCallReturns(loc) => loc.statement_index * 2 + 1,
- };
-
- assert!(idx < Self::BASIC_BLOCK_OFFSET, "Too many statements in basic block");
- Some(idx)
- }
-
- /// Returns the expected state at the given `SeekTarget`.
- ///
- /// This is the union of index of the target basic block, the index assigned to the
- /// target statement or terminator, and the indices of all preceding statements in the target
- /// basic block.
- ///
- /// For example, the expected state when calling
- /// `seek_before(Location { block: 2, statement_index: 2 })` would be `[102, 0, 1, 2, 3, 4]`.
- fn expected_state_at_target(&self, target: SeekTarget) -> BitSet<usize> {
- let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
- ret.insert(Self::BASIC_BLOCK_OFFSET + target.block().index());
-
- if let Some(target_effect) = self.effect_at_target(target) {
- for i in 0..=target_effect {
- ret.insert(i);
- }
- }
-
- ret
- }
-}
-
-impl BottomValue for MockAnalysis<'tcx> {
- const BOTTOM_VALUE: bool = false;
-}
-
-impl AnalysisDomain<'tcx> for MockAnalysis<'tcx> {
- type Idx = usize;
-
- const NAME: &'static str = "mock";
-
- fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
- Self::BASIC_BLOCK_OFFSET + body.basic_blocks().len()
- }
-
- fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
- unimplemented!("This is never called since `MockAnalysis` is never iterated to fixpoint");
- }
-}
-
-impl Analysis<'tcx> for MockAnalysis<'tcx> {
- fn apply_statement_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- _statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- let idx = self.effect_at_target(SeekTarget::After(location)).unwrap();
- assert!(state.insert(idx));
- }
-
- fn apply_before_statement_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- _statement: &mir::Statement<'tcx>,
- location: Location,
- ) {
- let idx = self.effect_at_target(SeekTarget::Before(location)).unwrap();
- assert!(state.insert(idx));
- }
-
- fn apply_terminator_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- let idx = self.effect_at_target(SeekTarget::After(location)).unwrap();
- assert!(state.insert(idx));
- }
-
- fn apply_before_terminator_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- _terminator: &mir::Terminator<'tcx>,
- location: Location,
- ) {
- let idx = self.effect_at_target(SeekTarget::Before(location)).unwrap();
- assert!(state.insert(idx));
- }
-
- fn apply_call_return_effect(
- &self,
- state: &mut BitSet<Self::Idx>,
- block: BasicBlock,
- _func: &mir::Operand<'tcx>,
- _args: &[mir::Operand<'tcx>],
- _return_place: &mir::Place<'tcx>,
- ) {
- let location = self.body.terminator_loc(block);
- let idx = self.effect_at_target(SeekTarget::AfterAssumeCallReturns(location)).unwrap();
- assert!(state.insert(idx));
- }
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-enum SeekTarget {
- BlockStart(BasicBlock),
- Before(Location),
- After(Location),
- AfterAssumeCallReturns(Location),
-}
-
-impl SeekTarget {
- fn block(&self) -> BasicBlock {
- use SeekTarget::*;
-
- match *self {
- BlockStart(block) => block,
- Before(loc) | After(loc) | AfterAssumeCallReturns(loc) => loc.block,
- }
- }
-
- /// An iterator over all possible `SeekTarget`s in a given block in order, starting with
- /// `BlockStart`.
- ///
- /// This includes both `After` and `AfterAssumeCallReturns` for every `Location`.
- fn iter_in_block(body: &mir::Body<'_>, block: BasicBlock) -> impl Iterator<Item = Self> {
- let statements_and_terminator = (0..=body[block].statements.len())
- .flat_map(|i| (0..3).map(move |j| (i, j)))
- .map(move |(i, kind)| {
- let loc = Location { block, statement_index: i };
- match kind {
- 0 => SeekTarget::Before(loc),
- 1 => SeekTarget::After(loc),
- 2 => SeekTarget::AfterAssumeCallReturns(loc),
- _ => unreachable!(),
- }
- });
-
- std::iter::once(SeekTarget::BlockStart(block)).chain(statements_and_terminator)
- }
-}
-
-#[test]
-fn cursor_seek() {
- let body = mock_body();
- let body = &body;
- let analysis = MockAnalysis { body };
-
- let mut cursor =
- Results { entry_sets: analysis.mock_entry_sets(), analysis }.into_results_cursor(body);
-
- // Sanity check: the mock call return effect is unique and actually being applied.
- let call_terminator_loc = Location { block: BasicBlock::from_usize(2), statement_index: 2 };
- assert!(is_call_terminator_non_diverging(body, call_terminator_loc));
-
- let call_return_effect = cursor
- .analysis()
- .effect_at_target(SeekTarget::AfterAssumeCallReturns(call_terminator_loc))
- .unwrap();
- assert_ne!(
- call_return_effect,
- cursor.analysis().effect_at_target(SeekTarget::After(call_terminator_loc)).unwrap()
- );
-
- cursor.seek_after(call_terminator_loc);
- assert!(!cursor.get().contains(call_return_effect));
- cursor.seek_after_assume_success(call_terminator_loc);
- assert!(cursor.get().contains(call_return_effect));
-
- let every_target = || {
- body.basic_blocks()
- .iter_enumerated()
- .flat_map(|(bb, _)| SeekTarget::iter_in_block(body, bb))
- };
-
- let mut seek_to_target = |targ| {
- use SeekTarget::*;
-
- match targ {
- BlockStart(block) => cursor.seek_to_block_start(block),
- Before(loc) => cursor.seek_before(loc),
- After(loc) => cursor.seek_after(loc),
- AfterAssumeCallReturns(loc) => cursor.seek_after_assume_success(loc),
- }
-
- assert_eq!(cursor.get(), &cursor.analysis().expected_state_at_target(targ));
- };
-
- // Seek *to* every possible `SeekTarget` *from* every possible `SeekTarget`.
- //
- // By resetting the cursor to `from` each time it changes, we end up checking some edges twice.
- // What we really want is an Eulerian cycle for the complete digraph over all possible
- // `SeekTarget`s, but it's not worth spending the time to compute it.
- for from in every_target() {
- seek_to_target(from);
-
- for to in every_target() {
- seek_to_target(to);
- seek_to_target(from);
- }
- }
-}
+++ /dev/null
-use rustc::mir::{self, BasicBlock, Location};
-use rustc_index::bit_set::BitSet;
-
-use super::{Analysis, Results};
-use crate::dataflow::impls::{borrows::Borrows, EverInitializedPlaces, MaybeUninitializedPlaces};
-
-/// Calls the corresponding method in `ResultsVisitor` for every location in a `mir::Body` with the
-/// dataflow state at that location.
-pub fn visit_results<F>(
- body: &'mir mir::Body<'tcx>,
- blocks: impl IntoIterator<Item = BasicBlock>,
- results: &impl ResultsVisitable<'tcx, FlowState = F>,
- vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>,
-) {
- let mut state = results.new_flow_state(body);
-
- for block in blocks {
- let block_data = &body[block];
- results.reset_to_block_start(&mut state, block);
-
- for (statement_index, stmt) in block_data.statements.iter().enumerate() {
- let loc = Location { block, statement_index };
-
- results.reconstruct_before_statement_effect(&mut state, stmt, loc);
- vis.visit_statement(&state, stmt, loc);
-
- results.reconstruct_statement_effect(&mut state, stmt, loc);
- vis.visit_statement_exit(&state, stmt, loc);
- }
-
- let loc = body.terminator_loc(block);
- let term = block_data.terminator();
-
- results.reconstruct_before_terminator_effect(&mut state, term, loc);
- vis.visit_terminator(&state, term, loc);
-
- results.reconstruct_terminator_effect(&mut state, term, loc);
- vis.visit_terminator_exit(&state, term, loc);
- }
-}
-
-pub trait ResultsVisitor<'mir, 'tcx> {
- type FlowState;
-
- /// Called with the `before_statement_effect` of the given statement applied to `state` but not
- /// its `statement_effect`.
- fn visit_statement(
- &mut self,
- _state: &Self::FlowState,
- _statement: &'mir mir::Statement<'tcx>,
- _location: Location,
- ) {
- }
-
- /// Called with both the `before_statement_effect` and the `statement_effect` of the given
- /// statement applied to `state`.
- fn visit_statement_exit(
- &mut self,
- _state: &Self::FlowState,
- _statement: &'mir mir::Statement<'tcx>,
- _location: Location,
- ) {
- }
-
- /// Called with the `before_terminator_effect` of the given terminator applied to `state` but not
- /// its `terminator_effect`.
- fn visit_terminator(
- &mut self,
- _state: &Self::FlowState,
- _terminator: &'mir mir::Terminator<'tcx>,
- _location: Location,
- ) {
- }
-
- /// Called with both the `before_terminator_effect` and the `terminator_effect` of the given
- /// terminator applied to `state`.
- ///
- /// The `call_return_effect` (if one exists) will *not* be applied to `state`.
- fn visit_terminator_exit(
- &mut self,
- _state: &Self::FlowState,
- _terminator: &'mir mir::Terminator<'tcx>,
- _location: Location,
- ) {
- }
-}
-
-/// Things that can be visited by a `ResultsVisitor`.
-///
-/// This trait exists so that we can visit the results of multiple dataflow analyses simultaneously.
-/// DO NOT IMPLEMENT MANUALLY. Instead, use the `impl_visitable` macro below.
-pub trait ResultsVisitable<'tcx> {
- type FlowState;
-
- /// Creates an empty `FlowState` to hold the transient state for these dataflow results.
- ///
- /// The value of the newly created `FlowState` will be overwritten by `reset_to_block_start`
- /// before it can be observed by a `ResultsVisitor`.
- fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState;
-
- fn reset_to_block_start(&self, state: &mut Self::FlowState, block: BasicBlock);
-
- fn reconstruct_before_statement_effect(
- &self,
- state: &mut Self::FlowState,
- statement: &mir::Statement<'tcx>,
- location: Location,
- );
-
- fn reconstruct_statement_effect(
- &self,
- state: &mut Self::FlowState,
- statement: &mir::Statement<'tcx>,
- location: Location,
- );
-
- fn reconstruct_before_terminator_effect(
- &self,
- state: &mut Self::FlowState,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- );
-
- fn reconstruct_terminator_effect(
- &self,
- state: &mut Self::FlowState,
- terminator: &mir::Terminator<'tcx>,
- location: Location,
- );
-}
-
-impl<'tcx, A> ResultsVisitable<'tcx> for Results<'tcx, A>
-where
- A: Analysis<'tcx>,
-{
- type FlowState = BitSet<A::Idx>;
-
- fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
- BitSet::new_empty(self.analysis.bits_per_block(body))
- }
-
- fn reset_to_block_start(&self, state: &mut Self::FlowState, block: BasicBlock) {
- state.overwrite(&self.entry_set_for_block(block));
- }
-
- fn reconstruct_before_statement_effect(
- &self,
- state: &mut Self::FlowState,
- stmt: &mir::Statement<'tcx>,
- loc: Location,
- ) {
- self.analysis.apply_before_statement_effect(state, stmt, loc);
- }
-
- fn reconstruct_statement_effect(
- &self,
- state: &mut Self::FlowState,
- stmt: &mir::Statement<'tcx>,
- loc: Location,
- ) {
- self.analysis.apply_statement_effect(state, stmt, loc);
- }
-
- fn reconstruct_before_terminator_effect(
- &self,
- state: &mut Self::FlowState,
- term: &mir::Terminator<'tcx>,
- loc: Location,
- ) {
- self.analysis.apply_before_terminator_effect(state, term, loc);
- }
-
- fn reconstruct_terminator_effect(
- &self,
- state: &mut Self::FlowState,
- term: &mir::Terminator<'tcx>,
- loc: Location,
- ) {
- self.analysis.apply_terminator_effect(state, term, loc);
- }
-}
-
-/// A tuple with named fields that can hold either the results or the transient state of the
-/// dataflow analyses used by the borrow checker.
-#[derive(Debug)]
-pub struct BorrowckAnalyses<B, U, E> {
- pub borrows: B,
- pub uninits: U,
- pub ever_inits: E,
-}
-
-/// The results of the dataflow analyses used by the borrow checker.
-pub type BorrowckResults<'mir, 'tcx> = BorrowckAnalyses<
- Results<'tcx, Borrows<'mir, 'tcx>>,
- Results<'tcx, MaybeUninitializedPlaces<'mir, 'tcx>>,
- Results<'tcx, EverInitializedPlaces<'mir, 'tcx>>,
->;
-
-/// The transient state of the dataflow analyses used by the borrow checker.
-pub type BorrowckFlowState<'mir, 'tcx> =
- <BorrowckResults<'mir, 'tcx> as ResultsVisitable<'tcx>>::FlowState;
-
-macro_rules! impl_visitable {
- ( $(
- $T:ident { $( $field:ident : $A:ident ),* $(,)? }
- )* ) => { $(
- impl<'tcx, $($A),*> ResultsVisitable<'tcx> for $T<$( Results<'tcx, $A> ),*>
- where
- $( $A: Analysis<'tcx>, )*
- {
- type FlowState = $T<$( BitSet<$A::Idx> ),*>;
-
- fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
- $T {
- $( $field: BitSet::new_empty(self.$field.analysis.bits_per_block(body)) ),*
- }
- }
-
- fn reset_to_block_start(
- &self,
- state: &mut Self::FlowState,
- block: BasicBlock,
- ) {
- $( state.$field.overwrite(&self.$field.entry_sets[block]); )*
- }
-
- fn reconstruct_before_statement_effect(
- &self,
- state: &mut Self::FlowState,
- stmt: &mir::Statement<'tcx>,
- loc: Location,
- ) {
- $( self.$field.analysis
- .apply_before_statement_effect(&mut state.$field, stmt, loc); )*
- }
-
- fn reconstruct_statement_effect(
- &self,
- state: &mut Self::FlowState,
- stmt: &mir::Statement<'tcx>,
- loc: Location,
- ) {
- $( self.$field.analysis
- .apply_statement_effect(&mut state.$field, stmt, loc); )*
- }
-
- fn reconstruct_before_terminator_effect(
- &self,
- state: &mut Self::FlowState,
- term: &mir::Terminator<'tcx>,
- loc: Location,
- ) {
- $( self.$field.analysis
- .apply_before_terminator_effect(&mut state.$field, term, loc); )*
- }
-
- fn reconstruct_terminator_effect(
- &self,
- state: &mut Self::FlowState,
- term: &mir::Terminator<'tcx>,
- loc: Location,
- ) {
- $( self.$field.analysis
- .apply_terminator_effect(&mut state.$field, term, loc); )*
- }
- }
- )* }
-}
-
-impl_visitable! {
- BorrowckAnalyses { borrows: B, uninits: U, ever_inits: E }
-}
pub use super::*;
-use crate::dataflow::generic::{AnalysisDomain, GenKill, GenKillAnalysis};
+use crate::dataflow::{AnalysisDomain, GenKill, GenKillAnalysis};
use rustc::mir::visit::Visitor;
use rustc::mir::*;
use rustc::ty::{ParamEnv, TyCtxt};
use crate::borrow_check::{
places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, ToRegionVid,
};
-use crate::dataflow::generic::{self, GenKill};
use crate::dataflow::BottomValue;
+use crate::dataflow::{self, GenKill};
use std::rc::Rc;
}
}
-impl<'tcx> generic::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
+impl<'tcx> dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
type Idx = BorrowIndex;
const NAME: &'static str = "borrows";
}
}
-impl<'tcx> generic::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
+impl<'tcx> dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
fn before_statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
use crate::util::elaborate_drops::DropFlagState;
-use super::generic::{AnalysisDomain, GenKill, GenKillAnalysis};
use super::move_paths::{HasMoveData, InitIndex, InitKind, LookupResult, MoveData, MovePathIndex};
-use super::BottomValue;
+use super::{AnalysisDomain, BottomValue, GenKill, GenKillAnalysis};
use super::drop_flag_effects_for_function_entry;
use super::drop_flag_effects_for_location;
pub use super::*;
-use crate::dataflow::generic::{self as dataflow, GenKill, Results, ResultsRefCursor};
use crate::dataflow::BottomValue;
+use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
use rustc::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
use rustc::mir::*;
use std::cell::RefCell;
-use rustc::mir::traversal;
-use rustc::mir::{self, BasicBlock, BasicBlockData, Body, Location, Statement, Terminator};
-use rustc::ty::{self, TyCtxt};
+use rustc::ty;
use rustc_ast::ast::{self, MetaItem};
-use rustc_ast_pretty::pprust;
-use rustc_data_structures::work_queue::WorkQueue;
-use rustc_hir::def_id::DefId;
-use rustc_index::bit_set::{BitSet, HybridBitSet};
-use rustc_index::vec::Idx;
-use rustc_session::Session;
use rustc_span::symbol::{sym, Symbol};
-use std::borrow::Borrow;
-use std::fmt;
-use std::io;
-use std::path::PathBuf;
-
-pub use self::at_location::{FlowAtLocation, FlowsAtLocation};
pub(crate) use self::drop_flag_effects::*;
-pub use self::impls::borrows::Borrows;
-pub use self::impls::DefinitelyInitializedPlaces;
-pub use self::impls::EverInitializedPlaces;
-pub use self::impls::{MaybeBorrowedLocals, MaybeMutBorrowedLocals};
-pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
-pub use self::impls::{MaybeRequiresStorage, MaybeStorageLive};
+pub use self::framework::{
+ visit_results, Analysis, AnalysisDomain, BorrowckFlowState, BorrowckResults, BottomValue,
+ Engine, GenKill, GenKillAnalysis, Results, ResultsCursor, ResultsRefCursor, ResultsVisitor,
+};
+pub use self::impls::{
+ borrows::Borrows, DefinitelyInitializedPlaces, EverInitializedPlaces, MaybeBorrowedLocals,
+ MaybeInitializedPlaces, MaybeMutBorrowedLocals, MaybeRequiresStorage, MaybeStorageLive,
+ MaybeUninitializedPlaces,
+};
use self::move_paths::MoveData;
-mod at_location;
pub mod drop_flag_effects;
-pub mod generic;
-mod graphviz;
+mod framework;
mod impls;
pub mod move_paths;
};
}
-pub(crate) struct DataflowBuilder<'a, 'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
-{
- def_id: DefId,
- flow_state: DataflowAnalysis<'a, 'tcx, BD>,
- print_preflow_to: Option<String>,
- print_postflow_to: Option<String>,
-}
-
-/// `DebugFormatted` encapsulates the "{:?}" rendering of some
-/// arbitrary value. This way: you pay cost of allocating an extra
-/// string (as well as that of rendering up-front); in exchange, you
-/// don't have to hand over ownership of your value or deal with
-/// borrowing it.
-pub struct DebugFormatted(String);
-
-impl DebugFormatted {
- pub fn new(input: &dyn fmt::Debug) -> DebugFormatted {
- DebugFormatted(format!("{:?}", input))
- }
-}
-
-impl fmt::Debug for DebugFormatted {
- fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
- write!(w, "{}", self.0)
- }
-}
-
-pub trait Dataflow<'tcx, BD: BitDenotation<'tcx>> {
- /// Sets up and runs the dataflow problem, using `p` to render results if
- /// implementation so chooses.
- fn dataflow<P>(&mut self, p: P)
- where
- P: Fn(&BD, BD::Idx) -> DebugFormatted,
- {
- let _ = p; // default implementation does not instrument process.
- self.build_sets();
- self.propagate();
- }
-
- /// Sets up the entry, gen, and kill sets for this instance of a dataflow problem.
- fn build_sets(&mut self);
-
- /// Finds a fixed-point solution to this instance of a dataflow problem.
- fn propagate(&mut self);
-}
-
-impl<'a, 'tcx, BD> Dataflow<'tcx, BD> for DataflowBuilder<'a, 'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
-{
- fn dataflow<P>(&mut self, p: P)
- where
- P: Fn(&BD, BD::Idx) -> DebugFormatted,
- {
- self.flow_state.build_sets();
- self.pre_dataflow_instrumentation(|c, i| p(c, i)).unwrap();
- self.flow_state.propagate();
- self.post_dataflow_instrumentation(|c, i| p(c, i)).unwrap();
- }
-
- fn build_sets(&mut self) {
- self.flow_state.build_sets();
- }
- fn propagate(&mut self) {
- self.flow_state.propagate();
- }
+pub struct MoveDataParamEnv<'tcx> {
+ pub(crate) move_data: MoveData<'tcx>,
+ pub(crate) param_env: ty::ParamEnv<'tcx>,
}
pub(crate) fn has_rustc_mir_with(attrs: &[ast::Attribute], name: Symbol) -> Option<MetaItem> {
}
None
}
-
-pub struct MoveDataParamEnv<'tcx> {
- pub(crate) move_data: MoveData<'tcx>,
- pub(crate) param_env: ty::ParamEnv<'tcx>,
-}
-
-pub fn do_dataflow<'a, 'tcx, BD, P>(
- tcx: TyCtxt<'tcx>,
- body: &'a Body<'tcx>,
- def_id: DefId,
- attributes: &[ast::Attribute],
- dead_unwinds: &BitSet<BasicBlock>,
- bd: BD,
- p: P,
-) -> DataflowResults<'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
- P: Fn(&BD, BD::Idx) -> DebugFormatted,
-{
- let flow_state = DataflowAnalysis::new(body, dead_unwinds, bd);
- flow_state.run(tcx, def_id, attributes, p)
-}
-
-impl<'a, 'tcx, BD> DataflowAnalysis<'a, 'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
-{
- pub(crate) fn run<P>(
- self,
- tcx: TyCtxt<'tcx>,
- def_id: DefId,
- attributes: &[ast::Attribute],
- p: P,
- ) -> DataflowResults<'tcx, BD>
- where
- P: Fn(&BD, BD::Idx) -> DebugFormatted,
- {
- let name_found = |sess: &Session, attrs: &[ast::Attribute], name| -> Option<String> {
- if let Some(item) = has_rustc_mir_with(attrs, name) {
- if let Some(s) = item.value_str() {
- return Some(s.to_string());
- } else {
- let path = pprust::path_to_string(&item.path);
- sess.span_err(item.span, &format!("{} attribute requires a path", path));
- return None;
- }
- }
- None
- };
-
- let print_preflow_to = name_found(tcx.sess, attributes, sym::borrowck_graphviz_preflow);
- let print_postflow_to = name_found(tcx.sess, attributes, sym::borrowck_graphviz_postflow);
-
- let mut mbcx =
- DataflowBuilder { def_id, print_preflow_to, print_postflow_to, flow_state: self };
-
- mbcx.dataflow(p);
- mbcx.flow_state.results()
- }
-}
-
-struct PropagationContext<'b, 'a, 'tcx, O>
-where
- O: BitDenotation<'tcx>,
-{
- builder: &'b mut DataflowAnalysis<'a, 'tcx, O>,
-}
-
-impl<'a, 'tcx, BD> DataflowAnalysis<'a, 'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
-{
- fn propagate(&mut self) {
- let mut temp = BitSet::new_empty(self.flow_state.sets.bits_per_block);
- let mut propcx = PropagationContext { builder: self };
- propcx.walk_cfg(&mut temp);
- }
-
- fn build_sets(&mut self) {
- // Build the transfer function for each block.
- for (bb, data) in self.body.basic_blocks().iter_enumerated() {
- let &mir::BasicBlockData { ref statements, ref terminator, is_cleanup: _ } = data;
-
- let trans = self.flow_state.sets.trans_mut_for(bb.index());
- for j_stmt in 0..statements.len() {
- let location = Location { block: bb, statement_index: j_stmt };
- self.flow_state.operator.before_statement_effect(trans, location);
- self.flow_state.operator.statement_effect(trans, location);
- }
-
- if terminator.is_some() {
- let location = Location { block: bb, statement_index: statements.len() };
- self.flow_state.operator.before_terminator_effect(trans, location);
- self.flow_state.operator.terminator_effect(trans, location);
- }
- }
-
- // Initialize the flow state at entry to the start block.
- let on_entry = self.flow_state.sets.entry_set_mut_for(mir::START_BLOCK.index());
- self.flow_state.operator.start_block_effect(on_entry);
- }
-}
-
-impl<'b, 'a, 'tcx, BD> PropagationContext<'b, 'a, 'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
-{
- fn walk_cfg(&mut self, in_out: &mut BitSet<BD::Idx>) {
- let body = self.builder.body;
-
- // Initialize the dirty queue in reverse post-order. This makes it more likely that the
- // entry state for each basic block will have the effects of its predecessors applied
- // before it is processed. In fact, for CFGs without back edges, this guarantees that
- // dataflow will converge in exactly `N` iterations, where `N` is the number of basic
- // blocks.
- let mut dirty_queue: WorkQueue<mir::BasicBlock> =
- WorkQueue::with_none(body.basic_blocks().len());
- for (bb, _) in traversal::reverse_postorder(body) {
- dirty_queue.insert(bb);
- }
-
- // Add blocks which are not reachable from START_BLOCK to the work queue. These blocks will
- // be processed after the ones added above.
- for bb in body.basic_blocks().indices() {
- dirty_queue.insert(bb);
- }
-
- while let Some(bb) = dirty_queue.pop() {
- let (on_entry, trans) = self.builder.flow_state.sets.get_mut(bb.index());
- debug_assert!(in_out.words().len() == on_entry.words().len());
- in_out.overwrite(on_entry);
- trans.apply(in_out);
-
- let bb_data = &body[bb];
- self.builder.propagate_bits_into_graph_successors_of(
- in_out,
- (bb, bb_data),
- &mut dirty_queue,
- );
- }
- }
-}
-
-fn dataflow_path(context: &str, path: &str) -> PathBuf {
- let mut path = PathBuf::from(path);
- let new_file_name = {
- let orig_file_name = path.file_name().unwrap().to_str().unwrap();
- format!("{}_{}", context, orig_file_name)
- };
- path.set_file_name(new_file_name);
- path
-}
-
-impl<'a, 'tcx, BD> DataflowBuilder<'a, 'tcx, BD>
-where
- BD: BitDenotation<'tcx>,
-{
- fn pre_dataflow_instrumentation<P>(&self, p: P) -> io::Result<()>
- where
- P: Fn(&BD, BD::Idx) -> DebugFormatted,
- {
- if let Some(ref path_str) = self.print_preflow_to {
- let path = dataflow_path(BD::name(), path_str);
- graphviz::print_borrowck_graph_to(self, &path, p)
- } else {
- Ok(())
- }
- }
-
- fn post_dataflow_instrumentation<P>(&self, p: P) -> io::Result<()>
- where
- P: Fn(&BD, BD::Idx) -> DebugFormatted,
- {
- if let Some(ref path_str) = self.print_postflow_to {
- let path = dataflow_path(BD::name(), path_str);
- graphviz::print_borrowck_graph_to(self, &path, p)
- } else {
- Ok(())
- }
- }
-}
-
-/// DataflowResultsConsumer abstracts over walking the MIR with some
-/// already constructed dataflow results.
-///
-/// It abstracts over the FlowState and also completely hides the
-/// underlying flow analysis results, because it needs to handle cases
-/// where we are combining the results of *multiple* flow analyses
-/// (e.g., borrows + inits + uninits).
-pub(crate) trait DataflowResultsConsumer<'a, 'tcx: 'a> {
- type FlowState: FlowsAtLocation;
-
- // Observation Hooks: override (at least one of) these to get analysis feedback.
- fn visit_block_entry(&mut self, _bb: BasicBlock, _flow_state: &Self::FlowState) {}
-
- fn visit_statement_entry(
- &mut self,
- _loc: Location,
- _stmt: &'a Statement<'tcx>,
- _flow_state: &Self::FlowState,
- ) {
- }
-
- fn visit_terminator_entry(
- &mut self,
- _loc: Location,
- _term: &'a Terminator<'tcx>,
- _flow_state: &Self::FlowState,
- ) {
- }
-
- // Main entry point: this drives the processing of results.
-
- fn analyze_results(&mut self, flow_uninit: &mut Self::FlowState) {
- let flow = flow_uninit;
- for (bb, _) in traversal::reverse_postorder(self.body()) {
- flow.reset_to_entry_of(bb);
- self.process_basic_block(bb, flow);
- }
- }
-
- fn process_basic_block(&mut self, bb: BasicBlock, flow_state: &mut Self::FlowState) {
- self.visit_block_entry(bb, flow_state);
-
- let BasicBlockData { ref statements, ref terminator, is_cleanup: _ } = self.body()[bb];
- let mut location = Location { block: bb, statement_index: 0 };
- for stmt in statements.iter() {
- flow_state.reconstruct_statement_effect(location);
- self.visit_statement_entry(location, stmt, flow_state);
- flow_state.apply_local_effect(location);
- location.statement_index += 1;
- }
-
- if let Some(ref term) = *terminator {
- flow_state.reconstruct_terminator_effect(location);
- self.visit_terminator_entry(location, term, flow_state);
-
- // We don't need to apply the effect of the terminator,
- // since we are only visiting dataflow state on control
- // flow entry to the various nodes. (But we still need to
- // reconstruct the effect, because the visit method might
- // inspect it.)
- }
- }
-
- // Delegated Hooks: Provide access to the MIR and process the flow state.
-
- fn body(&self) -> &'a Body<'tcx>;
-}
-
-/// Allows iterating dataflow results in a flexible and reasonably fast way.
-pub struct DataflowResultsCursor<'mir, 'tcx, BD, DR = DataflowResults<'tcx, BD>>
-where
- BD: BitDenotation<'tcx>,
- DR: Borrow<DataflowResults<'tcx, BD>>,
-{
- flow_state: FlowAtLocation<'tcx, BD, DR>,
-
- // The statement (or terminator) whose effect has been reconstructed in
- // flow_state.
- curr_loc: Option<Location>,
-
- body: &'mir Body<'tcx>,
-}
-
-pub type DataflowResultsRefCursor<'mir, 'tcx, BD> =
- DataflowResultsCursor<'mir, 'tcx, BD, &'mir DataflowResults<'tcx, BD>>;
-
-impl<'mir, 'tcx, BD, DR> DataflowResultsCursor<'mir, 'tcx, BD, DR>
-where
- BD: BitDenotation<'tcx>,
- DR: Borrow<DataflowResults<'tcx, BD>>,
-{
- pub fn new(result: DR, body: &'mir Body<'tcx>) -> Self {
- DataflowResultsCursor { flow_state: FlowAtLocation::new(result), curr_loc: None, body }
- }
-
- /// Seek to the given location in MIR. This method is fast if you are
- /// traversing your MIR statements in order.
- ///
- /// After calling `seek`, the current state will reflect all effects up to
- /// and including the `before_statement_effect` of the statement at location
- /// `loc`. The `statement_effect` of the statement at `loc` will be
- /// available as the current effect (see e.g. `each_gen_bit`).
- ///
- /// If `loc.statement_index` equals the number of statements in the block,
- /// we will reconstruct the terminator effect in the same way as described
- /// above.
- pub fn seek(&mut self, loc: Location) {
- if self.curr_loc.map(|cur| loc == cur).unwrap_or(false) {
- return;
- }
-
- let start_index;
- let should_reset = match self.curr_loc {
- None => true,
- Some(cur) if loc.block != cur.block || loc.statement_index < cur.statement_index => {
- true
- }
- _ => false,
- };
- if should_reset {
- self.flow_state.reset_to_entry_of(loc.block);
- start_index = 0;
- } else {
- let curr_loc = self.curr_loc.unwrap();
- start_index = curr_loc.statement_index;
- // Apply the effect from the last seek to the current state.
- self.flow_state.apply_local_effect(curr_loc);
- }
-
- for stmt in start_index..loc.statement_index {
- let mut stmt_loc = loc;
- stmt_loc.statement_index = stmt;
- self.flow_state.reconstruct_statement_effect(stmt_loc);
- self.flow_state.apply_local_effect(stmt_loc);
- }
-
- if loc.statement_index == self.body[loc.block].statements.len() {
- self.flow_state.reconstruct_terminator_effect(loc);
- } else {
- self.flow_state.reconstruct_statement_effect(loc);
- }
- self.curr_loc = Some(loc);
- }
-
- /// Return whether the current state contains bit `x`.
- pub fn contains(&self, x: BD::Idx) -> bool {
- self.flow_state.contains(x)
- }
-
- /// Iterate over each `gen` bit in the current effect (invoke `seek` first).
- pub fn each_gen_bit<F>(&self, f: F)
- where
- F: FnMut(BD::Idx),
- {
- self.flow_state.each_gen_bit(f)
- }
-
- pub fn get(&self) -> &BitSet<BD::Idx> {
- self.flow_state.as_dense()
- }
-}
-
-pub struct DataflowAnalysis<'a, 'tcx, O>
-where
- O: BitDenotation<'tcx>,
-{
- flow_state: DataflowState<'tcx, O>,
- dead_unwinds: &'a BitSet<mir::BasicBlock>,
- body: &'a Body<'tcx>,
-}
-
-impl<'a, 'tcx, O> DataflowAnalysis<'a, 'tcx, O>
-where
- O: BitDenotation<'tcx>,
-{
- pub fn results(self) -> DataflowResults<'tcx, O> {
- DataflowResults(self.flow_state)
- }
-
- pub fn body(&self) -> &'a Body<'tcx> {
- self.body
- }
-}
-
-pub struct DataflowResults<'tcx, O>(pub(crate) DataflowState<'tcx, O>)
-where
- O: BitDenotation<'tcx>;
-
-impl<'tcx, O: BitDenotation<'tcx>> DataflowResults<'tcx, O> {
- pub fn sets(&self) -> &AllSets<O::Idx> {
- &self.0.sets
- }
-
- pub fn operator(&self) -> &O {
- &self.0.operator
- }
-}
-
-/// State of a dataflow analysis; couples a collection of bit sets
-/// with operator used to initialize and merge bits during analysis.
-pub struct DataflowState<'tcx, O: BitDenotation<'tcx>> {
- /// All the sets for the analysis. (Factored into its
- /// own structure so that we can borrow it mutably
- /// on its own separate from other fields.)
- pub sets: AllSets<O::Idx>,
-
- /// operator used to initialize, combine, and interpret bits.
- pub(crate) operator: O,
-}
-
-impl<'tcx, O: BitDenotation<'tcx>> DataflowState<'tcx, O> {
- pub(crate) fn interpret_set<'c, P>(
- &self,
- o: &'c O,
- set: &BitSet<O::Idx>,
- render_idx: &P,
- ) -> Vec<DebugFormatted>
- where
- P: Fn(&O, O::Idx) -> DebugFormatted,
- {
- set.iter().map(|i| render_idx(o, i)).collect()
- }
-
- pub(crate) fn interpret_hybrid_set<'c, P>(
- &self,
- o: &'c O,
- set: &HybridBitSet<O::Idx>,
- render_idx: &P,
- ) -> Vec<DebugFormatted>
- where
- P: Fn(&O, O::Idx) -> DebugFormatted,
- {
- set.iter().map(|i| render_idx(o, i)).collect()
- }
-}
-
-/// A 2-tuple representing the "gen" and "kill" bitsets during
-/// dataflow analysis.
-///
-/// It is best to ensure that the intersection of `gen_set` and
-/// `kill_set` is empty; otherwise the results of the dataflow will
-/// have a hidden dependency on what order the bits are generated and
-/// killed during the iteration. (This is such a good idea that the
-/// `fn gen` and `fn kill` methods that set their state enforce this
-/// for you.)
-#[derive(Debug, Clone, Copy)]
-pub struct GenKill<T> {
- pub(crate) gen_set: T,
- pub(crate) kill_set: T,
-}
-
-pub type GenKillSet<T> = GenKill<HybridBitSet<T>>;
-
-impl<T> GenKill<T> {
- /// Creates a new tuple where `gen_set == kill_set == elem`.
- pub(crate) fn from_elem(elem: T) -> Self
- where
- T: Clone,
- {
- GenKill { gen_set: elem.clone(), kill_set: elem }
- }
-}
-
-impl<E: Idx> GenKillSet<E> {
- pub fn clear(&mut self) {
- self.gen_set.clear();
- self.kill_set.clear();
- }
-
- pub fn gen(&mut self, e: E) {
- self.gen_set.insert(e);
- self.kill_set.remove(e);
- }
-
- pub fn gen_all(&mut self, i: impl IntoIterator<Item: Borrow<E>>) {
- for j in i {
- self.gen(*j.borrow());
- }
- }
-
- pub fn kill(&mut self, e: E) {
- self.gen_set.remove(e);
- self.kill_set.insert(e);
- }
-
- pub fn kill_all(&mut self, i: impl IntoIterator<Item: Borrow<E>>) {
- for j in i {
- self.kill(*j.borrow());
- }
- }
-
- /// Computes `(set ∪ gen) - kill` and assigns the result to `set`.
- pub(crate) fn apply(&self, set: &mut BitSet<E>) {
- set.union(&self.gen_set);
- set.subtract(&self.kill_set);
- }
-}
-
-#[derive(Debug)]
-pub struct AllSets<E: Idx> {
- /// Analysis bitwidth for each block.
- bits_per_block: usize,
-
- /// For each block, bits valid on entry to the block.
- on_entry: Vec<BitSet<E>>,
-
- /// The transfer function of each block expressed as the set of bits
- /// generated and killed by executing the statements + terminator in the
- /// block -- with one caveat. In particular, for *call terminators*, the
- /// effect of storing the destination is not included, since that only takes
- /// effect on the **success** edge (and not the unwind edge).
- trans: Vec<GenKillSet<E>>,
-}
-
-impl<E: Idx> AllSets<E> {
- pub fn bits_per_block(&self) -> usize {
- self.bits_per_block
- }
-
- pub fn get_mut(&mut self, block_idx: usize) -> (&mut BitSet<E>, &mut GenKillSet<E>) {
- (&mut self.on_entry[block_idx], &mut self.trans[block_idx])
- }
-
- pub fn trans_for(&self, block_idx: usize) -> &GenKillSet<E> {
- &self.trans[block_idx]
- }
- pub fn trans_mut_for(&mut self, block_idx: usize) -> &mut GenKillSet<E> {
- &mut self.trans[block_idx]
- }
- pub fn entry_set_for(&self, block_idx: usize) -> &BitSet<E> {
- &self.on_entry[block_idx]
- }
- pub fn entry_set_mut_for(&mut self, block_idx: usize) -> &mut BitSet<E> {
- &mut self.on_entry[block_idx]
- }
- pub fn gen_set_for(&self, block_idx: usize) -> &HybridBitSet<E> {
- &self.trans_for(block_idx).gen_set
- }
- pub fn kill_set_for(&self, block_idx: usize) -> &HybridBitSet<E> {
- &self.trans_for(block_idx).kill_set
- }
-}
-
-/// Parameterization for the precise form of data flow that is used.
-///
-/// `BottomValue` determines whether the initial entry set for each basic block is empty or full.
-/// This also determines the semantics of the lattice `join` operator used to merge dataflow
-/// results, since dataflow works by starting at the bottom and moving monotonically to a fixed
-/// point.
-///
-/// This means, for propagation across the graph, that you either want to start at all-zeroes and
-/// then use Union as your merge when propagating, or you start at all-ones and then use Intersect
-/// as your merge when propagating.
-pub trait BottomValue {
- /// Specifies the initial value for each bit in the entry set for each basic block.
- const BOTTOM_VALUE: bool;
-
- /// Merges `in_set` into `inout_set`, returning `true` if `inout_set` changed.
- ///
- /// It is almost certainly wrong to override this, since it automatically applies
- /// * `inout_set & in_set` if `BOTTOM_VALUE == true`
- /// * `inout_set | in_set` if `BOTTOM_VALUE == false`
- ///
- /// This means that if a bit is not `BOTTOM_VALUE`, it is propagated into all target blocks.
- /// For clarity, the above statement again from a different perspective:
- /// A bit in the block's entry set is `!BOTTOM_VALUE` if *any* predecessor block's bit value is
- /// `!BOTTOM_VALUE`.
- ///
- /// There are situations where you want the opposite behaviour: propagate only if *all*
- /// predecessor blocks's value is `!BOTTOM_VALUE`.
- /// E.g. if you want to know whether a bit is *definitely* set at a specific location. This
- /// means that all code paths leading to the location must have set the bit, instead of any
- /// code path leading there.
- ///
- /// If you want this kind of "definitely set" analysis, you need to
- /// 1. Invert `BOTTOM_VALUE`
- /// 2. Reset the `entry_set` in `start_block_effect` to `!BOTTOM_VALUE`
- /// 3. Override `join` to do the opposite from what it's doing now.
- #[inline]
- fn join<T: Idx>(&self, inout_set: &mut BitSet<T>, in_set: &BitSet<T>) -> bool {
- if !Self::BOTTOM_VALUE { inout_set.union(in_set) } else { inout_set.intersect(in_set) }
- }
-}
-
-/// A specific flavor of dataflow analysis.
-///
-/// To run a dataflow analysis, one sets up an initial state for the
-/// `START_BLOCK` via `start_block_effect` and a transfer function (`trans`)
-/// for each block individually. The entry set for all other basic blocks is
-/// initialized to `Self::BOTTOM_VALUE`. The dataflow analysis then
-/// iteratively modifies the various entry sets (but leaves the the transfer
-/// function unchanged). `BottomValue::join` is used to merge the bitsets from
-/// two blocks (e.g. when two blocks' terminator jumps to a single block, that
-/// target block's state is the merged state of both incoming blocks).
-pub trait BitDenotation<'tcx>: BottomValue {
- /// Specifies what index type is used to access the bitvector.
- type Idx: Idx;
-
- /// A name describing the dataflow analysis that this
- /// `BitDenotation` is supporting. The name should be something
- /// suitable for plugging in as part of a filename (i.e., avoid
- /// space-characters or other things that tend to look bad on a
- /// file system, like slashes or periods). It is also better for
- /// the name to be reasonably short, again because it will be
- /// plugged into a filename.
- fn name() -> &'static str;
-
- /// Size of each bitvector allocated for each block in the analysis.
- fn bits_per_block(&self) -> usize;
-
- /// Mutates the entry set according to the effects that
- /// have been established *prior* to entering the start
- /// block. This can't access the gen/kill sets, because
- /// these won't be accounted for correctly.
- ///
- /// (For example, establishing the call arguments.)
- fn start_block_effect(&self, entry_set: &mut BitSet<Self::Idx>);
-
- /// Similar to `statement_effect`, except it applies
- /// *just before* the statement rather than *just after* it.
- ///
- /// This matters for "dataflow at location" APIs, because the
- /// before-statement effect is visible while visiting the
- /// statement, while the after-statement effect only becomes
- /// visible at the next statement.
- ///
- /// Both the before-statement and after-statement effects are
- /// applied, in that order, before moving for the next
- /// statement.
- fn before_statement_effect(&self, _trans: &mut GenKillSet<Self::Idx>, _location: Location) {}
-
- /// Mutates the block-sets (the flow sets for the given
- /// basic block) according to the effects of evaluating statement.
- ///
- /// This is used, in particular, for building up the
- /// "transfer-function" representing the overall-effect of the
- /// block, represented via GEN and KILL sets.
- ///
- /// The statement is identified as `bb_data[idx_stmt]`, where
- /// `bb_data` is the sequence of statements identified by `bb` in
- /// the MIR.
- fn statement_effect(&self, trans: &mut GenKillSet<Self::Idx>, location: Location);
-
- /// Similar to `terminator_effect`, except it applies
- /// *just before* the terminator rather than *just after* it.
- ///
- /// This matters for "dataflow at location" APIs, because the
- /// before-terminator effect is visible while visiting the
- /// terminator, while the after-terminator effect only becomes
- /// visible at the terminator's successors.
- ///
- /// Both the before-terminator and after-terminator effects are
- /// applied, in that order, before moving for the next
- /// terminator.
- fn before_terminator_effect(&self, _trans: &mut GenKillSet<Self::Idx>, _location: Location) {}
-
- /// Mutates the block-sets (the flow sets for the given
- /// basic block) according to the effects of evaluating
- /// the terminator.
- ///
- /// This is used, in particular, for building up the
- /// "transfer-function" representing the overall-effect of the
- /// block, represented via GEN and KILL sets.
- ///
- /// The effects applied here cannot depend on which branch the
- /// terminator took.
- fn terminator_effect(&self, trans: &mut GenKillSet<Self::Idx>, location: Location);
-
- /// Mutates the block-sets according to the (flow-dependent)
- /// effect of a successful return from a Call terminator.
- ///
- /// If basic-block BB_x ends with a call-instruction that, upon
- /// successful return, flows to BB_y, then this method will be
- /// called on the exit flow-state of BB_x in order to set up the
- /// entry flow-state of BB_y.
- ///
- /// This is used, in particular, as a special case during the
- /// "propagate" loop where all of the basic blocks are repeatedly
- /// visited. Since the effects of a Call terminator are
- /// flow-dependent, the current MIR cannot encode them via just
- /// GEN and KILL sets attached to the block, and so instead we add
- /// this extra machinery to represent the flow-dependent effect.
- //
- // FIXME: right now this is a bit of a wart in the API. It might
- // be better to represent this as an additional gen- and
- // kill-sets associated with each edge coming out of the basic
- // block.
- fn propagate_call_return(
- &self,
- in_out: &mut BitSet<Self::Idx>,
- call_bb: mir::BasicBlock,
- dest_bb: mir::BasicBlock,
- dest_place: &mir::Place<'tcx>,
- );
-}
-
-impl<'a, 'tcx, D> DataflowAnalysis<'a, 'tcx, D>
-where
- D: BitDenotation<'tcx>,
-{
- pub fn new(
- body: &'a Body<'tcx>,
- dead_unwinds: &'a BitSet<mir::BasicBlock>,
- denotation: D,
- ) -> Self {
- let bits_per_block = denotation.bits_per_block();
- let num_blocks = body.basic_blocks().len();
-
- let on_entry = if D::BOTTOM_VALUE {
- vec![BitSet::new_filled(bits_per_block); num_blocks]
- } else {
- vec![BitSet::new_empty(bits_per_block); num_blocks]
- };
- let nop = GenKill::from_elem(HybridBitSet::new_empty(bits_per_block));
-
- DataflowAnalysis {
- body,
- dead_unwinds,
- flow_state: DataflowState {
- sets: AllSets { bits_per_block, on_entry, trans: vec![nop; num_blocks] },
- operator: denotation,
- },
- }
- }
-}
-
-impl<'a, 'tcx, D> DataflowAnalysis<'a, 'tcx, D>
-where
- D: BitDenotation<'tcx>,
-{
- /// Propagates the bits of `in_out` into all the successors of `bb`,
- /// using bitwise operator denoted by `self.operator`.
- ///
- /// For most blocks, this is entirely uniform. However, for blocks
- /// that end with a call terminator, the effect of the call on the
- /// dataflow state may depend on whether the call returned
- /// successfully or unwound.
- ///
- /// To reflect this, the `propagate_call_return` method of the
- /// `BitDenotation` mutates `in_out` when propagating `in_out` via
- /// a call terminator; such mutation is performed *last*, to
- /// ensure its side-effects do not leak elsewhere (e.g., into
- /// unwind target).
- fn propagate_bits_into_graph_successors_of(
- &mut self,
- in_out: &mut BitSet<D::Idx>,
- (bb, bb_data): (mir::BasicBlock, &mir::BasicBlockData<'tcx>),
- dirty_list: &mut WorkQueue<mir::BasicBlock>,
- ) {
- match bb_data.terminator().kind {
- mir::TerminatorKind::Return
- | mir::TerminatorKind::Resume
- | mir::TerminatorKind::Abort
- | mir::TerminatorKind::GeneratorDrop
- | mir::TerminatorKind::Unreachable => {}
- mir::TerminatorKind::Goto { target }
- | mir::TerminatorKind::Assert { target, cleanup: None, .. }
- | mir::TerminatorKind::Yield { resume: target, drop: None, .. }
- | mir::TerminatorKind::Drop { target, location: _, unwind: None }
- | mir::TerminatorKind::DropAndReplace { target, value: _, location: _, unwind: None } =>
- {
- self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
- }
- mir::TerminatorKind::Yield { resume: target, drop: Some(drop), .. } => {
- self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
- self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
- }
- mir::TerminatorKind::Assert { target, cleanup: Some(unwind), .. }
- | mir::TerminatorKind::Drop { target, location: _, unwind: Some(unwind) }
- | mir::TerminatorKind::DropAndReplace {
- target,
- value: _,
- location: _,
- unwind: Some(unwind),
- } => {
- self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
- if !self.dead_unwinds.contains(bb) {
- self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
- }
- }
- mir::TerminatorKind::SwitchInt { ref targets, .. } => {
- for target in targets {
- self.propagate_bits_into_entry_set_for(in_out, *target, dirty_list);
- }
- }
- mir::TerminatorKind::Call { cleanup, ref destination, .. } => {
- if let Some(unwind) = cleanup {
- if !self.dead_unwinds.contains(bb) {
- self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
- }
- }
- if let Some((ref dest_place, dest_bb)) = *destination {
- // N.B.: This must be done *last*, after all other
- // propagation, as documented in comment above.
- self.flow_state.operator.propagate_call_return(in_out, bb, dest_bb, dest_place);
- self.propagate_bits_into_entry_set_for(in_out, dest_bb, dirty_list);
- }
- }
- mir::TerminatorKind::FalseEdges { real_target, imaginary_target } => {
- self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
- self.propagate_bits_into_entry_set_for(in_out, imaginary_target, dirty_list);
- }
- mir::TerminatorKind::FalseUnwind { real_target, unwind } => {
- self.propagate_bits_into_entry_set_for(in_out, real_target, dirty_list);
- if let Some(unwind) = unwind {
- if !self.dead_unwinds.contains(bb) {
- self.propagate_bits_into_entry_set_for(in_out, unwind, dirty_list);
- }
- }
- }
- }
- }
-
- fn propagate_bits_into_entry_set_for(
- &mut self,
- in_out: &BitSet<D::Idx>,
- bb: mir::BasicBlock,
- dirty_queue: &mut WorkQueue<mir::BasicBlock>,
- ) {
- let entry_set = self.flow_state.sets.entry_set_mut_for(bb.index());
- let set_changed = self.flow_state.operator.join(entry_set, &in_out);
- if set_changed {
- dirty_queue.insert(bb);
- }
- }
-}
use std::marker::PhantomData;
use super::{qualifs, Item, Qualif};
-use crate::dataflow::{self as old_dataflow, generic as dataflow};
+use crate::dataflow;
/// A `Visitor` that propagates qualifs between locals. This defines the transfer function of
/// `FlowSensitiveAnalysis`.
}
}
-impl<Q> old_dataflow::BottomValue for FlowSensitiveAnalysis<'_, '_, '_, Q> {
+impl<Q> dataflow::BottomValue for FlowSensitiveAnalysis<'_, '_, '_, Q> {
const BOTTOM_VALUE: bool = false;
}
use super::resolver::FlowSensitiveAnalysis;
use super::{is_lang_panic_fn, ConstKind, Item, Qualif};
use crate::const_eval::{is_const_fn, is_unstable_const_fn};
-use crate::dataflow::generic::{self as dataflow, Analysis};
use crate::dataflow::MaybeMutBorrowedLocals;
+use crate::dataflow::{self, Analysis};
// We are using `MaybeMutBorrowedLocals` as a proxy for whether an item may have been mutated
// through a pointer prior to the given point. This is okay even though `MaybeMutBorrowedLocals`
use crate::dataflow;
-use crate::dataflow::generic::{Analysis, ResultsCursor};
use crate::dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
use crate::dataflow::on_lookup_result_bits;
use crate::dataflow::MoveDataParamEnv;
use crate::dataflow::{on_all_children_bits, on_all_drop_children_bits};
+use crate::dataflow::{Analysis, ResultsCursor};
use crate::dataflow::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
use crate::transform::{MirPass, MirSource};
use crate::util::elaborate_drops::{elaborate_drop, DropFlagState, Unwind};
//! For generators with state 1 (returned) and state 2 (poisoned) it does nothing.
//! Otherwise it drops all the values in scope at the last suspension point.
-use crate::dataflow::generic::{self as dataflow, Analysis};
+use crate::dataflow::{self, Analysis};
use crate::dataflow::{MaybeBorrowedLocals, MaybeRequiresStorage, MaybeStorageLive};
use crate::transform::no_landing_pads::no_landing_pads;
use crate::transform::simplify;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::BitSet;
-use crate::dataflow::generic::{Analysis, Results, ResultsCursor};
use crate::dataflow::move_paths::{HasMoveData, MoveData};
use crate::dataflow::move_paths::{LookupResult, MovePathIndex};
use crate::dataflow::MaybeMutBorrowedLocals;
use crate::dataflow::MoveDataParamEnv;
+use crate::dataflow::{Analysis, Results, ResultsCursor};
use crate::dataflow::{
DefinitelyInitializedPlaces, MaybeInitializedPlaces, MaybeUninitializedPlaces,
};