From: Dylan MacKenzie Date: Mon, 11 Nov 2019 19:48:17 +0000 (-0800) Subject: Implement new dataflow framework and cursor X-Git-Url: https://git.lizzy.rs/?a=commitdiff_plain;h=07c51f605a021f1416a23e0cd76afa2156d5526c;p=rust.git Implement new dataflow framework and cursor --- diff --git a/src/librustc_mir/dataflow/generic/cursor.rs b/src/librustc_mir/dataflow/generic/cursor.rs new file mode 100644 index 00000000000..f819b0fb5d6 --- /dev/null +++ b/src/librustc_mir/dataflow/generic/cursor.rs @@ -0,0 +1,265 @@ +//! 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, + + 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>, +{ + /// 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 { + &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, + } + } +} diff --git a/src/librustc_mir/dataflow/generic/engine.rs b/src/librustc_mir/dataflow/generic/engine.rs new file mode 100644 index 00000000000..33221770066 --- /dev/null +++ b/src/librustc_mir/dataflow/generic/engine.rs @@ -0,0 +1,421 @@ +//! 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>, + entry_sets: IndexVec>, + analysis: A, + + /// Cached, cumulative transfer functions for each block. + trans_for_block: Option>>, +} + +impl 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 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>>, + ) -> 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) -> 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 = + 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, + 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, + (bb, bb_data): (BasicBlock, &'a mir::BasicBlockData<'tcx>), + dirty_list: &mut WorkQueue, + ) { + 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, + bb: BasicBlock, + dirty_queue: &mut WorkQueue, + ) { + 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( + tcx: TyCtxt<'tcx>, + def_id: DefId, + body: &mir::Body<'tcx>, + results: &Results<'tcx, A>, + block_transfer_functions: Option>>, +) -> 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> = 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, + formatter: Option, +} + +impl RustcMirAttrs { + fn parse(tcx: TyCtxt<'tcx>, def_id: DefId) -> Result { + 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( + field: &mut Option, + tcx: TyCtxt<'tcx>, + attr: &ast::NestedMetaItem, + mapper: impl FnOnce(Symbol) -> Result, + ) -> 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 { + 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) + } +} diff --git a/src/librustc_mir/dataflow/generic/mod.rs b/src/librustc_mir/dataflow/generic/mod.rs new file mode 100644 index 00000000000..1b628f2c7eb --- /dev/null +++ b/src/librustc_mir/dataflow/generic/mod.rs @@ -0,0 +1,309 @@ +//! 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>, +} + +impl 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 { + &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); + + /// 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, + 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, + _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, + 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, + _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, + 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, + statement: &mir::Statement<'tcx>, + location: Location, + ); + + /// See `Analysis::apply_before_statement_effect`. + fn before_statement_effect( + &self, + _trans: &mut impl GenKill, + _statement: &mir::Statement<'tcx>, + _location: Location, + ) { + } + + /// See `Analysis::apply_terminator_effect`. + fn terminator_effect( + &self, + trans: &mut impl GenKill, + terminator: &mir::Terminator<'tcx>, + location: Location, + ); + + /// See `Analysis::apply_before_terminator_effect`. + fn before_terminator_effect( + &self, + _trans: &mut impl GenKill, + _terminator: &mir::Terminator<'tcx>, + _location: Location, + ) { + } + + /// See `Analysis::apply_call_return_effect`. + fn call_return_effect( + &self, + trans: &mut impl GenKill, + block: BasicBlock, + func: &mir::Operand<'tcx>, + args: &[mir::Operand<'tcx>], + return_place: &mir::Place<'tcx>, + ); +} + +impl Analysis<'tcx> for A +where + A: GenKillAnalysis<'tcx>, +{ + fn apply_statement_effect( + &self, + state: &mut BitSet, + statement: &mir::Statement<'tcx>, + location: Location, + ) { + self.statement_effect(state, statement, location); + } + + fn apply_before_statement_effect( + &self, + state: &mut BitSet, + statement: &mir::Statement<'tcx>, + location: Location, + ) { + self.before_statement_effect(state, statement, location); + } + + fn apply_terminator_effect( + &self, + state: &mut BitSet, + terminator: &mir::Terminator<'tcx>, + location: Location, + ) { + self.terminator_effect(state, terminator, location); + } + + fn apply_before_terminator_effect( + &self, + state: &mut BitSet, + terminator: &mir::Terminator<'tcx>, + location: Location, + ) { + self.before_terminator_effect(state, terminator, location); + } + + fn apply_call_return_effect( + &self, + state: &mut BitSet, + 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: 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) { + 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) { + for elem in elems { + self.kill(elem); + } + } +} + +/// Stores a transfer function for a gen/kill problem. +#[derive(Clone)] +pub struct GenKillSet { + gen: HybridBitSet, + kill: HybridBitSet, +} + +impl GenKillSet { + /// 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) { + state.union(&self.gen); + state.subtract(&self.kill); + } +} + +impl GenKill for GenKillSet { + 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 GenKill for BitSet { + fn gen(&mut self, elem: T) { + self.insert(elem); + } + + fn kill(&mut self, elem: T) { + self.remove(elem); + } +}