1
Fork 0

Add RequiresStorage pass to decide which locals to save in generators

This avoids reserving storage in generators for locals that are moved
out of (and not re-initialized) prior to yield points.
This commit is contained in:
Tyler Mandry 2019-06-17 19:08:12 -07:00
parent 4a8a552b49
commit 99694177f9
4 changed files with 165 additions and 26 deletions

View file

@ -61,6 +61,7 @@ pub trait FlowsAtLocation {
/// (e.g., via `reconstruct_statement_effect` and
/// `reconstruct_terminator_effect`; don't forget to call
/// `apply_local_effect`).
#[derive(Clone)]
pub struct FlowAtLocation<'tcx, BD, DR = DataflowResults<'tcx, BD>>
where
BD: BitDenotation<'tcx>,

View file

@ -1,7 +1,13 @@
pub use super::*;
use rustc::mir::*;
use rustc::mir::visit::{
PlaceContext, Visitor, NonMutatingUseContext,
};
use std::cell::RefCell;
use crate::dataflow::BitDenotation;
use crate::dataflow::HaveBeenBorrowedLocals;
use crate::dataflow::{DataflowResults, DataflowResultsCursor, DataflowResultsRefCursor};
#[derive(Copy, Clone)]
pub struct MaybeStorageLive<'a, 'tcx> {
@ -63,3 +69,126 @@ impl<'a, 'tcx> BottomValue for MaybeStorageLive<'a, 'tcx> {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
/// Dataflow analysis that determines whether each local requires storage at a
/// given location; i.e. whether its storage can go away without being observed.
///
/// In the case of a movable generator, borrowed_locals can be `None` and we
/// will not consider borrows in this pass. This relies on the fact that we only
/// use this pass at yield points for these generators.
#[derive(Clone)]
pub struct RequiresStorage<'mir, 'tcx, 'b> {
body: &'mir Body<'tcx>,
borrowed_locals:
RefCell<DataflowResultsRefCursor<'mir, 'tcx, 'b, HaveBeenBorrowedLocals<'mir, 'tcx>>>,
}
impl<'mir, 'tcx: 'mir, 'b> RequiresStorage<'mir, 'tcx, 'b> {
pub fn new(
body: &'mir Body<'tcx>,
borrowed_locals: &'b DataflowResults<'tcx, HaveBeenBorrowedLocals<'mir, 'tcx>>,
) -> Self {
RequiresStorage {
body,
borrowed_locals: RefCell::new(DataflowResultsCursor::new(borrowed_locals, body)),
}
}
pub fn body(&self) -> &Body<'tcx> {
self.body
}
}
impl<'mir, 'tcx, 'b> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx, 'b> {
type Idx = Local;
fn name() -> &'static str { "requires_storage" }
fn bits_per_block(&self) -> usize {
self.body.local_decls.len()
}
fn start_block_effect(&self, _sets: &mut BitSet<Local>) {
// Nothing is live on function entry
}
fn statement_effect(&self,
sets: &mut GenKillSet<Local>,
loc: Location) {
self.check_for_move(sets, loc);
self.check_for_borrow(sets, loc);
let stmt = &self.body[loc.block].statements[loc.statement_index];
match stmt.kind {
StatementKind::StorageLive(l) => sets.gen(l),
StatementKind::StorageDead(l) => sets.kill(l),
StatementKind::Assign(ref place, _)
| StatementKind::SetDiscriminant { ref place, .. } => {
place.base_local().map(|l| sets.gen(l));
}
StatementKind::InlineAsm(box InlineAsm { ref outputs, .. }) => {
for p in &**outputs {
p.base_local().map(|l| sets.gen(l));
}
}
_ => (),
}
}
fn terminator_effect(&self,
sets: &mut GenKillSet<Local>,
loc: Location) {
self.check_for_move(sets, loc);
self.check_for_borrow(sets, loc);
}
fn propagate_call_return(
&self,
in_out: &mut BitSet<Local>,
_call_bb: mir::BasicBlock,
_dest_bb: mir::BasicBlock,
dest_place: &mir::Place<'tcx>,
) {
dest_place.base_local().map(|l| in_out.insert(l));
}
}
impl<'mir, 'tcx, 'b> RequiresStorage<'mir, 'tcx, 'b> {
/// Kill locals that are fully moved and have not been borrowed.
fn check_for_move(&self, sets: &mut GenKillSet<Local>, loc: Location) {
let mut visitor = MoveVisitor {
sets,
borrowed_locals: &self.borrowed_locals,
};
visitor.visit_location(self.body, loc);
}
/// Gen locals that are newly borrowed. This includes borrowing any part of
/// a local (we rely on this behavior of `HaveBeenBorrowedLocals`).
fn check_for_borrow(&self, sets: &mut GenKillSet<Local>, loc: Location) {
let mut borrowed_locals = self.borrowed_locals.borrow_mut();
borrowed_locals.seek(loc);
borrowed_locals.each_gen_bit(|l| sets.gen(l));
}
}
impl<'mir, 'tcx, 'b> BottomValue for RequiresStorage<'mir, 'tcx, 'b> {
/// bottom = dead
const BOTTOM_VALUE: bool = false;
}
struct MoveVisitor<'a, 'b, 'mir, 'tcx> {
borrowed_locals:
&'a RefCell<DataflowResultsRefCursor<'mir, 'tcx, 'b, HaveBeenBorrowedLocals<'mir, 'tcx>>>,
sets: &'a mut GenKillSet<Local>,
}
impl<'a, 'b, 'mir: 'a, 'tcx> Visitor<'tcx> for MoveVisitor<'a, 'b, 'mir, 'tcx> {
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(loc);
if !borrowed_locals.contains(*local) {
self.sets.kill(*local);
}
}
}
}

View file

@ -17,7 +17,7 @@ use std::io;
use std::path::PathBuf;
use std::usize;
pub use self::impls::{MaybeStorageLive};
pub use self::impls::{MaybeStorageLive, RequiresStorage};
pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
pub use self::impls::DefinitelyInitializedPlaces;
pub use self::impls::EverInitializedPlaces;

View file

@ -66,9 +66,9 @@ use std::mem;
use crate::transform::{MirPass, MirSource};
use crate::transform::simplify;
use crate::transform::no_landing_pads::no_landing_pads;
use crate::dataflow::{DataflowResults, DataflowResultsConsumer, FlowAtLocation, FlowAtLocationOwned};
use crate::dataflow::{DataflowResults, DataflowResultsConsumer, FlowAtLocation};
use crate::dataflow::{do_dataflow, DebugFormatted, state_for_location};
use crate::dataflow::{MaybeStorageLive, HaveBeenBorrowedLocals};
use crate::dataflow::{MaybeStorageLive, HaveBeenBorrowedLocals, RequiresStorage};
use crate::util::dump_mir;
use crate::util::liveness;
@ -437,16 +437,17 @@ fn locals_live_across_suspend_points(
// Calculate the MIR locals which have been previously
// borrowed (even if they are still active).
// This is only used for immovable generators.
let borrowed_locals = if !movable {
let analysis = HaveBeenBorrowedLocals::new(body);
let result =
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, analysis,
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
Some((analysis, result))
} else {
None
};
let borrowed_locals_analysis = HaveBeenBorrowedLocals::new(body);
let borrowed_locals_result =
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, borrowed_locals_analysis,
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
// Calculate the MIR locals that we actually need to keep storage around
// for.
let requires_storage_analysis = RequiresStorage::new(body, &borrowed_locals_result);
let requires_storage =
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, requires_storage_analysis.clone(),
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
// Calculate the liveness of MIR locals ignoring borrows.
let mut live_locals = liveness::LiveVarSet::new_empty(body.local_decls.len());
@ -471,10 +472,10 @@ fn locals_live_across_suspend_points(
statement_index: data.statements.len(),
};
if let Some((ref analysis, ref result)) = borrowed_locals {
if !movable {
let borrowed_locals = state_for_location(loc,
analysis,
result,
&borrowed_locals_analysis,
&borrowed_locals_result,
body);
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
// This is correct for movable generators since borrows cannot live across
@ -489,27 +490,34 @@ fn locals_live_across_suspend_points(
liveness.outs[block].union(&borrowed_locals);
}
let mut storage_liveness = state_for_location(loc,
&storage_live_analysis,
&storage_live,
body);
let storage_liveness = state_for_location(loc,
&storage_live_analysis,
&storage_live,
body);
// Store the storage liveness for later use so we can restore the state
// after a suspension point
storage_liveness_map.insert(block, storage_liveness.clone());
// Mark locals without storage statements as always having live storage
storage_liveness.union(&ignored.0);
let mut storage_required = state_for_location(loc,
&requires_storage_analysis,
&requires_storage,
body);
// Mark locals without storage statements as always requiring storage
storage_required.union(&ignored.0);
// Locals live are live at this point only if they are used across
// suspension points (the `liveness` variable)
// and their storage is live (the `storage_liveness` variable)
let mut live_locals_here = storage_liveness;
// and their storage is required (the `storage_required` variable)
let mut live_locals_here = storage_required;
live_locals_here.intersect(&liveness.outs[block]);
// The generator argument is ignored
live_locals_here.remove(self_arg());
debug!("loc = {:?}, live_locals_here = {:?}", loc, live_locals_here);
// Add the locals live at this suspension point to the set of locals which live across
// any suspension points
live_locals.union(&live_locals_here);
@ -517,6 +525,7 @@ fn locals_live_across_suspend_points(
live_locals_at_suspension_points.push(live_locals_here);
}
}
debug!("live_locals = {:?}", live_locals);
// Renumber our liveness_map bitsets to include only the locals we are
// saving.
@ -627,7 +636,7 @@ struct StorageConflictVisitor<'body, 'tcx, 's> {
impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
for StorageConflictVisitor<'body, 'tcx, 's>
{
type FlowState = FlowAtLocationOwned<'tcx, MaybeStorageLive<'body, 'tcx>>;
type FlowState = FlowAtLocation<'tcx, MaybeStorageLive<'body, 'tcx>>;
fn body(&self) -> &'body Body<'tcx> {
self.body
@ -657,7 +666,7 @@ impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
fn apply_state(&mut self,
flow_state: &FlowAtLocationOwned<'tcx, MaybeStorageLive<'body, 'tcx>>,
flow_state: &FlowAtLocation<'tcx, MaybeStorageLive<'body, 'tcx>>,
loc: Location) {
// Ignore unreachable blocks.
match self.body.basic_blocks()[loc.block].terminator().kind {