2017-09-10 22:34:56 +02:00
|
|
|
pub use super::*;
|
|
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
use crate::dataflow::BottomValue;
|
2020-06-08 14:50:39 -07:00
|
|
|
use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
|
2020-03-29 14:16:29 -07:00
|
|
|
use crate::util::storage::AlwaysLiveLocals;
|
2020-06-08 14:50:39 -07:00
|
|
|
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
|
2020-03-29 16:41:09 +02:00
|
|
|
use rustc_middle::mir::*;
|
2020-06-08 14:50:39 -07:00
|
|
|
use std::cell::RefCell;
|
2017-09-10 22:34:56 +02:00
|
|
|
|
2020-03-29 14:16:29 -07:00
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub struct MaybeStorageLive {
|
|
|
|
|
always_live_locals: AlwaysLiveLocals,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MaybeStorageLive {
|
|
|
|
|
pub fn new(always_live_locals: AlwaysLiveLocals) -> Self {
|
|
|
|
|
MaybeStorageLive { always_live_locals }
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-10 22:34:56 +02:00
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
impl dataflow::AnalysisDomain<'tcx> for MaybeStorageLive {
|
|
|
|
|
type Idx = Local;
|
2017-09-10 22:34:56 +02:00
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
const NAME: &'static str = "maybe_storage_live";
|
2017-09-10 22:34:56 +02:00
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
|
|
|
|
|
body.local_decls.len()
|
2017-09-10 22:34:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
|
2020-03-29 14:16:29 -07:00
|
|
|
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() {
|
2020-01-25 20:03:10 +01:00
|
|
|
on_entry.insert(arg);
|
|
|
|
|
}
|
2017-09-10 22:34:56 +02:00
|
|
|
}
|
2020-02-18 10:03:00 -08:00
|
|
|
}
|
2017-09-10 22:34:56 +02:00
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
impl dataflow::GenKillAnalysis<'tcx> for MaybeStorageLive {
|
|
|
|
|
fn statement_effect(
|
|
|
|
|
&self,
|
|
|
|
|
trans: &mut impl GenKill<Self::Idx>,
|
|
|
|
|
stmt: &mir::Statement<'tcx>,
|
|
|
|
|
_: Location,
|
|
|
|
|
) {
|
2017-09-10 22:34:56 +02:00
|
|
|
match stmt.kind {
|
rustc_mir: don't pass `on_entry` when building transfer functions.
This commit makes `sets.on_entry` inaccessible in
`{before_,}{statement,terminator}_effect`. This field was meant to allow
implementors of `BitDenotation` to access the initial state for each
block (optionally with the effect of all previous statements applied via
`accumulates_intrablock_state`) while defining transfer functions.
However, the ability to set the initial value for the entry set of each
basic block (except for START_BLOCK) no longer exists. As a result, this
functionality is mostly useless, and when it *was* used it was used
erroneously (see #62007).
Since `on_entry` is now useless, we can also remove `BlockSets`, which
held the `gen`, `kill`, and `on_entry` bitvectors and replace it with a
`GenKill` struct. Variables of this type are called `trans` since they
represent a transfer function. `GenKill`s are stored contiguously in
`AllSets`, which reduces the number of bounds checks and may improve
cache performance: one is almost never accessed without the other.
Replacing `BlockSets` with `GenKill` allows us to define some new helper
functions which streamline dataflow iteration and the
dataflow-at-location APIs. Notably, `state_for_location` used a subtle
side-effect of the `kill`/`kill_all` setters to apply the transfer
function, and could be incorrect if a transfer function depended on
effects of previous statements in the block on `gen_set`.
2019-06-12 15:00:43 -07:00
|
|
|
StatementKind::StorageLive(l) => trans.gen(l),
|
|
|
|
|
StatementKind::StorageDead(l) => trans.kill(l),
|
2017-09-10 22:34:56 +02:00
|
|
|
_ => (),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
fn terminator_effect(
|
|
|
|
|
&self,
|
|
|
|
|
_trans: &mut impl GenKill<Self::Idx>,
|
|
|
|
|
_: &mir::Terminator<'tcx>,
|
|
|
|
|
_: Location,
|
|
|
|
|
) {
|
2019-05-30 13:28:31 -07:00
|
|
|
// Terminators have no effect
|
2017-09-10 22:34:56 +02:00
|
|
|
}
|
|
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
fn call_return_effect(
|
2018-12-17 17:26:24 +01:00
|
|
|
&self,
|
2020-02-18 10:03:00 -08:00
|
|
|
_trans: &mut impl GenKill<Self::Idx>,
|
|
|
|
|
_block: BasicBlock,
|
|
|
|
|
_func: &mir::Operand<'tcx>,
|
|
|
|
|
_args: &[mir::Operand<'tcx>],
|
2020-03-31 13:54:20 -03:00
|
|
|
_return_place: mir::Place<'tcx>,
|
2018-12-17 17:26:24 +01:00
|
|
|
) {
|
2017-09-10 22:34:56 +02:00
|
|
|
// Nothing to do when a call returns successfully
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-18 10:03:00 -08:00
|
|
|
impl BottomValue for MaybeStorageLive {
|
2019-06-21 12:39:01 -07:00
|
|
|
/// bottom = dead
|
|
|
|
|
const BOTTOM_VALUE: bool = false;
|
2017-09-10 22:34:56 +02:00
|
|
|
}
|
2020-06-08 14:50:39 -07:00
|
|
|
|
|
|
|
|
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(..)
|
2020-08-15 04:42:13 -07:00
|
|
|
| StatementKind::Coverage(..)
|
2020-06-08 14:50:39 -07:00
|
|
|
| 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|