1
Fork 0

mv compiler to compiler/

This commit is contained in:
mark 2020-08-27 22:58:48 -05:00 committed by Vadim Petrochenkov
parent db534b3ac2
commit 9e5f7d5631
1686 changed files with 941 additions and 1051 deletions

View file

@ -0,0 +1,270 @@
use crate::util::elaborate_drops::DropFlagState;
use rustc_middle::mir::{self, Body, Location};
use rustc_middle::ty::{self, TyCtxt};
use rustc_target::abi::VariantIdx;
use super::indexes::MovePathIndex;
use super::move_paths::{InitKind, LookupResult, MoveData};
use super::MoveDataParamEnv;
pub fn move_path_children_matching<'tcx, F>(
move_data: &MoveData<'tcx>,
path: MovePathIndex,
mut cond: F,
) -> Option<MovePathIndex>
where
F: FnMut(mir::PlaceElem<'tcx>) -> bool,
{
let mut next_child = move_data.move_paths[path].first_child;
while let Some(child_index) = next_child {
let move_path_children = &move_data.move_paths[child_index];
if let Some(&elem) = move_path_children.place.projection.last() {
if cond(elem) {
return Some(child_index);
}
}
next_child = move_path_children.next_sibling;
}
None
}
/// When enumerating the child fragments of a path, don't recurse into
/// paths (1.) past arrays, slices, and pointers, nor (2.) into a type
/// that implements `Drop`.
///
/// Places behind references or arrays are not tracked by elaboration
/// and are always assumed to be initialized when accessible. As
/// references and indexes can be reseated, trying to track them can
/// only lead to trouble.
///
/// Places behind ADT's with a Drop impl are not tracked by
/// elaboration since they can never have a drop-flag state that
/// differs from that of the parent with the Drop impl.
///
/// In both cases, the contents can only be accessed if and only if
/// their parents are initialized. This implies for example that there
/// is no need to maintain separate drop flags to track such state.
//
// FIXME: we have to do something for moving slice patterns.
fn place_contents_drop_state_cannot_differ<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
place: mir::Place<'tcx>,
) -> bool {
let ty = place.ty(body, tcx).ty;
match ty.kind {
ty::Array(..) => {
debug!(
"place_contents_drop_state_cannot_differ place: {:?} ty: {:?} => false",
place, ty
);
false
}
ty::Slice(..) | ty::Ref(..) | ty::RawPtr(..) => {
debug!(
"place_contents_drop_state_cannot_differ place: {:?} ty: {:?} refd => true",
place, ty
);
true
}
ty::Adt(def, _) if (def.has_dtor(tcx) && !def.is_box()) || def.is_union() => {
debug!(
"place_contents_drop_state_cannot_differ place: {:?} ty: {:?} Drop => true",
place, ty
);
true
}
_ => false,
}
}
pub(crate) fn on_lookup_result_bits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
move_data: &MoveData<'tcx>,
lookup_result: LookupResult,
each_child: F,
) where
F: FnMut(MovePathIndex),
{
match lookup_result {
LookupResult::Parent(..) => {
// access to untracked value - do not touch children
}
LookupResult::Exact(e) => on_all_children_bits(tcx, body, move_data, e, each_child),
}
}
pub(crate) fn on_all_children_bits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
move_data: &MoveData<'tcx>,
move_path_index: MovePathIndex,
mut each_child: F,
) where
F: FnMut(MovePathIndex),
{
fn is_terminal_path<'tcx>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
move_data: &MoveData<'tcx>,
path: MovePathIndex,
) -> bool {
place_contents_drop_state_cannot_differ(tcx, body, move_data.move_paths[path].place)
}
fn on_all_children_bits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
move_data: &MoveData<'tcx>,
move_path_index: MovePathIndex,
each_child: &mut F,
) where
F: FnMut(MovePathIndex),
{
each_child(move_path_index);
if is_terminal_path(tcx, body, move_data, move_path_index) {
return;
}
let mut next_child_index = move_data.move_paths[move_path_index].first_child;
while let Some(child_index) = next_child_index {
on_all_children_bits(tcx, body, move_data, child_index, each_child);
next_child_index = move_data.move_paths[child_index].next_sibling;
}
}
on_all_children_bits(tcx, body, move_data, move_path_index, &mut each_child);
}
pub(crate) fn on_all_drop_children_bits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
ctxt: &MoveDataParamEnv<'tcx>,
path: MovePathIndex,
mut each_child: F,
) where
F: FnMut(MovePathIndex),
{
on_all_children_bits(tcx, body, &ctxt.move_data, path, |child| {
let place = &ctxt.move_data.move_paths[path].place;
let ty = place.ty(body, tcx).ty;
debug!("on_all_drop_children_bits({:?}, {:?} : {:?})", path, place, ty);
let erased_ty = tcx.erase_regions(&ty);
if erased_ty.needs_drop(tcx, ctxt.param_env) {
each_child(child);
} else {
debug!("on_all_drop_children_bits - skipping")
}
})
}
pub(crate) fn drop_flag_effects_for_function_entry<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
ctxt: &MoveDataParamEnv<'tcx>,
mut callback: F,
) where
F: FnMut(MovePathIndex, DropFlagState),
{
let move_data = &ctxt.move_data;
for arg in body.args_iter() {
let place = mir::Place::from(arg);
let lookup_result = move_data.rev_lookup.find(place.as_ref());
on_lookup_result_bits(tcx, body, move_data, lookup_result, |mpi| {
callback(mpi, DropFlagState::Present)
});
}
}
pub(crate) fn drop_flag_effects_for_location<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
ctxt: &MoveDataParamEnv<'tcx>,
loc: Location,
mut callback: F,
) where
F: FnMut(MovePathIndex, DropFlagState),
{
let move_data = &ctxt.move_data;
debug!("drop_flag_effects_for_location({:?})", loc);
// first, move out of the RHS
for mi in &move_data.loc_map[loc] {
let path = mi.move_path_index(move_data);
debug!("moving out of path {:?}", move_data.move_paths[path]);
on_all_children_bits(tcx, body, move_data, path, |mpi| callback(mpi, DropFlagState::Absent))
}
debug!("drop_flag_effects: assignment for location({:?})", loc);
for_location_inits(tcx, body, move_data, loc, |mpi| callback(mpi, DropFlagState::Present));
}
pub(crate) fn for_location_inits<'tcx, F>(
tcx: TyCtxt<'tcx>,
body: &Body<'tcx>,
move_data: &MoveData<'tcx>,
loc: Location,
mut callback: F,
) where
F: FnMut(MovePathIndex),
{
for ii in &move_data.init_loc_map[loc] {
let init = move_data.inits[*ii];
match init.kind {
InitKind::Deep => {
let path = init.path;
on_all_children_bits(tcx, body, move_data, path, &mut callback)
}
InitKind::Shallow => {
let mpi = init.path;
callback(mpi);
}
InitKind::NonPanicPathOnly => (),
}
}
}
/// Calls `handle_inactive_variant` for each descendant move path of `enum_place` that contains a
/// `Downcast` to a variant besides the `active_variant`.
///
/// NOTE: If there are no move paths corresponding to an inactive variant,
/// `handle_inactive_variant` will not be called for that variant.
pub(crate) fn on_all_inactive_variants<'tcx>(
tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
move_data: &MoveData<'tcx>,
enum_place: mir::Place<'tcx>,
active_variant: VariantIdx,
mut handle_inactive_variant: impl FnMut(MovePathIndex),
) {
let enum_mpi = match move_data.rev_lookup.find(enum_place.as_ref()) {
LookupResult::Exact(mpi) => mpi,
LookupResult::Parent(_) => return,
};
let enum_path = &move_data.move_paths[enum_mpi];
for (variant_mpi, variant_path) in enum_path.children(&move_data.move_paths) {
// Because of the way we build the `MoveData` tree, each child should have exactly one more
// projection than `enum_place`. This additional projection must be a downcast since the
// base is an enum.
let (downcast, base_proj) = variant_path.place.projection.split_last().unwrap();
assert_eq!(enum_place.projection.len(), base_proj.len());
let variant_idx = match *downcast {
mir::ProjectionElem::Downcast(_, idx) => idx,
_ => unreachable!(),
};
if variant_idx != active_variant {
on_all_children_bits(tcx, body, move_data, variant_mpi, |mpi| {
handle_inactive_variant(mpi)
});
}
}
}

View file

@ -0,0 +1,221 @@
//! Random access inspection of the results of a dataflow analysis.
use std::borrow::Borrow;
use std::cmp::Ordering;
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, Effect, EffectIndex, 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
/// the same order as the `DIRECTION` of the analysis. 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,
/// Indicates that `state` has been modified with a custom effect.
///
/// When this flag is set, we need to reset to an entry set before doing a seek.
state_needs_reset: bool,
#[cfg(debug_assertions)]
reachable_blocks: BitSet<BasicBlock>,
}
impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
where
A: Analysis<'tcx>,
R: Borrow<Results<'tcx, A>>,
{
/// Returns a new cursor that can inspect `results`.
pub fn new(body: &'mir mir::Body<'tcx>, results: R) -> Self {
let bits_per_block = results.borrow().entry_set_for_block(mir::START_BLOCK).domain_size();
ResultsCursor {
body,
results,
// Initialize to an empty `BitSet` and set `state_needs_reset` to tell the cursor that
// it needs to reset to block entry before the first seek. The cursor position is
// immaterial.
state_needs_reset: true,
state: BitSet::new_empty(bits_per_block),
pos: CursorPosition::block_entry(mir::START_BLOCK),
#[cfg(debug_assertions)]
reachable_blocks: mir::traversal::reachable_as_bitset(body),
}
}
pub fn body(&self) -> &'mir mir::Body<'tcx> {
self.body
}
/// 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 hold the entry set for the given basic block.
///
/// For forward dataflow analyses, this is the dataflow state prior to the first statement.
///
/// For backward dataflow analyses, this is the dataflow state after the terminator.
pub(super) fn seek_to_block_entry(&mut self, block: BasicBlock) {
#[cfg(debug_assertions)]
assert!(self.reachable_blocks.contains(block));
self.state.overwrite(&self.results.borrow().entry_set_for_block(block));
self.pos = CursorPosition::block_entry(block);
self.state_needs_reset = false;
}
/// Resets the cursor to hold the state prior to the first statement in a basic block.
///
/// For forward analyses, this is the entry set for the given block.
///
/// For backward analyses, this is the state that will be propagated to its
/// predecessors (ignoring edge-specific effects).
pub fn seek_to_block_start(&mut self, block: BasicBlock) {
if A::Direction::is_forward() {
self.seek_to_block_entry(block)
} else {
self.seek_after(Location { block, statement_index: 0 }, Effect::Primary)
}
}
/// Resets the cursor to hold the state after the terminator in a basic block.
///
/// For backward analyses, this is the entry set for the given block.
///
/// For forward analyses, this is the state that will be propagated to its
/// successors (ignoring edge-specific effects).
pub fn seek_to_block_end(&mut self, block: BasicBlock) {
if A::Direction::is_backward() {
self.seek_to_block_entry(block)
} else {
self.seek_after(self.body.terminator_loc(block), Effect::Primary)
}
}
/// Advances the cursor to hold the dataflow state at `target` before its "primary" effect is
/// applied.
///
/// The "before" effect at the target location *will be* applied.
pub fn seek_before_primary_effect(&mut self, target: Location) {
self.seek_after(target, Effect::Before)
}
/// Advances the cursor to hold the dataflow state at `target` after its "primary" effect is
/// applied.
///
/// The "before" effect at the target location will be applied as well.
pub fn seek_after_primary_effect(&mut self, target: Location) {
self.seek_after(target, Effect::Primary)
}
fn seek_after(&mut self, target: Location, effect: Effect) {
assert!(target <= self.body.terminator_loc(target.block));
// Reset to the entry of the target block if any of the following are true:
// - A custom effect has been applied to the cursor state.
// - We are in a different block than the target.
// - We are in the same block but have advanced past the target effect.
if self.state_needs_reset || self.pos.block != target.block {
self.seek_to_block_entry(target.block);
} else if let Some(curr_effect) = self.pos.curr_effect_index {
let mut ord = curr_effect.statement_index.cmp(&target.statement_index);
if A::Direction::is_backward() {
ord = ord.reverse()
}
match ord.then_with(|| curr_effect.effect.cmp(&effect)) {
Ordering::Equal => return,
Ordering::Greater => self.seek_to_block_entry(target.block),
Ordering::Less => {}
}
}
// 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);
let block_data = &self.body[target.block];
let next_effect = if A::Direction::is_forward() {
#[rustfmt::skip]
self.pos.curr_effect_index.map_or_else(
|| Effect::Before.at_index(0),
EffectIndex::next_in_forward_order,
)
} else {
self.pos.curr_effect_index.map_or_else(
|| Effect::Before.at_index(block_data.statements.len()),
EffectIndex::next_in_backward_order,
)
};
let analysis = &self.results.borrow().analysis;
let target_effect_index = effect.at_index(target.statement_index);
A::Direction::apply_effects_in_range(
analysis,
&mut self.state,
target.block,
block_data,
next_effect..=target_effect_index,
);
self.pos =
CursorPosition { block: target.block, curr_effect_index: Some(target_effect_index) };
}
/// Applies `f` to the cursor's internal state.
///
/// This can be used, e.g., to apply the call return effect directly to the cursor without
/// creating an extra copy of the dataflow state.
pub fn apply_custom_effect(&mut self, f: impl FnOnce(&A, &mut BitSet<A::Idx>)) {
f(&self.results.borrow().analysis, &mut self.state);
self.state_needs_reset = true;
}
}
#[derive(Clone, Copy, Debug)]
struct CursorPosition {
block: BasicBlock,
curr_effect_index: Option<EffectIndex>,
}
impl CursorPosition {
fn block_entry(block: BasicBlock) -> CursorPosition {
CursorPosition { block, curr_effect_index: None }
}
}

View file

@ -0,0 +1,576 @@
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{self, BasicBlock, Location};
use rustc_middle::ty::{self, TyCtxt};
use std::ops::RangeInclusive;
use super::visitor::{ResultsVisitable, ResultsVisitor};
use super::{Analysis, Effect, EffectIndex, GenKillAnalysis, GenKillSet};
pub trait Direction {
fn is_forward() -> bool;
fn is_backward() -> bool {
!Self::is_forward()
}
/// Applies all effects between the given `EffectIndex`s.
///
/// `effects.start()` must precede or equal `effects.end()` in this direction.
fn apply_effects_in_range<A>(
analysis: &A,
state: &mut BitSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>,
) where
A: Analysis<'tcx>;
fn apply_effects_in_block<A>(
analysis: &A,
state: &mut BitSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: Analysis<'tcx>;
fn gen_kill_effects_in_block<A>(
analysis: &A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: GenKillAnalysis<'tcx>;
fn visit_results_in_block<F, R>(
state: &mut F,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,
results: &R,
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>,
) where
R: ResultsVisitable<'tcx, FlowState = F>;
fn join_state_into_successors_of<A>(
analysis: &A,
tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>,
exit_state: &mut BitSet<A::Idx>,
block: (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
) where
A: Analysis<'tcx>;
}
/// Dataflow that runs from the exit of a block (the terminator), to its entry (the first statement).
pub struct Backward;
impl Direction for Backward {
fn is_forward() -> bool {
false
}
fn apply_effects_in_block<A>(
analysis: &A,
state: &mut BitSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: Analysis<'tcx>,
{
let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() };
analysis.apply_before_terminator_effect(state, terminator, location);
analysis.apply_terminator_effect(state, terminator, location);
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
let location = Location { block, statement_index };
analysis.apply_before_statement_effect(state, statement, location);
analysis.apply_statement_effect(state, statement, location);
}
}
fn gen_kill_effects_in_block<A>(
analysis: &A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: GenKillAnalysis<'tcx>,
{
let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() };
analysis.before_terminator_effect(trans, terminator, location);
analysis.terminator_effect(trans, terminator, location);
for (statement_index, statement) in block_data.statements.iter().enumerate().rev() {
let location = Location { block, statement_index };
analysis.before_statement_effect(trans, statement, location);
analysis.statement_effect(trans, statement, location);
}
}
fn apply_effects_in_range<A>(
analysis: &A,
state: &mut BitSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>,
) where
A: Analysis<'tcx>,
{
let (from, to) = (*effects.start(), *effects.end());
let terminator_index = block_data.statements.len();
assert!(from.statement_index <= terminator_index);
assert!(!to.precedes_in_backward_order(from));
// Handle the statement (or terminator) at `from`.
let next_effect = match from.effect {
// If we need to apply the terminator effect in all or in part, do so now.
_ if from.statement_index == terminator_index => {
let location = Location { block, statement_index: from.statement_index };
let terminator = block_data.terminator();
if from.effect == Effect::Before {
analysis.apply_before_terminator_effect(state, terminator, location);
if to == Effect::Before.at_index(terminator_index) {
return;
}
}
analysis.apply_terminator_effect(state, terminator, location);
if to == Effect::Primary.at_index(terminator_index) {
return;
}
// If `from.statement_index` is `0`, we will have hit one of the earlier comparisons
// with `to`.
from.statement_index - 1
}
Effect::Primary => {
let location = Location { block, statement_index: from.statement_index };
let statement = &block_data.statements[from.statement_index];
analysis.apply_statement_effect(state, statement, location);
if to == Effect::Primary.at_index(from.statement_index) {
return;
}
from.statement_index - 1
}
Effect::Before => from.statement_index,
};
// Handle all statements between `first_unapplied_idx` and `to.statement_index`.
for statement_index in (to.statement_index..next_effect).rev().map(|i| i + 1) {
let location = Location { block, statement_index };
let statement = &block_data.statements[statement_index];
analysis.apply_before_statement_effect(state, statement, location);
analysis.apply_statement_effect(state, statement, location);
}
// Handle the statement at `to`.
let location = Location { block, statement_index: to.statement_index };
let statement = &block_data.statements[to.statement_index];
analysis.apply_before_statement_effect(state, statement, location);
if to.effect == Effect::Before {
return;
}
analysis.apply_statement_effect(state, statement, location);
}
fn visit_results_in_block<F, R>(
state: &mut F,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,
results: &R,
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>,
) where
R: ResultsVisitable<'tcx, FlowState = F>,
{
results.reset_to_block_entry(state, block);
vis.visit_block_end(&state, block_data, block);
// Terminator
let loc = Location { block, statement_index: block_data.statements.len() };
let term = block_data.terminator();
results.reconstruct_before_terminator_effect(state, term, loc);
vis.visit_terminator_before_primary_effect(state, term, loc);
results.reconstruct_terminator_effect(state, term, loc);
vis.visit_terminator_after_primary_effect(state, term, loc);
for (statement_index, stmt) in block_data.statements.iter().enumerate().rev() {
let loc = Location { block, statement_index };
results.reconstruct_before_statement_effect(state, stmt, loc);
vis.visit_statement_before_primary_effect(state, stmt, loc);
results.reconstruct_statement_effect(state, stmt, loc);
vis.visit_statement_after_primary_effect(state, stmt, loc);
}
vis.visit_block_start(state, block_data, block);
}
fn join_state_into_successors_of<A>(
analysis: &A,
_tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>,
exit_state: &mut BitSet<A::Idx>,
(bb, _bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
) where
A: Analysis<'tcx>,
{
for pred in body.predecessors()[bb].iter().copied() {
match body[pred].terminator().kind {
// Apply terminator-specific edge effects.
//
// FIXME(ecstaticmorse): Avoid cloning the exit state unconditionally.
mir::TerminatorKind::Call {
destination: Some((return_place, dest)),
ref func,
ref args,
..
} if dest == bb => {
let mut tmp = exit_state.clone();
analysis.apply_call_return_effect(&mut tmp, pred, func, args, return_place);
propagate(pred, &tmp);
}
mir::TerminatorKind::Yield { resume, resume_arg, .. } if resume == bb => {
let mut tmp = exit_state.clone();
analysis.apply_yield_resume_effect(&mut tmp, resume, resume_arg);
propagate(pred, &tmp);
}
// Ignore dead unwinds.
mir::TerminatorKind::Call { cleanup: Some(unwind), .. }
| mir::TerminatorKind::Assert { cleanup: Some(unwind), .. }
| mir::TerminatorKind::Drop { unwind: Some(unwind), .. }
| mir::TerminatorKind::DropAndReplace { unwind: Some(unwind), .. }
| mir::TerminatorKind::FalseUnwind { unwind: Some(unwind), .. }
if unwind == bb =>
{
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
propagate(pred, exit_state);
}
}
_ => propagate(pred, exit_state),
}
}
}
}
/// Dataflow that runs from the entry of a block (the first statement), to its exit (terminator).
pub struct Forward;
impl Direction for Forward {
fn is_forward() -> bool {
true
}
fn apply_effects_in_block<A>(
analysis: &A,
state: &mut BitSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: Analysis<'tcx>,
{
for (statement_index, statement) in block_data.statements.iter().enumerate() {
let location = Location { block, statement_index };
analysis.apply_before_statement_effect(state, statement, location);
analysis.apply_statement_effect(state, statement, location);
}
let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() };
analysis.apply_before_terminator_effect(state, terminator, location);
analysis.apply_terminator_effect(state, terminator, location);
}
fn gen_kill_effects_in_block<A>(
analysis: &A,
trans: &mut GenKillSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
) where
A: GenKillAnalysis<'tcx>,
{
for (statement_index, statement) in block_data.statements.iter().enumerate() {
let location = Location { block, statement_index };
analysis.before_statement_effect(trans, statement, location);
analysis.statement_effect(trans, statement, location);
}
let terminator = block_data.terminator();
let location = Location { block, statement_index: block_data.statements.len() };
analysis.before_terminator_effect(trans, terminator, location);
analysis.terminator_effect(trans, terminator, location);
}
fn apply_effects_in_range<A>(
analysis: &A,
state: &mut BitSet<A::Idx>,
block: BasicBlock,
block_data: &mir::BasicBlockData<'tcx>,
effects: RangeInclusive<EffectIndex>,
) where
A: Analysis<'tcx>,
{
let (from, to) = (*effects.start(), *effects.end());
let terminator_index = block_data.statements.len();
assert!(to.statement_index <= terminator_index);
assert!(!to.precedes_in_forward_order(from));
// If we have applied the before affect of the statement or terminator at `from` but not its
// after effect, do so now and start the loop below from the next statement.
let first_unapplied_index = match from.effect {
Effect::Before => from.statement_index,
Effect::Primary if from.statement_index == terminator_index => {
debug_assert_eq!(from, to);
let location = Location { block, statement_index: terminator_index };
let terminator = block_data.terminator();
analysis.apply_terminator_effect(state, terminator, location);
return;
}
Effect::Primary => {
let location = Location { block, statement_index: from.statement_index };
let statement = &block_data.statements[from.statement_index];
analysis.apply_statement_effect(state, statement, location);
// If we only needed to apply the after effect of the statement at `idx`, we are done.
if from == to {
return;
}
from.statement_index + 1
}
};
// Handle all statements between `from` and `to` whose effects must be applied in full.
for statement_index in first_unapplied_index..to.statement_index {
let location = Location { block, statement_index };
let statement = &block_data.statements[statement_index];
analysis.apply_before_statement_effect(state, statement, location);
analysis.apply_statement_effect(state, statement, location);
}
// Handle the statement or terminator at `to`.
let location = Location { block, statement_index: to.statement_index };
if to.statement_index == terminator_index {
let terminator = block_data.terminator();
analysis.apply_before_terminator_effect(state, terminator, location);
if to.effect == Effect::Primary {
analysis.apply_terminator_effect(state, terminator, location);
}
} else {
let statement = &block_data.statements[to.statement_index];
analysis.apply_before_statement_effect(state, statement, location);
if to.effect == Effect::Primary {
analysis.apply_statement_effect(state, statement, location);
}
}
}
fn visit_results_in_block<F, R>(
state: &mut F,
block: BasicBlock,
block_data: &'mir mir::BasicBlockData<'tcx>,
results: &R,
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>,
) where
R: ResultsVisitable<'tcx, FlowState = F>,
{
results.reset_to_block_entry(state, block);
vis.visit_block_start(state, block_data, block);
for (statement_index, stmt) in block_data.statements.iter().enumerate() {
let loc = Location { block, statement_index };
results.reconstruct_before_statement_effect(state, stmt, loc);
vis.visit_statement_before_primary_effect(state, stmt, loc);
results.reconstruct_statement_effect(state, stmt, loc);
vis.visit_statement_after_primary_effect(state, stmt, loc);
}
let loc = Location { block, statement_index: block_data.statements.len() };
let term = block_data.terminator();
results.reconstruct_before_terminator_effect(state, term, loc);
vis.visit_terminator_before_primary_effect(state, term, loc);
results.reconstruct_terminator_effect(state, term, loc);
vis.visit_terminator_after_primary_effect(state, term, loc);
vis.visit_block_end(state, block_data, block);
}
fn join_state_into_successors_of<A>(
analysis: &A,
tcx: TyCtxt<'tcx>,
body: &mir::Body<'tcx>,
dead_unwinds: Option<&BitSet<BasicBlock>>,
exit_state: &mut BitSet<A::Idx>,
(bb, bb_data): (BasicBlock, &'_ mir::BasicBlockData<'tcx>),
mut propagate: impl FnMut(BasicBlock, &BitSet<A::Idx>),
) where
A: Analysis<'tcx>,
{
use mir::TerminatorKind::*;
match bb_data.terminator().kind {
Return | Resume | Abort | GeneratorDrop | Unreachable => {}
Goto { target } => propagate(target, exit_state),
Assert { target, cleanup: unwind, expected: _, msg: _, cond: _ }
| Drop { target, unwind, place: _ }
| DropAndReplace { target, unwind, value: _, place: _ }
| FalseUnwind { real_target: target, unwind } => {
if let Some(unwind) = unwind {
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
propagate(unwind, exit_state);
}
}
propagate(target, exit_state);
}
FalseEdge { real_target, imaginary_target } => {
propagate(real_target, exit_state);
propagate(imaginary_target, exit_state);
}
Yield { resume: target, drop, resume_arg, value: _ } => {
if let Some(drop) = drop {
propagate(drop, exit_state);
}
analysis.apply_yield_resume_effect(exit_state, target, resume_arg);
propagate(target, exit_state);
}
Call { cleanup, destination, ref func, ref args, from_hir_call: _, fn_span: _ } => {
if let Some(unwind) = cleanup {
if dead_unwinds.map_or(true, |dead| !dead.contains(bb)) {
propagate(unwind, exit_state);
}
}
if let Some((dest_place, target)) = destination {
// N.B.: This must be done *last*, otherwise the unwind path will see the call
// return effect.
analysis.apply_call_return_effect(exit_state, bb, func, args, dest_place);
propagate(target, exit_state);
}
}
InlineAsm { template: _, operands: _, options: _, line_spans: _, destination } => {
if let Some(target) = destination {
propagate(target, exit_state);
}
}
SwitchInt { ref targets, ref values, ref discr, switch_ty: _ } => {
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)) => {
// 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(exit_state.domain_size());
let mut discriminants = enum_def.discriminants(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(exit_state);
analysis.apply_discriminant_switch_effect(
&mut tmp,
bb,
enum_place,
enum_def,
variant_idx,
);
propagate(target, &tmp);
}
// Move out of `tmp` so we don't accidentally use it below.
std::mem::drop(tmp);
// Propagate dataflow state along the "otherwise" edge.
let otherwise = targets.last().copied().unwrap();
propagate(otherwise, exit_state)
}
// Otherwise, it's just a normal `SwitchInt`, and every successor sees the same
// exit state.
None => {
for target in targets.iter().copied() {
propagate(target, exit_state);
}
}
}
}
}
}
}
/// 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::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,
}
}

View file

@ -0,0 +1,411 @@
//! A solver for dataflow problems.
use std::ffi::OsString;
use std::fs;
use std::path::PathBuf;
use rustc_ast as ast;
use rustc_data_structures::work_queue::WorkQueue;
use rustc_graphviz as dot;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::IndexVec;
use rustc_middle::mir::{self, traversal, BasicBlock};
use rustc_middle::ty::{self, TyCtxt};
use rustc_span::symbol::{sym, Symbol};
use super::graphviz;
use super::{
visit_results, Analysis, Direction, GenKillAnalysis, GenKillSet, ResultsCursor, ResultsVisitor,
};
use crate::util::pretty::dump_enabled;
/// A dataflow analysis that has converged to fixpoint.
pub struct Results<'tcx, A>
where
A: Analysis<'tcx>,
{
pub analysis: A,
pub(super) 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 dataflow state 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_reachable_with(
&self,
body: &'mir mir::Body<'tcx>,
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = BitSet<A::Idx>>,
) {
let blocks = mir::traversal::reachable(body);
visit_results(body, blocks.map(|(bb, _)| bb), 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)
}
}
/// 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];
A::Direction::gen_kill_effects_in_block(&analysis, trans, block, block_data);
}
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.clone(), body.basic_blocks());
analysis.initialize_start_block(body, &mut entry_sets[mir::START_BLOCK]);
if A::Direction::is_backward() && entry_sets[mir::START_BLOCK] != bottom_value_set {
bug!("`initialize_start_block` is not yet supported for backward dataflow analyses");
}
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(self) -> Results<'tcx, A> {
let Engine {
analysis,
bits_per_block,
body,
dead_unwinds,
def_id,
mut entry_sets,
tcx,
trans_for_block,
..
} = self;
let mut dirty_queue: WorkQueue<BasicBlock> =
WorkQueue::with_none(body.basic_blocks().len());
if A::Direction::is_forward() {
for (bb, _) in traversal::reverse_postorder(body) {
dirty_queue.insert(bb);
}
} else {
// Reverse post-order on the reverse CFG may generate a better iteration order for
// backward dataflow analyses, but probably not enough to matter.
for (bb, _) in traversal::postorder(body) {
dirty_queue.insert(bb);
}
}
let mut state = BitSet::new_empty(bits_per_block);
while let Some(bb) = dirty_queue.pop() {
let bb_data = &body[bb];
// Apply the block transfer function, using the cached one if it exists.
state.overwrite(&entry_sets[bb]);
match &trans_for_block {
Some(trans_for_block) => trans_for_block[bb].apply(&mut state),
None => A::Direction::apply_effects_in_block(&analysis, &mut state, bb, bb_data),
}
A::Direction::join_state_into_successors_of(
&analysis,
tcx,
body,
dead_unwinds,
&mut state,
(bb, bb_data),
|target: BasicBlock, state: &BitSet<A::Idx>| {
let set_changed = analysis.join(&mut entry_sets[target], state);
if set_changed {
dirty_queue.insert(target);
}
},
);
}
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
}
}
// 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 are reported in `RustcMirAttrs::parse`
Err(()) => return Ok(()),
};
let path = match attrs.output_path(A::NAME) {
Some(path) => path,
None if tcx.sess.opts.debugging_opts.dump_mir_dataflow
&& dump_enabled(tcx, A::NAME, def_id) =>
{
let mut path = PathBuf::from(&tcx.sess.opts.debugging_opts.dump_mir_dir);
let item_name = ty::print::with_forced_impl_filename_line(|| {
tcx.def_path(def_id).to_filename_friendly_no_crate()
});
path.push(format!("rustc.{}.{}.dot", item_name, A::NAME));
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(body, &results))
}
}
// Default to the `SimpleDiff` output style.
_ => Box::new(graphviz::SimpleDiff::new(body, &results)),
};
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])?;
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
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| tcx.sess.check_name(attr, 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.has_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.has_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)
}
}

View file

@ -0,0 +1,740 @@
//! A helpful diagram for debugging dataflow problems.
use std::cell::RefCell;
use std::{io, ops, str};
use rustc_graphviz as dot;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_index::vec::{Idx, IndexVec};
use rustc_middle::mir::{self, BasicBlock, Body, Location};
use super::{Analysis, Direction, 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 dataflow_successors(body: &Body<'tcx>, 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| dataflow_successors(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: State at start of block
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 start)")?;
// 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: State at end of block
// Write the full dataflow state immediately after the terminator if it differs from the
// state at block entry.
self.results.seek_to_block_end(block);
if self.results.get() != &block_entry_state || A::Direction::is_backward() {
let after_terminator_name = match terminator.kind {
mir::TerminatorKind::Call { destination: Some(_), .. } => "(on unwind)",
_ => "(on end)",
};
self.write_row_with_full_state(w, "", after_terminator_name)?;
}
// Write any changes caused by terminator-specific effects
let num_state_columns = self.num_state_columns();
match terminator.kind {
mir::TerminatorKind::Call {
destination: Some((return_place, _)),
ref func,
ref args,
..
} => {
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.apply_custom_effect(|analysis, state| {
analysis.apply_call_return_effect(state, block, func, args, return_place);
});
write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
write!(w, "</td>")
})?;
}
mir::TerminatorKind::Yield { resume, resume_arg, .. } => {
self.write_row(w, "", "(on yield resume)", |this, w, fmt| {
write!(
w,
r#"<td balign="left" colspan="{colspan}" {fmt} align="left">"#,
colspan = num_state_columns,
fmt = fmt,
)?;
let state_on_generator_drop = this.results.get().clone();
this.results.apply_custom_effect(|analysis, state| {
analysis.apply_yield_resume_effect(state, resume, resume_arg);
});
write_diff(
w,
this.results.analysis(),
&state_on_generator_drop,
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<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
prev_state: ResultsRefCursor<'a, 'a, 'tcx, A>,
}
impl<A> SimpleDiff<'a, 'tcx, A>
where
A: Analysis<'tcx>,
{
pub fn new(body: &'a Body<'tcx>, results: &'a Results<'tcx, A>) -> Self {
SimpleDiff { prev_state: ResultsRefCursor::new(body, results) }
}
}
impl<A> StateFormatter<'tcx, A> for SimpleDiff<'_, 'tcx, A>
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 A::Direction::is_forward() {
if location.statement_index == 0 {
self.prev_state.seek_to_block_start(location.block);
} else {
self.prev_state.seek_after_primary_effect(Location {
statement_index: location.statement_index - 1,
..location
});
}
} else {
if location == results.body().terminator_loc(location.block) {
self.prev_state.seek_to_block_end(location.block);
} else {
self.prev_state.seek_after_primary_effect(location.successor_within_block());
}
}
write!(w, r#"<td {fmt} balign="left" align="left">"#, fmt = fmt)?;
results.seek_after_primary_effect(location);
let curr_state = results.get();
write_diff(&mut w, results.analysis(), self.prev_state.get(), 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_entry(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_primary_effect(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_primary_effect(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,
}
}
}

View file

@ -0,0 +1,556 @@
//! 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::cmp::Ordering;
use std::io;
use rustc_hir::def_id::DefId;
use rustc_index::bit_set::{BitSet, HybridBitSet};
use rustc_index::vec::Idx;
use rustc_middle::mir::{self, BasicBlock, Location};
use rustc_middle::ty::{self, TyCtxt};
use rustc_target::abi::VariantIdx;
mod cursor;
mod direction;
mod engine;
mod graphviz;
mod visitor;
pub use self::cursor::{ResultsCursor, ResultsRefCursor};
pub use self::direction::{Backward, Direction, Forward};
pub use self::engine::{Engine, Results};
pub use self::visitor::{visit_results, ResultsVisitor};
pub use self::visitor::{BorrowckFlowState, BorrowckResults};
/// 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;
/// The direction of this analyis. Either `Forward` or `Backward`.
type Direction: Direction = Forward;
/// 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.
///
/// For backward analyses, initial state besides the bottom value is not yet supported. Trying
/// to mutate the initial state will result in a panic.
//
// FIXME: For backward dataflow analyses, the initial state should be applied to every basic
// block where control flow could exit the MIR body (e.g., those terminated with `return` or
// `resume`). It's not obvious how to handle `yield` points in generators, however.
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.
///
/// FIXME: This class of effects is not supported for backward dataflow analyses.
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 impl GenKill<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);
}
}
// NOTE: DO NOT CHANGE VARIANT ORDER. The derived `Ord` impls rely on the current order.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Effect {
/// The "before" effect (e.g., `apply_before_statement_effect`) for a statement (or
/// terminator).
Before,
/// The "primary" effect (e.g., `apply_statement_effect`) for a statement (or terminator).
Primary,
}
impl Effect {
pub const fn at_index(self, statement_index: usize) -> EffectIndex {
EffectIndex { effect: self, statement_index }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EffectIndex {
statement_index: usize,
effect: Effect,
}
impl EffectIndex {
fn next_in_forward_order(self) -> Self {
match self.effect {
Effect::Before => Effect::Primary.at_index(self.statement_index),
Effect::Primary => Effect::Before.at_index(self.statement_index + 1),
}
}
fn next_in_backward_order(self) -> Self {
match self.effect {
Effect::Before => Effect::Primary.at_index(self.statement_index),
Effect::Primary => Effect::Before.at_index(self.statement_index - 1),
}
}
/// Returns `true` if the effect at `self` should be applied eariler than the effect at `other`
/// in forward order.
fn precedes_in_forward_order(self, other: Self) -> bool {
let ord = self
.statement_index
.cmp(&other.statement_index)
.then_with(|| self.effect.cmp(&other.effect));
ord == Ordering::Less
}
/// Returns `true` if the effect at `self` should be applied earlier than the effect at `other`
/// in backward order.
fn precedes_in_backward_order(self, other: Self) -> bool {
let ord = other
.statement_index
.cmp(&self.statement_index)
.then_with(|| self.effect.cmp(&other.effect));
ord == Ordering::Less
}
}
#[cfg(test)]
mod tests;

View file

@ -0,0 +1,325 @@
//! A test for the logic that updates the state in a `ResultsCursor` during seek.
use std::marker::PhantomData;
use rustc_index::bit_set::BitSet;
use rustc_index::vec::IndexVec;
use rustc_middle::mir::{self, BasicBlock, Location};
use rustc_middle::ty;
use rustc_span::DUMMY_SP;
use super::*;
use crate::dataflow::BottomValue;
/// 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::outermost(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,
fn_span: DUMMY_SP,
},
);
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,
fn_span: DUMMY_SP,
},
);
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} ||
///
/// 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, D> {
body: &'tcx mir::Body<'tcx>,
dir: PhantomData<D>,
}
impl<D: Direction> MockAnalysis<'tcx, D> {
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.
fn effect(&self, loc: EffectIndex) -> usize {
let idx = match loc.effect {
Effect::Before => loc.statement_index * 2,
Effect::Primary => loc.statement_index * 2 + 1,
};
assert!(idx < Self::BASIC_BLOCK_OFFSET, "Too many statements in basic block");
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_primary_effect(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 block = target.block();
let mut ret = BitSet::new_empty(self.bits_per_block(self.body));
ret.insert(Self::BASIC_BLOCK_OFFSET + block.index());
let target = match target {
SeekTarget::BlockEntry { .. } => return ret,
SeekTarget::Before(loc) => Effect::Before.at_index(loc.statement_index),
SeekTarget::After(loc) => Effect::Primary.at_index(loc.statement_index),
};
let mut pos = if D::is_forward() {
Effect::Before.at_index(0)
} else {
Effect::Before.at_index(self.body[block].statements.len())
};
loop {
ret.insert(self.effect(pos));
if pos == target {
return ret;
}
if D::is_forward() {
pos = pos.next_in_forward_order();
} else {
pos = pos.next_in_backward_order();
}
}
}
}
impl<D: Direction> BottomValue for MockAnalysis<'tcx, D> {
const BOTTOM_VALUE: bool = false;
}
impl<D: Direction> AnalysisDomain<'tcx> for MockAnalysis<'tcx, D> {
type Idx = usize;
type Direction = D;
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<D: Direction> Analysis<'tcx> for MockAnalysis<'tcx, D> {
fn apply_statement_effect(
&self,
state: &mut BitSet<Self::Idx>,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
let idx = self.effect(Effect::Primary.at_index(location.statement_index));
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(Effect::Before.at_index(location.statement_index));
assert!(state.insert(idx));
}
fn apply_terminator_effect(
&self,
state: &mut BitSet<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
let idx = self.effect(Effect::Primary.at_index(location.statement_index));
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(Effect::Before.at_index(location.statement_index));
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>,
) {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SeekTarget {
BlockEntry(BasicBlock),
Before(Location),
After(Location),
}
impl SeekTarget {
fn block(&self) -> BasicBlock {
use SeekTarget::*;
match *self {
BlockEntry(block) => block,
Before(loc) | After(loc) => loc.block,
}
}
/// An iterator over all possible `SeekTarget`s in a given block in order, starting with
/// `BlockEntry`.
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..2).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),
_ => unreachable!(),
}
});
std::iter::once(SeekTarget::BlockEntry(block)).chain(statements_and_terminator)
}
}
fn test_cursor<D: Direction>(analysis: MockAnalysis<'tcx, D>) {
let body = analysis.body;
let mut cursor =
Results { entry_sets: analysis.mock_entry_sets(), analysis }.into_results_cursor(body);
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 {
BlockEntry(block) => cursor.seek_to_block_entry(block),
Before(loc) => cursor.seek_before_primary_effect(loc),
After(loc) => cursor.seek_after_primary_effect(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() {
dbg!(from);
dbg!(to);
seek_to_target(to);
seek_to_target(from);
}
}
}
#[test]
fn backward_cursor() {
let body = mock_body();
let body = &body;
let analysis = MockAnalysis { body, dir: PhantomData::<Backward> };
test_cursor(analysis)
}
#[test]
fn forward_cursor() {
let body = mock_body();
let body = &body;
let analysis = MockAnalysis { body, dir: PhantomData::<Forward> };
test_cursor(analysis)
}

View file

@ -0,0 +1,281 @@
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::{self, BasicBlock, Location};
use super::{Analysis, Direction, 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, V>(
body: &'mir mir::Body<'tcx>,
blocks: impl IntoIterator<Item = BasicBlock>,
results: &V,
vis: &mut impl ResultsVisitor<'mir, 'tcx, FlowState = F>,
) where
V: ResultsVisitable<'tcx, FlowState = F>,
{
let mut state = results.new_flow_state(body);
#[cfg(debug_assertions)]
let reachable_blocks = mir::traversal::reachable_as_bitset(body);
for block in blocks {
#[cfg(debug_assertions)]
assert!(reachable_blocks.contains(block));
let block_data = &body[block];
V::Direction::visit_results_in_block(&mut state, block, block_data, results, vis);
}
}
pub trait ResultsVisitor<'mir, 'tcx> {
type FlowState;
fn visit_block_start(
&mut self,
_state: &Self::FlowState,
_block_data: &'mir mir::BasicBlockData<'tcx>,
_block: BasicBlock,
) {
}
/// Called with the `before_statement_effect` of the given statement applied to `state` but not
/// its `statement_effect`.
fn visit_statement_before_primary_effect(
&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_after_primary_effect(
&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_before_primary_effect(
&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_after_primary_effect(
&mut self,
_state: &Self::FlowState,
_terminator: &'mir mir::Terminator<'tcx>,
_location: Location,
) {
}
fn visit_block_end(
&mut self,
_state: &Self::FlowState,
_block_data: &'mir mir::BasicBlockData<'tcx>,
_block: BasicBlock,
) {
}
}
/// 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 Direction: Direction;
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_entry`
/// before it can be observed by a `ResultsVisitor`.
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState;
fn reset_to_block_entry(&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>;
type Direction = A::Direction;
fn new_flow_state(&self, body: &mir::Body<'tcx>) -> Self::FlowState {
BitSet::new_empty(self.analysis.bits_per_block(body))
}
fn reset_to_block_entry(&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),*, D: Direction> ResultsVisitable<'tcx> for $T<$( Results<'tcx, $A> ),*>
where
$( $A: Analysis<'tcx, Direction = D>, )*
{
type Direction = D;
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_entry(
&self,
state: &mut Self::FlowState,
block: BasicBlock,
) {
$( state.$field.overwrite(&self.$field.entry_set_for_block(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 }
}

View file

@ -0,0 +1,276 @@
pub use super::*;
use crate::dataflow::{AnalysisDomain, GenKill, GenKillAnalysis};
use rustc_middle::mir::visit::Visitor;
use rustc_middle::mir::*;
use rustc_middle::ty::{ParamEnv, TyCtxt};
use rustc_span::DUMMY_SP;
pub type MaybeMutBorrowedLocals<'mir, 'tcx> = MaybeBorrowedLocals<MutBorrow<'mir, 'tcx>>;
/// A dataflow analysis that tracks whether a pointer or reference could possibly exist that points
/// to a given local.
///
/// The `K` parameter determines what kind of borrows are tracked. By default,
/// `MaybeBorrowedLocals` looks for *any* borrow of a local. If you are only interested in borrows
/// that might allow mutation, use the `MaybeMutBorrowedLocals` type alias instead.
///
/// At present, this is used as a very limited form of alias analysis. For example,
/// `MaybeBorrowedLocals` is used to compute which locals are live during a yield expression for
/// immovable generators. `MaybeMutBorrowedLocals` is used during const checking to prove that a
/// local has not been mutated via indirect assignment (e.g., `*p = 42`), the side-effects of a
/// function call or inline assembly.
pub struct MaybeBorrowedLocals<K = AnyBorrow> {
kind: K,
ignore_borrow_on_drop: bool,
}
impl MaybeBorrowedLocals {
/// A dataflow analysis that records whether a pointer or reference exists that may alias the
/// given local.
pub fn all_borrows() -> Self {
MaybeBorrowedLocals { kind: AnyBorrow, ignore_borrow_on_drop: false }
}
}
impl MaybeMutBorrowedLocals<'mir, 'tcx> {
/// A dataflow analysis that records whether a pointer or reference exists that may *mutably*
/// alias the given local.
///
/// This includes `&mut` and pointers derived from an `&mut`, as well as shared borrows of
/// types with interior mutability.
pub fn mut_borrows_only(
tcx: TyCtxt<'tcx>,
body: &'mir mir::Body<'tcx>,
param_env: ParamEnv<'tcx>,
) -> Self {
MaybeBorrowedLocals {
kind: MutBorrow { body, tcx, param_env },
ignore_borrow_on_drop: false,
}
}
}
impl<K> MaybeBorrowedLocals<K> {
/// During dataflow analysis, ignore the borrow that may occur when a place is dropped.
///
/// Drop terminators may call custom drop glue (`Drop::drop`), which takes `&mut self` as a
/// parameter. In the general case, a drop impl could launder that reference into the
/// surrounding environment through a raw pointer, thus creating a valid `*mut` pointing to the
/// dropped local. We are not yet willing to declare this particular case UB, so we must treat
/// all dropped locals as mutably borrowed for now. See discussion on [#61069].
///
/// In some contexts, we know that this borrow will never occur. For example, during
/// const-eval, custom drop glue cannot be run. Code that calls this should document the
/// assumptions that justify ignoring `Drop` terminators in this way.
///
/// [#61069]: https://github.com/rust-lang/rust/pull/61069
pub fn unsound_ignore_borrow_on_drop(self) -> Self {
MaybeBorrowedLocals { ignore_borrow_on_drop: true, ..self }
}
fn transfer_function<'a, T>(&'a self, trans: &'a mut T) -> TransferFunction<'a, T, K> {
TransferFunction {
kind: &self.kind,
trans,
ignore_borrow_on_drop: self.ignore_borrow_on_drop,
}
}
}
impl<K> AnalysisDomain<'tcx> for MaybeBorrowedLocals<K>
where
K: BorrowAnalysisKind<'tcx>,
{
type Idx = Local;
const NAME: &'static str = K::ANALYSIS_NAME;
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls().len()
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
// No locals are aliased on function entry
}
}
impl<K> GenKillAnalysis<'tcx> for MaybeBorrowedLocals<K>
where
K: BorrowAnalysisKind<'tcx>,
{
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
statement: &mir::Statement<'tcx>,
location: Location,
) {
self.transfer_function(trans).visit_statement(statement, location);
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
terminator: &mir::Terminator<'tcx>,
location: Location,
) {
self.transfer_function(trans).visit_terminator(terminator, location);
}
fn call_return_effect(
&self,
_trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_dest_place: mir::Place<'tcx>,
) {
}
}
impl<K> BottomValue for MaybeBorrowedLocals<K> {
// bottom = unborrowed
const BOTTOM_VALUE: bool = false;
}
/// A `Visitor` that defines the transfer function for `MaybeBorrowedLocals`.
struct TransferFunction<'a, T, K> {
trans: &'a mut T,
kind: &'a K,
ignore_borrow_on_drop: bool,
}
impl<T, K> Visitor<'tcx> for TransferFunction<'a, T, K>
where
T: GenKill<Local>,
K: BorrowAnalysisKind<'tcx>,
{
fn visit_statement(&mut self, stmt: &Statement<'tcx>, location: Location) {
self.super_statement(stmt, location);
// When we reach a `StorageDead` statement, we can assume that any pointers to this memory
// are now invalid.
if let StatementKind::StorageDead(local) = stmt.kind {
self.trans.kill(local);
}
}
fn visit_rvalue(&mut self, rvalue: &mir::Rvalue<'tcx>, location: Location) {
self.super_rvalue(rvalue, location);
match rvalue {
mir::Rvalue::AddressOf(mt, borrowed_place) => {
if !borrowed_place.is_indirect() && self.kind.in_address_of(*mt, *borrowed_place) {
self.trans.gen(borrowed_place.local);
}
}
mir::Rvalue::Ref(_, kind, borrowed_place) => {
if !borrowed_place.is_indirect() && self.kind.in_ref(*kind, *borrowed_place) {
self.trans.gen(borrowed_place.local);
}
}
mir::Rvalue::Cast(..)
| mir::Rvalue::Use(..)
| mir::Rvalue::ThreadLocalRef(..)
| mir::Rvalue::Repeat(..)
| mir::Rvalue::Len(..)
| mir::Rvalue::BinaryOp(..)
| mir::Rvalue::CheckedBinaryOp(..)
| mir::Rvalue::NullaryOp(..)
| mir::Rvalue::UnaryOp(..)
| mir::Rvalue::Discriminant(..)
| mir::Rvalue::Aggregate(..) => {}
}
}
fn visit_terminator(&mut self, terminator: &mir::Terminator<'tcx>, location: Location) {
self.super_terminator(terminator, location);
match terminator.kind {
mir::TerminatorKind::Drop { place: dropped_place, .. }
| mir::TerminatorKind::DropAndReplace { place: dropped_place, .. } => {
// See documentation for `unsound_ignore_borrow_on_drop` for an explanation.
if !self.ignore_borrow_on_drop {
self.trans.gen(dropped_place.local);
}
}
TerminatorKind::Abort
| TerminatorKind::Assert { .. }
| TerminatorKind::Call { .. }
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::Goto { .. }
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable
| TerminatorKind::Yield { .. } => {}
}
}
}
pub struct AnyBorrow;
pub struct MutBorrow<'mir, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'mir Body<'tcx>,
param_env: ParamEnv<'tcx>,
}
impl MutBorrow<'mir, 'tcx> {
/// `&` and `&raw` only allow mutation if the borrowed place is `!Freeze`.
///
/// This assumes that it is UB to take the address of a struct field whose type is
/// `Freeze`, then use pointer arithmetic to derive a pointer to a *different* field of
/// that same struct whose type is `!Freeze`. If we decide that this is not UB, we will
/// have to check the type of the borrowed **local** instead of the borrowed **place**
/// below. See [rust-lang/unsafe-code-guidelines#134].
///
/// [rust-lang/unsafe-code-guidelines#134]: https://github.com/rust-lang/unsafe-code-guidelines/issues/134
fn shared_borrow_allows_mutation(&self, place: Place<'tcx>) -> bool {
!place.ty(self.body, self.tcx).ty.is_freeze(self.tcx.at(DUMMY_SP), self.param_env)
}
}
pub trait BorrowAnalysisKind<'tcx> {
const ANALYSIS_NAME: &'static str;
fn in_address_of(&self, mt: Mutability, place: Place<'tcx>) -> bool;
fn in_ref(&self, kind: mir::BorrowKind, place: Place<'tcx>) -> bool;
}
impl BorrowAnalysisKind<'tcx> for AnyBorrow {
const ANALYSIS_NAME: &'static str = "maybe_borrowed_locals";
fn in_ref(&self, _: mir::BorrowKind, _: Place<'_>) -> bool {
true
}
fn in_address_of(&self, _: Mutability, _: Place<'_>) -> bool {
true
}
}
impl BorrowAnalysisKind<'tcx> for MutBorrow<'mir, 'tcx> {
const ANALYSIS_NAME: &'static str = "maybe_mut_borrowed_locals";
fn in_ref(&self, kind: mir::BorrowKind, place: Place<'tcx>) -> bool {
match kind {
mir::BorrowKind::Mut { .. } => true,
mir::BorrowKind::Shared | mir::BorrowKind::Shallow | mir::BorrowKind::Unique => {
self.shared_borrow_allows_mutation(place)
}
}
}
fn in_address_of(&self, mt: Mutability, place: Place<'tcx>) -> bool {
match mt {
Mutability::Mut => true,
Mutability::Not => self.shared_borrow_allows_mutation(place),
}
}
}

View file

@ -0,0 +1,350 @@
use rustc_middle::mir::{self, Body, Location, Place};
use rustc_middle::ty::RegionVid;
use rustc_middle::ty::TyCtxt;
use rustc_data_structures::fx::FxHashMap;
use rustc_index::bit_set::BitSet;
use crate::borrow_check::{
places_conflict, BorrowSet, PlaceConflictBias, PlaceExt, RegionInferenceContext, ToRegionVid,
};
use crate::dataflow::BottomValue;
use crate::dataflow::{self, GenKill};
use std::rc::Rc;
rustc_index::newtype_index! {
pub struct BorrowIndex {
DEBUG_FORMAT = "bw{}"
}
}
/// `Borrows` stores the data used in the analyses that track the flow
/// of borrows.
///
/// It uniquely identifies every borrow (`Rvalue::Ref`) by a
/// `BorrowIndex`, and maps each such index to a `BorrowData`
/// describing the borrow. These indexes are used for representing the
/// borrows in compact bitvectors.
pub struct Borrows<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
borrow_set: Rc<BorrowSet<'tcx>>,
borrows_out_of_scope_at_location: FxHashMap<Location, Vec<BorrowIndex>>,
/// NLL region inference context with which NLL queries should be resolved
_nonlexical_regioncx: Rc<RegionInferenceContext<'tcx>>,
}
struct StackEntry {
bb: mir::BasicBlock,
lo: usize,
hi: usize,
first_part_only: bool,
}
fn precompute_borrows_out_of_scope<'tcx>(
body: &Body<'tcx>,
regioncx: &Rc<RegionInferenceContext<'tcx>>,
borrows_out_of_scope_at_location: &mut FxHashMap<Location, Vec<BorrowIndex>>,
borrow_index: BorrowIndex,
borrow_region: RegionVid,
location: Location,
) {
// We visit one BB at a time. The complication is that we may start in the
// middle of the first BB visited (the one containing `location`), in which
// case we may have to later on process the first part of that BB if there
// is a path back to its start.
// For visited BBs, we record the index of the first statement processed.
// (In fully processed BBs this index is 0.) Note also that we add BBs to
// `visited` once they are added to `stack`, before they are actually
// processed, because this avoids the need to look them up again on
// completion.
let mut visited = FxHashMap::default();
visited.insert(location.block, location.statement_index);
let mut stack = vec![];
stack.push(StackEntry {
bb: location.block,
lo: location.statement_index,
hi: body[location.block].statements.len(),
first_part_only: false,
});
while let Some(StackEntry { bb, lo, hi, first_part_only }) = stack.pop() {
let mut finished_early = first_part_only;
for i in lo..=hi {
let location = Location { block: bb, statement_index: i };
// If region does not contain a point at the location, then add to list and skip
// successor locations.
if !regioncx.region_contains(borrow_region, location) {
debug!("borrow {:?} gets killed at {:?}", borrow_index, location);
borrows_out_of_scope_at_location.entry(location).or_default().push(borrow_index);
finished_early = true;
break;
}
}
if !finished_early {
// Add successor BBs to the work list, if necessary.
let bb_data = &body[bb];
assert!(hi == bb_data.statements.len());
for &succ_bb in bb_data.terminator().successors() {
visited
.entry(succ_bb)
.and_modify(|lo| {
// `succ_bb` has been seen before. If it wasn't
// fully processed, add its first part to `stack`
// for processing.
if *lo > 0 {
stack.push(StackEntry {
bb: succ_bb,
lo: 0,
hi: *lo - 1,
first_part_only: true,
});
}
// And update this entry with 0, to represent the
// whole BB being processed.
*lo = 0;
})
.or_insert_with(|| {
// succ_bb hasn't been seen before. Add it to
// `stack` for processing.
stack.push(StackEntry {
bb: succ_bb,
lo: 0,
hi: body[succ_bb].statements.len(),
first_part_only: false,
});
// Insert 0 for this BB, to represent the whole BB
// being processed.
0
});
}
}
}
}
impl<'a, 'tcx> Borrows<'a, 'tcx> {
crate fn new(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
nonlexical_regioncx: Rc<RegionInferenceContext<'tcx>>,
borrow_set: &Rc<BorrowSet<'tcx>>,
) -> Self {
let mut borrows_out_of_scope_at_location = FxHashMap::default();
for (borrow_index, borrow_data) in borrow_set.iter_enumerated() {
let borrow_region = borrow_data.region.to_region_vid();
let location = borrow_data.reserve_location;
precompute_borrows_out_of_scope(
body,
&nonlexical_regioncx,
&mut borrows_out_of_scope_at_location,
borrow_index,
borrow_region,
location,
);
}
Borrows {
tcx,
body,
borrow_set: borrow_set.clone(),
borrows_out_of_scope_at_location,
_nonlexical_regioncx: nonlexical_regioncx,
}
}
pub fn location(&self, idx: BorrowIndex) -> &Location {
&self.borrow_set[idx].reserve_location
}
/// Add all borrows to the kill set, if those borrows are out of scope at `location`.
/// That means they went out of a nonlexical scope
fn kill_loans_out_of_scope_at_location(
&self,
trans: &mut impl GenKill<BorrowIndex>,
location: Location,
) {
// NOTE: The state associated with a given `location`
// reflects the dataflow on entry to the statement.
// Iterate over each of the borrows that we've precomputed
// to have went out of scope at this location and kill them.
//
// We are careful always to call this function *before* we
// set up the gen-bits for the statement or
// termanator. That way, if the effect of the statement or
// terminator *does* introduce a new loan of the same
// region, then setting that gen-bit will override any
// potential kill introduced here.
if let Some(indices) = self.borrows_out_of_scope_at_location.get(&location) {
trans.kill_all(indices.iter().copied());
}
}
/// Kill any borrows that conflict with `place`.
fn kill_borrows_on_place(&self, trans: &mut impl GenKill<BorrowIndex>, place: Place<'tcx>) {
debug!("kill_borrows_on_place: place={:?}", place);
let other_borrows_of_local = self
.borrow_set
.local_map
.get(&place.local)
.into_iter()
.flat_map(|bs| bs.iter())
.copied();
// If the borrowed place is a local with no projections, all other borrows of this
// local must conflict. This is purely an optimization so we don't have to call
// `places_conflict` for every borrow.
if place.projection.is_empty() {
if !self.body.local_decls[place.local].is_ref_to_static() {
trans.kill_all(other_borrows_of_local);
}
return;
}
// By passing `PlaceConflictBias::NoOverlap`, we conservatively assume that any given
// pair of array indices are unequal, so that when `places_conflict` returns true, we
// will be assured that two places being compared definitely denotes the same sets of
// locations.
let definitely_conflicting_borrows = other_borrows_of_local.filter(|&i| {
places_conflict(
self.tcx,
self.body,
self.borrow_set[i].borrowed_place,
place,
PlaceConflictBias::NoOverlap,
)
});
trans.kill_all(definitely_conflicting_borrows);
}
}
impl<'tcx> dataflow::AnalysisDomain<'tcx> for Borrows<'_, 'tcx> {
type Idx = BorrowIndex;
const NAME: &'static str = "borrows";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.borrow_set.len() * 2
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
// no borrows of code region_scopes have been taken prior to
// function execution, so this method has no effect.
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, idx: Self::Idx) -> std::io::Result<()> {
write!(w, "{:?}", self.location(idx))
}
}
impl<'tcx> dataflow::GenKillAnalysis<'tcx> for Borrows<'_, 'tcx> {
fn before_statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
self.kill_loans_out_of_scope_at_location(trans, location);
}
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
stmt: &mir::Statement<'tcx>,
location: Location,
) {
match stmt.kind {
mir::StatementKind::Assign(box (lhs, ref rhs)) => {
if let mir::Rvalue::Ref(_, _, place) = *rhs {
if place.ignore_borrow(
self.tcx,
self.body,
&self.borrow_set.locals_state_at_exit,
) {
return;
}
let index = self.borrow_set.get_index_of(&location).unwrap_or_else(|| {
panic!("could not find BorrowIndex for location {:?}", location);
});
trans.gen(index);
}
// Make sure there are no remaining borrows for variables
// that are assigned over.
self.kill_borrows_on_place(trans, lhs);
}
mir::StatementKind::StorageDead(local) => {
// Make sure there are no remaining borrows for locals that
// are gone out of scope.
self.kill_borrows_on_place(trans, Place::from(local));
}
mir::StatementKind::LlvmInlineAsm(ref asm) => {
for (output, kind) in asm.outputs.iter().zip(&asm.asm.outputs) {
if !kind.is_indirect && !kind.is_rw {
self.kill_borrows_on_place(trans, *output);
}
}
}
mir::StatementKind::FakeRead(..)
| mir::StatementKind::SetDiscriminant { .. }
| mir::StatementKind::StorageLive(..)
| mir::StatementKind::Retag { .. }
| mir::StatementKind::AscribeUserType(..)
| mir::StatementKind::Coverage(..)
| mir::StatementKind::Nop => {}
}
}
fn before_terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
self.kill_loans_out_of_scope_at_location(trans, location);
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
teminator: &mir::Terminator<'tcx>,
_location: Location,
) {
if let mir::TerminatorKind::InlineAsm { operands, .. } = &teminator.kind {
for op in operands {
if let mir::InlineAsmOperand::Out { place: Some(place), .. }
| mir::InlineAsmOperand::InOut { out_place: Some(place), .. } = *op
{
self.kill_borrows_on_place(trans, place);
}
}
}
}
fn call_return_effect(
&self,
_trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_dest_place: mir::Place<'tcx>,
) {
}
}
impl<'a, 'tcx> BottomValue for Borrows<'a, 'tcx> {
/// bottom = nothing is reserved or activated yet;
const BOTTOM_VALUE: bool = false;
}

View file

@ -0,0 +1,116 @@
//! A less precise version of `MaybeInitializedPlaces` whose domain is entire locals.
//!
//! A local will be maybe initialized if *any* projections of that local might be initialized.
use crate::dataflow::{self, BottomValue, GenKill};
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{PlaceContext, Visitor};
use rustc_middle::mir::{self, BasicBlock, Local, Location};
pub struct MaybeInitializedLocals;
impl BottomValue for MaybeInitializedLocals {
/// bottom = uninit
const BOTTOM_VALUE: bool = false;
}
impl dataflow::AnalysisDomain<'tcx> for MaybeInitializedLocals {
type Idx = Local;
const NAME: &'static str = "maybe_init_locals";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, entry_set: &mut BitSet<Self::Idx>) {
// Function arguments are initialized to begin with.
for arg in body.args_iter() {
entry_set.insert(arg);
}
}
}
impl dataflow::GenKillAnalysis<'tcx> for MaybeInitializedLocals {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
statement: &mir::Statement<'tcx>,
loc: Location,
) {
TransferFunction { trans }.visit_statement(statement, loc)
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
terminator: &mir::Terminator<'tcx>,
loc: Location,
) {
TransferFunction { trans }.visit_terminator(terminator, loc)
}
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>,
) {
trans.gen(return_place.local)
}
/// See `Analysis::apply_yield_resume_effect`.
fn yield_resume_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_resume_block: BasicBlock,
resume_place: mir::Place<'tcx>,
) {
trans.gen(resume_place.local)
}
}
struct TransferFunction<'a, T> {
trans: &'a mut T,
}
impl<T> Visitor<'tcx> for TransferFunction<'a, T>
where
T: GenKill<Local>,
{
fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, NonUseContext};
match context {
// These are handled specially in `call_return_effect` and `yield_resume_effect`.
PlaceContext::MutatingUse(MutatingUseContext::Call | MutatingUseContext::Yield) => {}
// Otherwise, when a place is mutated, we must consider it possibly initialized.
PlaceContext::MutatingUse(_) => self.trans.gen(local),
// If the local is moved out of, or if it gets marked `StorageDead`, consider it no
// longer initialized.
PlaceContext::NonUse(NonUseContext::StorageDead)
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => self.trans.kill(local),
// All other uses do not affect this analysis.
PlaceContext::NonUse(
NonUseContext::StorageLive
| NonUseContext::AscribeUserTy
| NonUseContext::Coverage
| NonUseContext::VarDebugInfo,
)
| PlaceContext::NonMutatingUse(
NonMutatingUseContext::Inspect
| NonMutatingUseContext::Copy
| NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::ShallowBorrow
| NonMutatingUseContext::UniqueBorrow
| NonMutatingUseContext::AddressOf
| NonMutatingUseContext::Projection,
) => {}
}
}
}

View file

@ -0,0 +1,167 @@
use rustc_index::bit_set::BitSet;
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::{self, Local, Location};
use crate::dataflow::{AnalysisDomain, Backward, BottomValue, GenKill, GenKillAnalysis};
/// A [live-variable dataflow analysis][liveness].
///
/// This analysis considers references as being used only at the point of the
/// borrow. In other words, this analysis does not track uses because of references that already
/// exist. See [this `mir-datalow` test][flow-test] for an example. You almost never want to use
/// this analysis without also looking at the results of [`MaybeBorrowedLocals`].
///
/// [`MaybeBorrowedLocals`]: ../struct.MaybeBorrowedLocals.html
/// [flow-test]: https://github.com/rust-lang/rust/blob/a08c47310c7d49cbdc5d7afb38408ba519967ecd/src/test/ui/mir-dataflow/liveness-ptr.rs
/// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis
pub struct MaybeLiveLocals;
impl MaybeLiveLocals {
fn transfer_function<T>(&self, trans: &'a mut T) -> TransferFunction<'a, T> {
TransferFunction(trans)
}
}
impl BottomValue for MaybeLiveLocals {
// bottom = not live
const BOTTOM_VALUE: bool = false;
}
impl AnalysisDomain<'tcx> for MaybeLiveLocals {
type Idx = Local;
type Direction = Backward;
const NAME: &'static str = "liveness";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, _: &mut BitSet<Self::Idx>) {
// No variables are live until we observe a use
}
}
impl GenKillAnalysis<'tcx> for MaybeLiveLocals {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
statement: &mir::Statement<'tcx>,
location: Location,
) {
self.transfer_function(trans).visit_statement(statement, location);
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
terminator: &mir::Terminator<'tcx>,
location: Location,
) {
self.transfer_function(trans).visit_terminator(terminator, location);
}
fn call_return_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
) {
if let Some(local) = dest_place.as_local() {
trans.kill(local);
}
}
fn yield_resume_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_resume_block: mir::BasicBlock,
resume_place: mir::Place<'tcx>,
) {
if let Some(local) = resume_place.as_local() {
trans.kill(local);
}
}
}
struct TransferFunction<'a, T>(&'a mut T);
impl<'tcx, T> Visitor<'tcx> for TransferFunction<'_, T>
where
T: GenKill<Local>,
{
fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
let mir::Place { projection, local } = *place;
// We purposefully do not call `super_place` here to avoid calling `visit_local` for this
// place with one of the `Projection` variants of `PlaceContext`.
self.visit_projection(local, projection, context, location);
match DefUse::for_place(context) {
// Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a use.
Some(_) if place.is_indirect() => self.0.gen(local),
Some(DefUse::Def) if projection.is_empty() => self.0.kill(local),
Some(DefUse::Use) => self.0.gen(local),
_ => {}
}
}
fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
// Because we do not call `super_place` above, `visit_local` is only called for locals that
// do not appear as part of a `Place` in the MIR. This handles cases like the implicit use
// of the return place in a `Return` terminator or the index in an `Index` projection.
match DefUse::for_place(context) {
Some(DefUse::Def) => self.0.kill(local),
Some(DefUse::Use) => self.0.gen(local),
_ => {}
}
}
}
#[derive(Eq, PartialEq, Clone)]
enum DefUse {
Def,
Use,
}
impl DefUse {
fn for_place(context: PlaceContext) -> Option<DefUse> {
match context {
PlaceContext::NonUse(_) => None,
PlaceContext::MutatingUse(MutatingUseContext::Store) => Some(DefUse::Def),
// `MutatingUseContext::Call` and `MutatingUseContext::Yield` indicate that this is the
// destination place for a `Call` return or `Yield` resume respectively. Since this is
// only a `Def` when the function returns succesfully, we handle this case separately
// in `call_return_effect` above.
PlaceContext::MutatingUse(MutatingUseContext::Call | MutatingUseContext::Yield) => None,
// All other contexts are uses...
PlaceContext::MutatingUse(
MutatingUseContext::AddressOf
| MutatingUseContext::AsmOutput
| MutatingUseContext::Borrow
| MutatingUseContext::Drop
| MutatingUseContext::Retag,
)
| PlaceContext::NonMutatingUse(
NonMutatingUseContext::AddressOf
| NonMutatingUseContext::Copy
| NonMutatingUseContext::Inspect
| NonMutatingUseContext::Move
| NonMutatingUseContext::ShallowBorrow
| NonMutatingUseContext::SharedBorrow
| NonMutatingUseContext::UniqueBorrow,
) => Some(DefUse::Use),
PlaceContext::MutatingUse(MutatingUseContext::Projection)
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Projection) => {
unreachable!("A projection could be a def or a use and must be handled separately")
}
}
}
}

View file

@ -0,0 +1,647 @@
//! Dataflow analyses are built upon some interpretation of the
//! bitvectors attached to each basic block, represented via a
//! zero-sized structure.
use rustc_index::bit_set::BitSet;
use rustc_index::vec::Idx;
use rustc_middle::mir::{self, Body, Location};
use rustc_middle::ty::{self, TyCtxt};
use rustc_target::abi::VariantIdx;
use super::MoveDataParamEnv;
use crate::util::elaborate_drops::DropFlagState;
use super::move_paths::{HasMoveData, InitIndex, InitKind, MoveData, MovePathIndex};
use super::{AnalysisDomain, BottomValue, GenKill, GenKillAnalysis};
use super::drop_flag_effects_for_function_entry;
use super::drop_flag_effects_for_location;
use super::on_lookup_result_bits;
use crate::dataflow::drop_flag_effects;
mod borrowed_locals;
pub(super) mod borrows;
mod init_locals;
mod liveness;
mod storage_liveness;
pub use self::borrowed_locals::{MaybeBorrowedLocals, MaybeMutBorrowedLocals};
pub use self::borrows::Borrows;
pub use self::init_locals::MaybeInitializedLocals;
pub use self::liveness::MaybeLiveLocals;
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageLive};
/// `MaybeInitializedPlaces` tracks all places that might be
/// initialized upon reaching a particular point in the control flow
/// for a function.
///
/// For example, in code like the following, we have corresponding
/// dataflow information shown in the right-hand comments.
///
/// ```rust
/// struct S;
/// fn foo(pred: bool) { // maybe-init:
/// // {}
/// let a = S; let b = S; let c; let d; // {a, b}
///
/// if pred {
/// drop(a); // { b}
/// b = S; // { b}
///
/// } else {
/// drop(b); // {a}
/// d = S; // {a, d}
///
/// } // {a, b, d}
///
/// c = S; // {a, b, c, d}
/// }
/// ```
///
/// To determine whether a place *must* be initialized at a
/// particular control-flow point, one can take the set-difference
/// between this data and the data from `MaybeUninitializedPlaces` at the
/// corresponding control-flow point.
///
/// Similarly, at a given `drop` statement, the set-intersection
/// between this data and `MaybeUninitializedPlaces` yields the set of
/// places that would require a dynamic drop-flag at that statement.
pub struct MaybeInitializedPlaces<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
mdpe: &'a MoveDataParamEnv<'tcx>,
}
impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
MaybeInitializedPlaces { tcx, body, mdpe }
}
}
impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
fn move_data(&self) -> &MoveData<'tcx> {
&self.mdpe.move_data
}
}
/// `MaybeUninitializedPlaces` tracks all places that might be
/// uninitialized upon reaching a particular point in the control flow
/// for a function.
///
/// For example, in code like the following, we have corresponding
/// dataflow information shown in the right-hand comments.
///
/// ```rust
/// struct S;
/// fn foo(pred: bool) { // maybe-uninit:
/// // {a, b, c, d}
/// let a = S; let b = S; let c; let d; // { c, d}
///
/// if pred {
/// drop(a); // {a, c, d}
/// b = S; // {a, c, d}
///
/// } else {
/// drop(b); // { b, c, d}
/// d = S; // { b, c }
///
/// } // {a, b, c, d}
///
/// c = S; // {a, b, d}
/// }
/// ```
///
/// To determine whether a place *must* be uninitialized at a
/// particular control-flow point, one can take the set-difference
/// between this data and the data from `MaybeInitializedPlaces` at the
/// corresponding control-flow point.
///
/// Similarly, at a given `drop` statement, the set-intersection
/// between this data and `MaybeInitializedPlaces` yields the set of
/// places that would require a dynamic drop-flag at that statement.
pub struct MaybeUninitializedPlaces<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
mdpe: &'a MoveDataParamEnv<'tcx>,
mark_inactive_variants_as_uninit: bool,
}
impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
MaybeUninitializedPlaces { tcx, body, mdpe, mark_inactive_variants_as_uninit: false }
}
/// Causes inactive enum variants to be marked as "maybe uninitialized" after a switch on an
/// enum discriminant.
///
/// This is correct in a vacuum but is not the default because it causes problems in the borrow
/// checker, where this information gets propagated along `FakeEdge`s.
pub fn mark_inactive_variants_as_uninit(mut self) -> Self {
self.mark_inactive_variants_as_uninit = true;
self
}
}
impl<'a, 'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'a, 'tcx> {
fn move_data(&self) -> &MoveData<'tcx> {
&self.mdpe.move_data
}
}
/// `DefinitelyInitializedPlaces` tracks all places that are definitely
/// initialized upon reaching a particular point in the control flow
/// for a function.
///
/// For example, in code like the following, we have corresponding
/// dataflow information shown in the right-hand comments.
///
/// ```rust
/// struct S;
/// fn foo(pred: bool) { // definite-init:
/// // { }
/// let a = S; let b = S; let c; let d; // {a, b }
///
/// if pred {
/// drop(a); // { b, }
/// b = S; // { b, }
///
/// } else {
/// drop(b); // {a, }
/// d = S; // {a, d}
///
/// } // { }
///
/// c = S; // { c }
/// }
/// ```
///
/// To determine whether a place *may* be uninitialized at a
/// particular control-flow point, one can take the set-complement
/// of this data.
///
/// Similarly, at a given `drop` statement, the set-difference between
/// this data and `MaybeInitializedPlaces` yields the set of places
/// that would require a dynamic drop-flag at that statement.
pub struct DefinitelyInitializedPlaces<'a, 'tcx> {
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
mdpe: &'a MoveDataParamEnv<'tcx>,
}
impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
DefinitelyInitializedPlaces { tcx, body, mdpe }
}
}
impl<'a, 'tcx> HasMoveData<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
fn move_data(&self) -> &MoveData<'tcx> {
&self.mdpe.move_data
}
}
/// `EverInitializedPlaces` tracks all places that might have ever been
/// initialized upon reaching a particular point in the control flow
/// for a function, without an intervening `Storage Dead`.
///
/// This dataflow is used to determine if an immutable local variable may
/// be assigned to.
///
/// For example, in code like the following, we have corresponding
/// dataflow information shown in the right-hand comments.
///
/// ```rust
/// struct S;
/// fn foo(pred: bool) { // ever-init:
/// // { }
/// let a = S; let b = S; let c; let d; // {a, b }
///
/// if pred {
/// drop(a); // {a, b, }
/// b = S; // {a, b, }
///
/// } else {
/// drop(b); // {a, b, }
/// d = S; // {a, b, d }
///
/// } // {a, b, d }
///
/// c = S; // {a, b, c, d }
/// }
/// ```
pub struct EverInitializedPlaces<'a, 'tcx> {
#[allow(dead_code)]
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
mdpe: &'a MoveDataParamEnv<'tcx>,
}
impl<'a, 'tcx> EverInitializedPlaces<'a, 'tcx> {
pub fn new(tcx: TyCtxt<'tcx>, body: &'a Body<'tcx>, mdpe: &'a MoveDataParamEnv<'tcx>) -> Self {
EverInitializedPlaces { tcx, body, mdpe }
}
}
impl<'a, 'tcx> HasMoveData<'tcx> for EverInitializedPlaces<'a, 'tcx> {
fn move_data(&self) -> &MoveData<'tcx> {
&self.mdpe.move_data
}
}
impl<'a, 'tcx> MaybeInitializedPlaces<'a, 'tcx> {
fn update_bits(
trans: &mut impl GenKill<MovePathIndex>,
path: MovePathIndex,
state: DropFlagState,
) {
match state {
DropFlagState::Absent => trans.kill(path),
DropFlagState::Present => trans.gen(path),
}
}
}
impl<'a, 'tcx> MaybeUninitializedPlaces<'a, 'tcx> {
fn update_bits(
trans: &mut impl GenKill<MovePathIndex>,
path: MovePathIndex,
state: DropFlagState,
) {
match state {
DropFlagState::Absent => trans.gen(path),
DropFlagState::Present => trans.kill(path),
}
}
}
impl<'a, 'tcx> DefinitelyInitializedPlaces<'a, 'tcx> {
fn update_bits(
trans: &mut impl GenKill<MovePathIndex>,
path: MovePathIndex,
state: DropFlagState,
) {
match state {
DropFlagState::Absent => trans.kill(path),
DropFlagState::Present => trans.gen(path),
}
}
}
impl<'tcx> AnalysisDomain<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
const NAME: &'static str = "maybe_init";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().move_paths.len()
}
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
assert!(s == DropFlagState::Present);
state.insert(path);
});
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, mpi: Self::Idx) -> std::io::Result<()> {
write!(w, "{}", self.move_data().move_paths[mpi])
}
}
impl<'tcx> GenKillAnalysis<'tcx> for MaybeInitializedPlaces<'_, 'tcx> {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
Self::update_bits(trans, path, s)
})
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
Self::update_bits(trans, path, s)
})
}
fn call_return_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
) {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 1 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(dest_place.as_ref()),
|mpi| {
trans.gen(mpi);
},
);
}
fn discriminant_switch_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
enum_place: mir::Place<'tcx>,
_adt: &ty::AdtDef,
variant: VariantIdx,
) {
// Kill all move paths that correspond to variants we know to be inactive along this
// particular outgoing edge of a `SwitchInt`.
drop_flag_effects::on_all_inactive_variants(
self.tcx,
self.body,
self.move_data(),
enum_place,
variant,
|mpi| trans.kill(mpi),
);
}
}
impl<'tcx> AnalysisDomain<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
type Idx = MovePathIndex;
const NAME: &'static str = "maybe_uninit";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().move_paths.len()
}
// sets on_entry bits for Arg places
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
// set all bits to 1 (uninit) before gathering counterevidence
assert!(self.bits_per_block(body) == state.domain_size());
state.insert_all();
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
assert!(s == DropFlagState::Present);
state.remove(path);
});
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, mpi: Self::Idx) -> std::io::Result<()> {
write!(w, "{}", self.move_data().move_paths[mpi])
}
}
impl<'tcx> GenKillAnalysis<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
Self::update_bits(trans, path, s)
})
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
Self::update_bits(trans, path, s)
})
}
fn call_return_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
) {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 0 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(dest_place.as_ref()),
|mpi| {
trans.kill(mpi);
},
);
}
fn discriminant_switch_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
enum_place: mir::Place<'tcx>,
_adt: &ty::AdtDef,
variant: VariantIdx,
) {
if !self.mark_inactive_variants_as_uninit {
return;
}
// Mark all move paths that correspond to variants other than this one as maybe
// uninitialized (in reality, they are *definitely* uninitialized).
drop_flag_effects::on_all_inactive_variants(
self.tcx,
self.body,
self.move_data(),
enum_place,
variant,
|mpi| trans.gen(mpi),
);
}
}
impl<'a, 'tcx> AnalysisDomain<'tcx> for DefinitelyInitializedPlaces<'a, 'tcx> {
type Idx = MovePathIndex;
const NAME: &'static str = "definite_init";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().move_paths.len()
}
// sets on_entry bits for Arg places
fn initialize_start_block(&self, _: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
state.clear();
drop_flag_effects_for_function_entry(self.tcx, self.body, self.mdpe, |path, s| {
assert!(s == DropFlagState::Present);
state.insert(path);
});
}
fn pretty_print_idx(&self, w: &mut impl std::io::Write, mpi: Self::Idx) -> std::io::Result<()> {
write!(w, "{}", self.move_data().move_paths[mpi])
}
}
impl<'tcx> GenKillAnalysis<'tcx> for DefinitelyInitializedPlaces<'_, 'tcx> {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_statement: &mir::Statement<'tcx>,
location: Location,
) {
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
Self::update_bits(trans, path, s)
})
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
drop_flag_effects_for_location(self.tcx, self.body, self.mdpe, location, |path, s| {
Self::update_bits(trans, path, s)
})
}
fn call_return_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
dest_place: mir::Place<'tcx>,
) {
// when a call returns successfully, that means we need to set
// the bits for that dest_place to 1 (initialized).
on_lookup_result_bits(
self.tcx,
self.body,
self.move_data(),
self.move_data().rev_lookup.find(dest_place.as_ref()),
|mpi| {
trans.gen(mpi);
},
);
}
}
impl<'tcx> AnalysisDomain<'tcx> for EverInitializedPlaces<'_, 'tcx> {
type Idx = InitIndex;
const NAME: &'static str = "ever_init";
fn bits_per_block(&self, _: &mir::Body<'tcx>) -> usize {
self.move_data().inits.len()
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, state: &mut BitSet<Self::Idx>) {
for arg_init in 0..body.arg_count {
state.insert(InitIndex::new(arg_init));
}
}
}
impl<'tcx> GenKillAnalysis<'tcx> for EverInitializedPlaces<'_, 'tcx> {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
stmt: &mir::Statement<'tcx>,
location: Location,
) {
let move_data = self.move_data();
let init_path_map = &move_data.init_path_map;
let init_loc_map = &move_data.init_loc_map;
let rev_lookup = &move_data.rev_lookup;
debug!(
"statement {:?} at loc {:?} initializes move_indexes {:?}",
stmt, location, &init_loc_map[location]
);
trans.gen_all(init_loc_map[location].iter().copied());
if let mir::StatementKind::StorageDead(local) = stmt.kind {
// End inits for StorageDead, so that an immutable variable can
// be reinitialized on the next iteration of the loop.
let move_path_index = rev_lookup.find_local(local);
debug!(
"stmt {:?} at loc {:?} clears the ever initialized status of {:?}",
stmt, location, &init_path_map[move_path_index]
);
trans.kill_all(init_path_map[move_path_index].iter().copied());
}
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_terminator: &mir::Terminator<'tcx>,
location: Location,
) {
let (body, move_data) = (self.body, self.move_data());
let term = body[location.block].terminator();
let init_loc_map = &move_data.init_loc_map;
debug!(
"terminator {:?} at loc {:?} initializes move_indexes {:?}",
term, location, &init_loc_map[location]
);
trans.gen_all(
init_loc_map[location]
.iter()
.filter(|init_index| {
move_data.inits[**init_index].kind != InitKind::NonPanicPathOnly
})
.copied(),
);
}
fn call_return_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
block: mir::BasicBlock,
_func: &mir::Operand<'tcx>,
_args: &[mir::Operand<'tcx>],
_dest_place: mir::Place<'tcx>,
) {
let move_data = self.move_data();
let init_loc_map = &move_data.init_loc_map;
let call_loc = self.body.terminator_loc(block);
for init_index in &init_loc_map[call_loc] {
trans.gen(*init_index);
}
}
}
impl<'a, 'tcx> BottomValue for MaybeInitializedPlaces<'a, 'tcx> {
/// bottom = uninitialized
const BOTTOM_VALUE: bool = false;
}
impl<'a, 'tcx> BottomValue for MaybeUninitializedPlaces<'a, 'tcx> {
/// bottom = initialized (start_block_effect counters this at outset)
const BOTTOM_VALUE: bool = false;
}
impl<'a, 'tcx> BottomValue for DefinitelyInitializedPlaces<'a, 'tcx> {
/// bottom = initialized (start_block_effect counters this at outset)
const BOTTOM_VALUE: bool = true;
}
impl<'a, 'tcx> BottomValue for EverInitializedPlaces<'a, 'tcx> {
/// bottom = no initialized variables by default
const BOTTOM_VALUE: bool = false;
}

View file

@ -0,0 +1,311 @@
pub use super::*;
use crate::dataflow::BottomValue;
use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
use crate::util::storage::AlwaysLiveLocals;
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
use rustc_middle::mir::*;
use std::cell::RefCell;
#[derive(Clone)]
pub struct MaybeStorageLive {
always_live_locals: AlwaysLiveLocals,
}
impl MaybeStorageLive {
pub fn new(always_live_locals: AlwaysLiveLocals) -> Self {
MaybeStorageLive { always_live_locals }
}
}
impl dataflow::AnalysisDomain<'tcx> for MaybeStorageLive {
type Idx = Local;
const NAME: &'static str = "maybe_storage_live";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
assert_eq!(body.local_decls.len(), self.always_live_locals.domain_size());
for local in self.always_live_locals.iter() {
on_entry.insert(local);
}
for arg in body.args_iter() {
on_entry.insert(arg);
}
}
}
impl dataflow::GenKillAnalysis<'tcx> for MaybeStorageLive {
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
stmt: &mir::Statement<'tcx>,
_: Location,
) {
match stmt.kind {
StatementKind::StorageLive(l) => trans.gen(l),
StatementKind::StorageDead(l) => trans.kill(l),
_ => (),
}
}
fn terminator_effect(
&self,
_trans: &mut impl GenKill<Self::Idx>,
_: &mir::Terminator<'tcx>,
_: Location,
) {
// Terminators have no 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>,
) {
// Nothing to do when a call returns successfully
}
}
impl BottomValue for MaybeStorageLive {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
type BorrowedLocalsResults<'a, 'tcx> = ResultsRefCursor<'a, 'a, 'tcx, MaybeBorrowedLocals>;
/// Dataflow analysis that determines whether each local requires storage at a
/// given location; i.e. whether its storage can go away without being observed.
pub struct MaybeRequiresStorage<'mir, 'tcx> {
body: &'mir Body<'tcx>,
borrowed_locals: RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
}
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
pub fn new(
body: &'mir Body<'tcx>,
borrowed_locals: &'mir Results<'tcx, MaybeBorrowedLocals>,
) -> Self {
MaybeRequiresStorage {
body,
borrowed_locals: RefCell::new(ResultsRefCursor::new(&body, borrowed_locals)),
}
}
}
impl<'mir, 'tcx> dataflow::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
type Idx = Local;
const NAME: &'static str = "requires_storage";
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
body.local_decls.len()
}
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
// The resume argument is live on function entry (we don't care about
// the `self` argument)
for arg in body.args_iter().skip(1) {
on_entry.insert(arg);
}
}
}
impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
fn before_statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
stmt: &mir::Statement<'tcx>,
loc: Location,
) {
// If a place is borrowed in a statement, it needs storage for that statement.
self.borrowed_locals.borrow().analysis().statement_effect(trans, stmt, loc);
match &stmt.kind {
StatementKind::StorageDead(l) => trans.kill(*l),
// If a place is assigned to in a statement, it needs storage for that statement.
StatementKind::Assign(box (place, _))
| StatementKind::SetDiscriminant { box place, .. } => {
trans.gen(place.local);
}
StatementKind::LlvmInlineAsm(asm) => {
for place in &*asm.outputs {
trans.gen(place.local);
}
}
// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::FakeRead(..)
| StatementKind::Nop
| StatementKind::Retag(..)
| StatementKind::StorageLive(..) => {}
}
}
fn statement_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_: &mir::Statement<'tcx>,
loc: Location,
) {
// If we move from a place then only stops needing storage *after*
// that statement.
self.check_for_move(trans, loc);
}
fn before_terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
terminator: &mir::Terminator<'tcx>,
loc: Location,
) {
// If a place is borrowed in a terminator, it needs storage for that terminator.
self.borrowed_locals.borrow().analysis().terminator_effect(trans, terminator, loc);
match &terminator.kind {
TerminatorKind::Call { destination: Some((place, _)), .. } => {
trans.gen(place.local);
}
// Note that we do *not* gen the `resume_arg` of `Yield` terminators. The reason for
// that is that a `yield` will return from the function, and `resume_arg` is written
// only when the generator is later resumed. Unlike `Call`, this doesn't require the
// place to have storage *before* the yield, only after.
TerminatorKind::Yield { .. } => {}
TerminatorKind::InlineAsm { operands, .. } => {
for op in operands {
match op {
InlineAsmOperand::Out { place, .. }
| InlineAsmOperand::InOut { out_place: place, .. } => {
if let Some(place) = place {
trans.gen(place.local);
}
}
InlineAsmOperand::In { .. }
| InlineAsmOperand::Const { .. }
| InlineAsmOperand::SymFn { .. }
| InlineAsmOperand::SymStatic { .. } => {}
}
}
}
// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
TerminatorKind::Call { destination: None, .. }
| TerminatorKind::Abort
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {}
}
}
fn terminator_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
terminator: &mir::Terminator<'tcx>,
loc: Location,
) {
match &terminator.kind {
// For call terminators the destination requires storage for the call
// and after the call returns successfully, but not after a panic.
// Since `propagate_call_unwind` doesn't exist, we have to kill the
// destination here, and then gen it again in `call_return_effect`.
TerminatorKind::Call { destination: Some((place, _)), .. } => {
trans.kill(place.local);
}
// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
TerminatorKind::Call { destination: None, .. }
| TerminatorKind::Yield { .. }
| TerminatorKind::Abort
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::Goto { .. }
| TerminatorKind::InlineAsm { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {}
}
self.check_for_move(trans, loc);
}
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>,
) {
trans.gen(return_place.local);
}
fn yield_resume_effect(
&self,
trans: &mut impl GenKill<Self::Idx>,
_resume_block: BasicBlock,
resume_place: mir::Place<'tcx>,
) {
trans.gen(resume_place.local);
}
}
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
/// Kill locals that are fully moved and have not been borrowed.
fn check_for_move(&self, trans: &mut impl GenKill<Local>, loc: Location) {
let mut visitor = MoveVisitor { trans, borrowed_locals: &self.borrowed_locals };
visitor.visit_location(&self.body, loc);
}
}
impl<'mir, 'tcx> BottomValue for MaybeRequiresStorage<'mir, 'tcx> {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
struct MoveVisitor<'a, 'mir, 'tcx, T> {
borrowed_locals: &'a RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
trans: &'a mut T,
}
impl<'a, 'mir, 'tcx, T> Visitor<'tcx> for MoveVisitor<'a, 'mir, 'tcx, T>
where
T: GenKill<Local>,
{
fn visit_local(&mut self, local: &Local, context: PlaceContext, loc: Location) {
if PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) == context {
let mut borrowed_locals = self.borrowed_locals.borrow_mut();
borrowed_locals.seek_before_primary_effect(loc);
if !borrowed_locals.contains(*local) {
self.trans.kill(*local);
}
}
}
}

View file

@ -0,0 +1,49 @@
use rustc_ast::{self as ast, MetaItem};
use rustc_middle::ty;
use rustc_session::Session;
use rustc_span::symbol::{sym, Symbol};
pub(crate) use self::drop_flag_effects::*;
pub use self::framework::{
visit_results, Analysis, AnalysisDomain, Backward, BorrowckFlowState, BorrowckResults,
BottomValue, Engine, Forward, GenKill, GenKillAnalysis, Results, ResultsCursor,
ResultsRefCursor, ResultsVisitor,
};
use self::move_paths::MoveData;
pub mod drop_flag_effects;
mod framework;
pub mod impls;
pub mod move_paths;
pub(crate) mod indexes {
pub(crate) use super::{
impls::borrows::BorrowIndex,
move_paths::{InitIndex, MoveOutIndex, MovePathIndex},
};
}
pub struct MoveDataParamEnv<'tcx> {
pub(crate) move_data: MoveData<'tcx>,
pub(crate) param_env: ty::ParamEnv<'tcx>,
}
pub(crate) fn has_rustc_mir_with(
sess: &Session,
attrs: &[ast::Attribute],
name: Symbol,
) -> Option<MetaItem> {
for attr in attrs {
if sess.check_name(attr, sym::rustc_mir) {
let items = attr.meta_item_list();
for item in items.iter().flat_map(|l| l.iter()) {
match item.meta_item() {
Some(mi) if mi.has_name(name) => return Some(mi.clone()),
_ => continue,
}
}
}
}
None
}

View file

@ -0,0 +1,61 @@
//! The move-analysis portion of borrowck needs to work in an abstract
//! domain of lifted `Place`s. Most of the `Place` variants fall into a
//! one-to-one mapping between the concrete and abstract (e.g., a
//! field-deref on a local variable, `x.field`, has the same meaning
//! in both domains). Indexed projections are the exception: `a[x]`
//! needs to be treated as mapping to the same move path as `a[y]` as
//! well as `a[13]`, etc.
//!
//! (In theory, the analysis could be extended to work with sets of
//! paths, so that `a[0]` and `a[13]` could be kept distinct, while
//! `a[x]` would still overlap them both. But that is not this
//! representation does today.)
use rustc_middle::mir::{Local, Operand, PlaceElem, ProjectionElem};
use rustc_middle::ty::Ty;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct AbstractOperand;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct AbstractType;
pub type AbstractElem = ProjectionElem<AbstractOperand, AbstractType>;
pub trait Lift {
type Abstract;
fn lift(&self) -> Self::Abstract;
}
impl<'tcx> Lift for Operand<'tcx> {
type Abstract = AbstractOperand;
fn lift(&self) -> Self::Abstract {
AbstractOperand
}
}
impl Lift for Local {
type Abstract = AbstractOperand;
fn lift(&self) -> Self::Abstract {
AbstractOperand
}
}
impl<'tcx> Lift for Ty<'tcx> {
type Abstract = AbstractType;
fn lift(&self) -> Self::Abstract {
AbstractType
}
}
impl<'tcx> Lift for PlaceElem<'tcx> {
type Abstract = AbstractElem;
fn lift(&self) -> Self::Abstract {
match *self {
ProjectionElem::Deref => ProjectionElem::Deref,
ProjectionElem::Field(f, ty) => ProjectionElem::Field(f, ty.lift()),
ProjectionElem::Index(ref i) => ProjectionElem::Index(i.lift()),
ProjectionElem::Subslice { from, to, from_end } => {
ProjectionElem::Subslice { from, to, from_end }
}
ProjectionElem::ConstantIndex { offset, min_length, from_end } => {
ProjectionElem::ConstantIndex { offset, min_length, from_end }
}
ProjectionElem::Downcast(a, u) => ProjectionElem::Downcast(a, u),
}
}
}

View file

@ -0,0 +1,552 @@
use rustc_index::vec::IndexVec;
use rustc_middle::mir::tcx::RvalueInitializationState;
use rustc_middle::mir::*;
use rustc_middle::ty::{self, TyCtxt};
use smallvec::{smallvec, SmallVec};
use std::convert::TryInto;
use std::mem;
use super::abs_domain::Lift;
use super::IllegalMoveOriginKind::*;
use super::{Init, InitIndex, InitKind, InitLocation, LookupResult, MoveError};
use super::{
LocationMap, MoveData, MoveOut, MoveOutIndex, MovePath, MovePathIndex, MovePathLookup,
};
struct MoveDataBuilder<'a, 'tcx> {
body: &'a Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
data: MoveData<'tcx>,
errors: Vec<(Place<'tcx>, MoveError<'tcx>)>,
}
impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
fn new(body: &'a Body<'tcx>, tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>) -> Self {
let mut move_paths = IndexVec::new();
let mut path_map = IndexVec::new();
let mut init_path_map = IndexVec::new();
MoveDataBuilder {
body,
tcx,
param_env,
errors: Vec::new(),
data: MoveData {
moves: IndexVec::new(),
loc_map: LocationMap::new(body),
rev_lookup: MovePathLookup {
locals: body
.local_decls
.indices()
.map(|i| {
Self::new_move_path(
&mut move_paths,
&mut path_map,
&mut init_path_map,
None,
Place::from(i),
)
})
.collect(),
projections: Default::default(),
},
move_paths,
path_map,
inits: IndexVec::new(),
init_loc_map: LocationMap::new(body),
init_path_map,
},
}
}
fn new_move_path(
move_paths: &mut IndexVec<MovePathIndex, MovePath<'tcx>>,
path_map: &mut IndexVec<MovePathIndex, SmallVec<[MoveOutIndex; 4]>>,
init_path_map: &mut IndexVec<MovePathIndex, SmallVec<[InitIndex; 4]>>,
parent: Option<MovePathIndex>,
place: Place<'tcx>,
) -> MovePathIndex {
let move_path =
move_paths.push(MovePath { next_sibling: None, first_child: None, parent, place });
if let Some(parent) = parent {
let next_sibling = mem::replace(&mut move_paths[parent].first_child, Some(move_path));
move_paths[move_path].next_sibling = next_sibling;
}
let path_map_ent = path_map.push(smallvec![]);
assert_eq!(path_map_ent, move_path);
let init_path_map_ent = init_path_map.push(smallvec![]);
assert_eq!(init_path_map_ent, move_path);
move_path
}
}
impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
/// This creates a MovePath for a given place, returning an `MovePathError`
/// if that place can't be moved from.
///
/// NOTE: places behind references *do not* get a move path, which is
/// problematic for borrowck.
///
/// Maybe we should have separate "borrowck" and "moveck" modes.
fn move_path_for(&mut self, place: Place<'tcx>) -> Result<MovePathIndex, MoveError<'tcx>> {
debug!("lookup({:?})", place);
let mut base = self.builder.data.rev_lookup.locals[place.local];
// The move path index of the first union that we find. Once this is
// some we stop creating child move paths, since moves from unions
// move the whole thing.
// We continue looking for other move errors though so that moving
// from `*(u.f: &_)` isn't allowed.
let mut union_path = None;
for (i, elem) in place.projection.iter().enumerate() {
let proj_base = &place.projection[..i];
let body = self.builder.body;
let tcx = self.builder.tcx;
let place_ty = Place::ty_from(place.local, proj_base, body, tcx).ty;
match place_ty.kind {
ty::Ref(..) | ty::RawPtr(..) => {
let proj = &place.projection[..i + 1];
return Err(MoveError::cannot_move_out_of(
self.loc,
BorrowedContent {
target_place: Place {
local: place.local,
projection: tcx.intern_place_elems(proj),
},
},
));
}
ty::Adt(adt, _) if adt.has_dtor(tcx) && !adt.is_box() => {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfTypeWithDestructor { container_ty: place_ty },
));
}
ty::Adt(adt, _) if adt.is_union() => {
union_path.get_or_insert(base);
}
ty::Slice(_) => {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfSliceOrArray {
ty: place_ty,
is_index: match elem {
ProjectionElem::Index(..) => true,
_ => false,
},
},
));
}
ty::Array(..) => {
if let ProjectionElem::Index(..) = elem {
return Err(MoveError::cannot_move_out_of(
self.loc,
InteriorOfSliceOrArray { ty: place_ty, is_index: true },
));
}
}
_ => {}
};
if union_path.is_none() {
base = self.add_move_path(base, elem, |tcx| Place {
local: place.local,
projection: tcx.intern_place_elems(&place.projection[..i + 1]),
});
}
}
if let Some(base) = union_path {
// Move out of union - always move the entire union.
Err(MoveError::UnionMove { path: base })
} else {
Ok(base)
}
}
fn add_move_path(
&mut self,
base: MovePathIndex,
elem: PlaceElem<'tcx>,
mk_place: impl FnOnce(TyCtxt<'tcx>) -> Place<'tcx>,
) -> MovePathIndex {
let MoveDataBuilder {
data: MoveData { rev_lookup, move_paths, path_map, init_path_map, .. },
tcx,
..
} = self.builder;
*rev_lookup.projections.entry((base, elem.lift())).or_insert_with(move || {
MoveDataBuilder::new_move_path(
move_paths,
path_map,
init_path_map,
Some(base),
mk_place(*tcx),
)
})
}
fn create_move_path(&mut self, place: Place<'tcx>) {
// This is an non-moving access (such as an overwrite or
// drop), so this not being a valid move path is OK.
let _ = self.move_path_for(place);
}
}
impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
fn finalize(
self,
) -> Result<MoveData<'tcx>, (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>)> {
debug!("{}", {
debug!("moves for {:?}:", self.body.span);
for (j, mo) in self.data.moves.iter_enumerated() {
debug!(" {:?} = {:?}", j, mo);
}
debug!("move paths for {:?}:", self.body.span);
for (j, path) in self.data.move_paths.iter_enumerated() {
debug!(" {:?} = {:?}", j, path);
}
"done dumping moves"
});
if !self.errors.is_empty() { Err((self.data, self.errors)) } else { Ok(self.data) }
}
}
pub(super) fn gather_moves<'tcx>(
body: &Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
) -> Result<MoveData<'tcx>, (MoveData<'tcx>, Vec<(Place<'tcx>, MoveError<'tcx>)>)> {
let mut builder = MoveDataBuilder::new(body, tcx, param_env);
builder.gather_args();
for (bb, block) in body.basic_blocks().iter_enumerated() {
for (i, stmt) in block.statements.iter().enumerate() {
let source = Location { block: bb, statement_index: i };
builder.gather_statement(source, stmt);
}
let terminator_loc = Location { block: bb, statement_index: block.statements.len() };
builder.gather_terminator(terminator_loc, block.terminator());
}
builder.finalize()
}
impl<'a, 'tcx> MoveDataBuilder<'a, 'tcx> {
fn gather_args(&mut self) {
for arg in self.body.args_iter() {
let path = self.data.rev_lookup.locals[arg];
let init = self.data.inits.push(Init {
path,
kind: InitKind::Deep,
location: InitLocation::Argument(arg),
});
debug!("gather_args: adding init {:?} of {:?} for argument {:?}", init, path, arg);
self.data.init_path_map[path].push(init);
}
}
fn gather_statement(&mut self, loc: Location, stmt: &Statement<'tcx>) {
debug!("gather_statement({:?}, {:?})", loc, stmt);
(Gatherer { builder: self, loc }).gather_statement(stmt);
}
fn gather_terminator(&mut self, loc: Location, term: &Terminator<'tcx>) {
debug!("gather_terminator({:?}, {:?})", loc, term);
(Gatherer { builder: self, loc }).gather_terminator(term);
}
}
struct Gatherer<'b, 'a, 'tcx> {
builder: &'b mut MoveDataBuilder<'a, 'tcx>,
loc: Location,
}
impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
fn gather_statement(&mut self, stmt: &Statement<'tcx>) {
match &stmt.kind {
StatementKind::Assign(box (place, rval)) => {
self.create_move_path(*place);
if let RvalueInitializationState::Shallow = rval.initialization_state() {
// Box starts out uninitialized - need to create a separate
// move-path for the interior so it will be separate from
// the exterior.
self.create_move_path(self.builder.tcx.mk_place_deref(*place));
self.gather_init(place.as_ref(), InitKind::Shallow);
} else {
self.gather_init(place.as_ref(), InitKind::Deep);
}
self.gather_rvalue(rval);
}
StatementKind::FakeRead(_, place) => {
self.create_move_path(**place);
}
StatementKind::LlvmInlineAsm(ref asm) => {
for (output, kind) in asm.outputs.iter().zip(&asm.asm.outputs) {
if !kind.is_indirect {
self.gather_init(output.as_ref(), InitKind::Deep);
}
}
for (_, input) in asm.inputs.iter() {
self.gather_operand(input);
}
}
StatementKind::StorageLive(_) => {}
StatementKind::StorageDead(local) => {
self.gather_move(Place::from(*local));
}
StatementKind::SetDiscriminant { .. } => {
span_bug!(
stmt.source_info.span,
"SetDiscriminant should not exist during borrowck"
);
}
StatementKind::Retag { .. }
| StatementKind::AscribeUserType(..)
| StatementKind::Coverage(..)
| StatementKind::Nop => {}
}
}
fn gather_rvalue(&mut self, rvalue: &Rvalue<'tcx>) {
match *rvalue {
Rvalue::ThreadLocalRef(_) => {} // not-a-move
Rvalue::Use(ref operand)
| Rvalue::Repeat(ref operand, _)
| Rvalue::Cast(_, ref operand, _)
| Rvalue::UnaryOp(_, ref operand) => self.gather_operand(operand),
Rvalue::BinaryOp(ref _binop, ref lhs, ref rhs)
| Rvalue::CheckedBinaryOp(ref _binop, ref lhs, ref rhs) => {
self.gather_operand(lhs);
self.gather_operand(rhs);
}
Rvalue::Aggregate(ref _kind, ref operands) => {
for operand in operands {
self.gather_operand(operand);
}
}
Rvalue::Ref(..)
| Rvalue::AddressOf(..)
| Rvalue::Discriminant(..)
| Rvalue::Len(..)
| Rvalue::NullaryOp(NullOp::SizeOf, _)
| Rvalue::NullaryOp(NullOp::Box, _) => {
// This returns an rvalue with uninitialized contents. We can't
// move out of it here because it is an rvalue - assignments always
// completely initialize their place.
//
// However, this does not matter - MIR building is careful to
// only emit a shallow free for the partially-initialized
// temporary.
//
// In any case, if we want to fix this, we have to register a
// special move and change the `statement_effect` functions.
}
}
}
fn gather_terminator(&mut self, term: &Terminator<'tcx>) {
match term.kind {
TerminatorKind::Goto { target: _ }
| TerminatorKind::Resume
| TerminatorKind::Abort
| TerminatorKind::GeneratorDrop
| TerminatorKind::FalseEdge { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::Unreachable => {}
TerminatorKind::Return => {
self.gather_move(Place::return_place());
}
TerminatorKind::Assert { ref cond, .. } => {
self.gather_operand(cond);
}
TerminatorKind::SwitchInt { ref discr, .. } => {
self.gather_operand(discr);
}
TerminatorKind::Yield { ref value, resume_arg: place, .. } => {
self.gather_operand(value);
self.create_move_path(place);
self.gather_init(place.as_ref(), InitKind::Deep);
}
TerminatorKind::Drop { place, target: _, unwind: _ } => {
self.gather_move(place);
}
TerminatorKind::DropAndReplace { place, ref value, .. } => {
self.create_move_path(place);
self.gather_operand(value);
self.gather_init(place.as_ref(), InitKind::Deep);
}
TerminatorKind::Call {
ref func,
ref args,
ref destination,
cleanup: _,
from_hir_call: _,
fn_span: _,
} => {
self.gather_operand(func);
for arg in args {
self.gather_operand(arg);
}
if let Some((destination, _bb)) = *destination {
self.create_move_path(destination);
self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly);
}
}
TerminatorKind::InlineAsm {
template: _,
ref operands,
options: _,
line_spans: _,
destination: _,
} => {
for op in operands {
match *op {
InlineAsmOperand::In { reg: _, ref value }
| InlineAsmOperand::Const { ref value } => {
self.gather_operand(value);
}
InlineAsmOperand::Out { reg: _, late: _, place, .. } => {
if let Some(place) = place {
self.create_move_path(place);
self.gather_init(place.as_ref(), InitKind::Deep);
}
}
InlineAsmOperand::InOut { reg: _, late: _, ref in_value, out_place } => {
self.gather_operand(in_value);
if let Some(out_place) = out_place {
self.create_move_path(out_place);
self.gather_init(out_place.as_ref(), InitKind::Deep);
}
}
InlineAsmOperand::SymFn { value: _ }
| InlineAsmOperand::SymStatic { def_id: _ } => {}
}
}
}
}
}
fn gather_operand(&mut self, operand: &Operand<'tcx>) {
match *operand {
Operand::Constant(..) | Operand::Copy(..) => {} // not-a-move
Operand::Move(place) => {
// a move
self.gather_move(place);
}
}
}
fn gather_move(&mut self, place: Place<'tcx>) {
debug!("gather_move({:?}, {:?})", self.loc, place);
if let [ref base @ .., ProjectionElem::Subslice { from, to, from_end: false }] =
**place.projection
{
// Split `Subslice` patterns into the corresponding list of
// `ConstIndex` patterns. This is done to ensure that all move paths
// are disjoint, which is expected by drop elaboration.
let base_place =
Place { local: place.local, projection: self.builder.tcx.intern_place_elems(base) };
let base_path = match self.move_path_for(base_place) {
Ok(path) => path,
Err(MoveError::UnionMove { path }) => {
self.record_move(place, path);
return;
}
Err(error @ MoveError::IllegalMove { .. }) => {
self.builder.errors.push((base_place, error));
return;
}
};
let base_ty = base_place.ty(self.builder.body, self.builder.tcx).ty;
let len: u64 = match base_ty.kind {
ty::Array(_, size) => {
let length = size.eval_usize(self.builder.tcx, self.builder.param_env);
length
.try_into()
.expect("slice pattern of array with more than u32::MAX elements")
}
_ => bug!("from_end: false slice pattern of non-array type"),
};
for offset in from..to {
let elem =
ProjectionElem::ConstantIndex { offset, min_length: len, from_end: false };
let path =
self.add_move_path(base_path, elem, |tcx| tcx.mk_place_elem(base_place, elem));
self.record_move(place, path);
}
} else {
match self.move_path_for(place) {
Ok(path) | Err(MoveError::UnionMove { path }) => self.record_move(place, path),
Err(error @ MoveError::IllegalMove { .. }) => {
self.builder.errors.push((place, error));
}
};
}
}
fn record_move(&mut self, place: Place<'tcx>, path: MovePathIndex) {
let move_out = self.builder.data.moves.push(MoveOut { path, source: self.loc });
debug!(
"gather_move({:?}, {:?}): adding move {:?} of {:?}",
self.loc, place, move_out, path
);
self.builder.data.path_map[path].push(move_out);
self.builder.data.loc_map[self.loc].push(move_out);
}
fn gather_init(&mut self, place: PlaceRef<'tcx>, kind: InitKind) {
debug!("gather_init({:?}, {:?})", self.loc, place);
let mut place = place;
// Check if we are assigning into a field of a union, if so, lookup the place
// of the union so it is marked as initialized again.
if let [proj_base @ .., ProjectionElem::Field(_, _)] = place.projection {
if let ty::Adt(def, _) =
Place::ty_from(place.local, proj_base, self.builder.body, self.builder.tcx).ty.kind
{
if def.is_union() {
place = PlaceRef { local: place.local, projection: proj_base }
}
}
}
if let LookupResult::Exact(path) = self.builder.data.rev_lookup.find(place) {
let init = self.builder.data.inits.push(Init {
location: InitLocation::Statement(self.loc),
path,
kind,
});
debug!(
"gather_init({:?}, {:?}): adding init {:?} of {:?}",
self.loc, place, init, path
);
self.builder.data.init_path_map[path].push(init);
self.builder.data.init_loc_map[self.loc].push(init);
}
}
}

View file

@ -0,0 +1,415 @@
use core::slice::Iter;
use rustc_data_structures::fx::FxHashMap;
use rustc_index::vec::{Enumerated, IndexVec};
use rustc_middle::mir::*;
use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
use rustc_span::Span;
use smallvec::SmallVec;
use std::fmt;
use std::ops::{Index, IndexMut};
use self::abs_domain::{AbstractElem, Lift};
mod abs_domain;
rustc_index::newtype_index! {
pub struct MovePathIndex {
DEBUG_FORMAT = "mp{}"
}
}
rustc_index::newtype_index! {
pub struct MoveOutIndex {
DEBUG_FORMAT = "mo{}"
}
}
rustc_index::newtype_index! {
pub struct InitIndex {
DEBUG_FORMAT = "in{}"
}
}
impl MoveOutIndex {
pub fn move_path_index(&self, move_data: &MoveData<'_>) -> MovePathIndex {
move_data.moves[*self].path
}
}
/// `MovePath` is a canonicalized representation of a path that is
/// moved or assigned to.
///
/// It follows a tree structure.
///
/// Given `struct X { m: M, n: N }` and `x: X`, moves like `drop x.m;`
/// move *out* of the place `x.m`.
///
/// The MovePaths representing `x.m` and `x.n` are siblings (that is,
/// one of them will link to the other via the `next_sibling` field,
/// and the other will have no entry in its `next_sibling` field), and
/// they both have the MovePath representing `x` as their parent.
#[derive(Clone)]
pub struct MovePath<'tcx> {
pub next_sibling: Option<MovePathIndex>,
pub first_child: Option<MovePathIndex>,
pub parent: Option<MovePathIndex>,
pub place: Place<'tcx>,
}
impl<'tcx> MovePath<'tcx> {
/// Returns an iterator over the parents of `self`.
pub fn parents<'a>(
&self,
move_paths: &'a IndexVec<MovePathIndex, MovePath<'tcx>>,
) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)> {
let first = self.parent.map(|mpi| (mpi, &move_paths[mpi]));
MovePathLinearIter {
next: first,
fetch_next: move |_, parent: &MovePath<'_>| {
parent.parent.map(|mpi| (mpi, &move_paths[mpi]))
},
}
}
/// Returns an iterator over the immediate children of `self`.
pub fn children<'a>(
&self,
move_paths: &'a IndexVec<MovePathIndex, MovePath<'tcx>>,
) -> impl 'a + Iterator<Item = (MovePathIndex, &'a MovePath<'tcx>)> {
let first = self.first_child.map(|mpi| (mpi, &move_paths[mpi]));
MovePathLinearIter {
next: first,
fetch_next: move |_, child: &MovePath<'_>| {
child.next_sibling.map(|mpi| (mpi, &move_paths[mpi]))
},
}
}
/// Finds the closest descendant of `self` for which `f` returns `true` using a breadth-first
/// search.
///
/// `f` will **not** be called on `self`.
pub fn find_descendant(
&self,
move_paths: &IndexVec<MovePathIndex, MovePath<'_>>,
f: impl Fn(MovePathIndex) -> bool,
) -> Option<MovePathIndex> {
let mut todo = if let Some(child) = self.first_child {
vec![child]
} else {
return None;
};
while let Some(mpi) = todo.pop() {
if f(mpi) {
return Some(mpi);
}
let move_path = &move_paths[mpi];
if let Some(child) = move_path.first_child {
todo.push(child);
}
// After we've processed the original `mpi`, we should always
// traverse the siblings of any of its children.
if let Some(sibling) = move_path.next_sibling {
todo.push(sibling);
}
}
None
}
}
impl<'tcx> fmt::Debug for MovePath<'tcx> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(w, "MovePath {{")?;
if let Some(parent) = self.parent {
write!(w, " parent: {:?},", parent)?;
}
if let Some(first_child) = self.first_child {
write!(w, " first_child: {:?},", first_child)?;
}
if let Some(next_sibling) = self.next_sibling {
write!(w, " next_sibling: {:?}", next_sibling)?;
}
write!(w, " place: {:?} }}", self.place)
}
}
impl<'tcx> fmt::Display for MovePath<'tcx> {
fn fmt(&self, w: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(w, "{:?}", self.place)
}
}
#[allow(unused)]
struct MovePathLinearIter<'a, 'tcx, F> {
next: Option<(MovePathIndex, &'a MovePath<'tcx>)>,
fetch_next: F,
}
impl<'a, 'tcx, F> Iterator for MovePathLinearIter<'a, 'tcx, F>
where
F: FnMut(MovePathIndex, &'a MovePath<'tcx>) -> Option<(MovePathIndex, &'a MovePath<'tcx>)>,
{
type Item = (MovePathIndex, &'a MovePath<'tcx>);
fn next(&mut self) -> Option<Self::Item> {
let ret = self.next.take()?;
self.next = (self.fetch_next)(ret.0, ret.1);
Some(ret)
}
}
#[derive(Debug)]
pub struct MoveData<'tcx> {
pub move_paths: IndexVec<MovePathIndex, MovePath<'tcx>>,
pub moves: IndexVec<MoveOutIndex, MoveOut>,
/// Each Location `l` is mapped to the MoveOut's that are effects
/// of executing the code at `l`. (There can be multiple MoveOut's
/// for a given `l` because each MoveOut is associated with one
/// particular path being moved.)
pub loc_map: LocationMap<SmallVec<[MoveOutIndex; 4]>>,
pub path_map: IndexVec<MovePathIndex, SmallVec<[MoveOutIndex; 4]>>,
pub rev_lookup: MovePathLookup,
pub inits: IndexVec<InitIndex, Init>,
/// Each Location `l` is mapped to the Inits that are effects
/// of executing the code at `l`.
pub init_loc_map: LocationMap<SmallVec<[InitIndex; 4]>>,
pub init_path_map: IndexVec<MovePathIndex, SmallVec<[InitIndex; 4]>>,
}
pub trait HasMoveData<'tcx> {
fn move_data(&self) -> &MoveData<'tcx>;
}
#[derive(Debug)]
pub struct LocationMap<T> {
/// Location-indexed (BasicBlock for outer index, index within BB
/// for inner index) map.
pub(crate) map: IndexVec<BasicBlock, Vec<T>>,
}
impl<T> Index<Location> for LocationMap<T> {
type Output = T;
fn index(&self, index: Location) -> &Self::Output {
&self.map[index.block][index.statement_index]
}
}
impl<T> IndexMut<Location> for LocationMap<T> {
fn index_mut(&mut self, index: Location) -> &mut Self::Output {
&mut self.map[index.block][index.statement_index]
}
}
impl<T> LocationMap<T>
where
T: Default + Clone,
{
fn new(body: &Body<'_>) -> Self {
LocationMap {
map: body
.basic_blocks()
.iter()
.map(|block| vec![T::default(); block.statements.len() + 1])
.collect(),
}
}
}
/// `MoveOut` represents a point in a program that moves out of some
/// L-value; i.e., "creates" uninitialized memory.
///
/// With respect to dataflow analysis:
/// - Generated by moves and declaration of uninitialized variables.
/// - Killed by assignments to the memory.
#[derive(Copy, Clone)]
pub struct MoveOut {
/// path being moved
pub path: MovePathIndex,
/// location of move
pub source: Location,
}
impl fmt::Debug for MoveOut {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{:?}@{:?}", self.path, self.source)
}
}
/// `Init` represents a point in a program that initializes some L-value;
#[derive(Copy, Clone)]
pub struct Init {
/// path being initialized
pub path: MovePathIndex,
/// location of initialization
pub location: InitLocation,
/// Extra information about this initialization
pub kind: InitKind,
}
/// Initializations can be from an argument or from a statement. Arguments
/// do not have locations, in those cases the `Local` is kept..
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InitLocation {
Argument(Local),
Statement(Location),
}
/// Additional information about the initialization.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum InitKind {
/// Deep init, even on panic
Deep,
/// Only does a shallow init
Shallow,
/// This doesn't initialize the variable on panic (and a panic is possible).
NonPanicPathOnly,
}
impl fmt::Debug for Init {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{:?}@{:?} ({:?})", self.path, self.location, self.kind)
}
}
impl Init {
crate fn span<'tcx>(&self, body: &Body<'tcx>) -> Span {
match self.location {
InitLocation::Argument(local) => body.local_decls[local].source_info.span,
InitLocation::Statement(location) => body.source_info(location).span,
}
}
}
/// Tables mapping from a place to its MovePathIndex.
#[derive(Debug)]
pub struct MovePathLookup {
locals: IndexVec<Local, MovePathIndex>,
/// projections are made from a base-place and a projection
/// elem. The base-place will have a unique MovePathIndex; we use
/// the latter as the index into the outer vector (narrowing
/// subsequent search so that it is solely relative to that
/// base-place). For the remaining lookup, we map the projection
/// elem to the associated MovePathIndex.
projections: FxHashMap<(MovePathIndex, AbstractElem), MovePathIndex>,
}
mod builder;
#[derive(Copy, Clone, Debug)]
pub enum LookupResult {
Exact(MovePathIndex),
Parent(Option<MovePathIndex>),
}
impl MovePathLookup {
// Unlike the builder `fn move_path_for` below, this lookup
// alternative will *not* create a MovePath on the fly for an
// unknown place, but will rather return the nearest available
// parent.
pub fn find(&self, place: PlaceRef<'_>) -> LookupResult {
let mut result = self.locals[place.local];
for elem in place.projection.iter() {
if let Some(&subpath) = self.projections.get(&(result, elem.lift())) {
result = subpath;
} else {
return LookupResult::Parent(Some(result));
}
}
LookupResult::Exact(result)
}
pub fn find_local(&self, local: Local) -> MovePathIndex {
self.locals[local]
}
/// An enumerated iterator of `local`s and their associated
/// `MovePathIndex`es.
pub fn iter_locals_enumerated(&self) -> Enumerated<Local, Iter<'_, MovePathIndex>> {
self.locals.iter_enumerated()
}
}
#[derive(Debug)]
pub struct IllegalMoveOrigin<'tcx> {
pub(crate) location: Location,
pub(crate) kind: IllegalMoveOriginKind<'tcx>,
}
#[derive(Debug)]
pub(crate) enum IllegalMoveOriginKind<'tcx> {
/// Illegal move due to attempt to move from behind a reference.
BorrowedContent {
/// The place the reference refers to: if erroneous code was trying to
/// move from `(*x).f` this will be `*x`.
target_place: Place<'tcx>,
},
/// Illegal move due to attempt to move from field of an ADT that
/// implements `Drop`. Rust maintains invariant that all `Drop`
/// ADT's remain fully-initialized so that user-defined destructor
/// can safely read from all of the ADT's fields.
InteriorOfTypeWithDestructor { container_ty: Ty<'tcx> },
/// Illegal move due to attempt to move out of a slice or array.
InteriorOfSliceOrArray { ty: Ty<'tcx>, is_index: bool },
}
#[derive(Debug)]
pub enum MoveError<'tcx> {
IllegalMove { cannot_move_out_of: IllegalMoveOrigin<'tcx> },
UnionMove { path: MovePathIndex },
}
impl<'tcx> MoveError<'tcx> {
fn cannot_move_out_of(location: Location, kind: IllegalMoveOriginKind<'tcx>) -> Self {
let origin = IllegalMoveOrigin { location, kind };
MoveError::IllegalMove { cannot_move_out_of: origin }
}
}
impl<'tcx> MoveData<'tcx> {
pub fn gather_moves(
body: &Body<'tcx>,
tcx: TyCtxt<'tcx>,
param_env: ParamEnv<'tcx>,
) -> Result<Self, (Self, Vec<(Place<'tcx>, MoveError<'tcx>)>)> {
builder::gather_moves(body, tcx, param_env)
}
/// For the move path `mpi`, returns the root local variable (if any) that starts the path.
/// (e.g., for a path like `a.b.c` returns `Some(a)`)
pub fn base_local(&self, mut mpi: MovePathIndex) -> Option<Local> {
loop {
let path = &self.move_paths[mpi];
if let Some(l) = path.place.as_local() {
return Some(l);
}
if let Some(parent) = path.parent {
mpi = parent;
continue;
} else {
return None;
}
}
}
pub fn find_in_move_path_or_its_descendants(
&self,
root: MovePathIndex,
pred: impl Fn(MovePathIndex) -> bool,
) -> Option<MovePathIndex> {
if pred(root) {
return Some(root);
}
self.move_paths[root].find_descendant(&self.move_paths, pred)
}
}