mv compiler to compiler/
This commit is contained in:
parent
db534b3ac2
commit
9e5f7d5631
1686 changed files with 941 additions and 1051 deletions
276
compiler/rustc_mir/src/dataflow/impls/borrowed_locals.rs
Normal file
276
compiler/rustc_mir/src/dataflow/impls/borrowed_locals.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
350
compiler/rustc_mir/src/dataflow/impls/borrows.rs
Normal file
350
compiler/rustc_mir/src/dataflow/impls/borrows.rs
Normal 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;
|
||||
}
|
116
compiler/rustc_mir/src/dataflow/impls/init_locals.rs
Normal file
116
compiler/rustc_mir/src/dataflow/impls/init_locals.rs
Normal 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,
|
||||
) => {}
|
||||
}
|
||||
}
|
||||
}
|
167
compiler/rustc_mir/src/dataflow/impls/liveness.rs
Normal file
167
compiler/rustc_mir/src/dataflow/impls/liveness.rs
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
647
compiler/rustc_mir/src/dataflow/impls/mod.rs
Normal file
647
compiler/rustc_mir/src/dataflow/impls/mod.rs
Normal 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;
|
||||
}
|
311
compiler/rustc_mir/src/dataflow/impls/storage_liveness.rs
Normal file
311
compiler/rustc_mir/src/dataflow/impls/storage_liveness.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue