--- /dev/null
+//! Random access inspection of the results of a dataflow analysis.
+
+use std::borrow::Borrow;
+
+use rustc::mir::{self, BasicBlock, Location};
+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` terminator whose call return
+ /// effect has been applied to `state`.
+ ///
+ /// This flag helps to ensure that multiple calls to `seek_after_assume_call_returns` 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.
+ call_return_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(),
+ call_return_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
+ }
+
+ /// 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.call_return_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_call_returns`.
+ 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_call_returns` 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.call_return_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` terminator, any call return effect for that terminator will
+ /// be observed. Use `seek_after` if you do **not** wish to observe the call return effect.
+ pub fn seek_after_assume_call_returns(&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 {
+ return;
+ }
+
+ let terminator = self.body.basic_blocks()[target.block].terminator();
+ if let mir::TerminatorKind::Call {
+ destination: Some((return_place, _)), func, args, ..
+ } = &terminator.kind
+ {
+ if !self.call_return_effect_applied {
+ self.call_return_effect_applied = true;
+ self.results.borrow().analysis.apply_call_return_effect(
+ &mut self.state,
+ target.block,
+ func,
+ args,
+ return_place,
+ );
+ }
+ }
+ }
+
+ 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., `call_return_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::TyCtxt;
+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 syntax::ast;
+
+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 {
+ 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());
+
+ // Compute cumulative block transfer functions.
+ //
+ // FIXME: we may want to skip this if the MIR is acyclic, since we will never access a
+ // block transfer function more than once.
+
+ 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);
+ }
+
+ if let Some(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 == true {
+ 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,
+ }
+ }
+
+ pub fn dead_unwinds(mut self, dead_unwinds: &'a BitSet<BasicBlock>) -> Self {
+ self.dead_unwinds = Some(dead_unwinds);
+ self
+ }
+
+ 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, .. }
+ | Yield { resume: target, drop: 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: 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);
+ }
+
+ 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, .. } => {
+ for target in targets {
+ 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);
+ }
+ }
+}
+
+// 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(&graphviz, &mut buf)?;
+ 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
+ .into_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 framework for expressing dataflow problems.
+
+use std::io;
+
+use rustc::mir::{self, BasicBlock, Location};
+use rustc_index::bit_set::{BitSet, HybridBitSet};
+use rustc_index::vec::{Idx, IndexVec};
+
+use crate::dataflow::BottomValue;
+
+mod cursor;
+mod engine;
+mod graphviz;
+
+pub use self::cursor::{ResultsCursor, ResultsRefCursor};
+pub use self::engine::Engine;
+
+/// 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>,
+{
+ pub fn into_cursor(self, body: &'mir mir::Body<'tcx>) -> ResultsCursor<'mir, 'tcx, A> {
+ ResultsCursor::new(body, self)
+ }
+
+ pub fn on_block_entry(&self, block: BasicBlock) -> &BitSet<A::Idx> {
+ &self.entry_sets[block]
+ }
+}
+
+/// 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)
+ }
+}
+
+/// Define 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>,
+ );
+}
+
+/// Define 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>,
+ );
+}
+
+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);
+ }
+}
+
+/// The legal operations for a transfer function in a gen/kill problem.
+pub trait GenKill<T>: Sized {
+ /// Inserts `elem` into the `gen` set, removing it the `kill` set if present.
+ fn gen(&mut self, elem: T);
+
+ /// Inserts `elem` into the `kill` set, removing it the `gen` set if present.
+ fn kill(&mut self, elem: T);
+
+ /// Inserts the given elements into the `gen` set, removing them from the `kill` set if present.
+ fn gen_all(&mut self, elems: impl IntoIterator<Item = T>) {
+ for elem in elems {
+ self.gen(elem);
+ }
+ }
+
+ /// Inserts the given elements into the `kill` set, removing them from the `gen` set if present.
+ 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.
+#[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 bitset.
+ 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);
+ }
+}