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:
parent
4a8a552b49
commit
99694177f9
4 changed files with 165 additions and 26 deletions
|
@ -61,6 +61,7 @@ pub trait FlowsAtLocation {
|
||||||
/// (e.g., via `reconstruct_statement_effect` and
|
/// (e.g., via `reconstruct_statement_effect` and
|
||||||
/// `reconstruct_terminator_effect`; don't forget to call
|
/// `reconstruct_terminator_effect`; don't forget to call
|
||||||
/// `apply_local_effect`).
|
/// `apply_local_effect`).
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct FlowAtLocation<'tcx, BD, DR = DataflowResults<'tcx, BD>>
|
pub struct FlowAtLocation<'tcx, BD, DR = DataflowResults<'tcx, BD>>
|
||||||
where
|
where
|
||||||
BD: BitDenotation<'tcx>,
|
BD: BitDenotation<'tcx>,
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
pub use super::*;
|
pub use super::*;
|
||||||
|
|
||||||
use rustc::mir::*;
|
use rustc::mir::*;
|
||||||
|
use rustc::mir::visit::{
|
||||||
|
PlaceContext, Visitor, NonMutatingUseContext,
|
||||||
|
};
|
||||||
|
use std::cell::RefCell;
|
||||||
use crate::dataflow::BitDenotation;
|
use crate::dataflow::BitDenotation;
|
||||||
|
use crate::dataflow::HaveBeenBorrowedLocals;
|
||||||
|
use crate::dataflow::{DataflowResults, DataflowResultsCursor, DataflowResultsRefCursor};
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct MaybeStorageLive<'a, 'tcx> {
|
pub struct MaybeStorageLive<'a, 'tcx> {
|
||||||
|
@ -63,3 +69,126 @@ impl<'a, 'tcx> BottomValue for MaybeStorageLive<'a, 'tcx> {
|
||||||
/// bottom = dead
|
/// bottom = dead
|
||||||
const BOTTOM_VALUE: bool = false;
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::io;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::usize;
|
use std::usize;
|
||||||
|
|
||||||
pub use self::impls::{MaybeStorageLive};
|
pub use self::impls::{MaybeStorageLive, RequiresStorage};
|
||||||
pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
|
pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
|
||||||
pub use self::impls::DefinitelyInitializedPlaces;
|
pub use self::impls::DefinitelyInitializedPlaces;
|
||||||
pub use self::impls::EverInitializedPlaces;
|
pub use self::impls::EverInitializedPlaces;
|
||||||
|
|
|
@ -66,9 +66,9 @@ use std::mem;
|
||||||
use crate::transform::{MirPass, MirSource};
|
use crate::transform::{MirPass, MirSource};
|
||||||
use crate::transform::simplify;
|
use crate::transform::simplify;
|
||||||
use crate::transform::no_landing_pads::no_landing_pads;
|
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::{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::dump_mir;
|
||||||
use crate::util::liveness;
|
use crate::util::liveness;
|
||||||
|
|
||||||
|
@ -437,16 +437,17 @@ fn locals_live_across_suspend_points(
|
||||||
|
|
||||||
// Calculate the MIR locals which have been previously
|
// Calculate the MIR locals which have been previously
|
||||||
// borrowed (even if they are still active).
|
// borrowed (even if they are still active).
|
||||||
// This is only used for immovable generators.
|
let borrowed_locals_analysis = HaveBeenBorrowedLocals::new(body);
|
||||||
let borrowed_locals = if !movable {
|
let borrowed_locals_result =
|
||||||
let analysis = HaveBeenBorrowedLocals::new(body);
|
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, borrowed_locals_analysis,
|
||||||
let result =
|
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
|
||||||
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, analysis,
|
|
||||||
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
|
// Calculate the MIR locals that we actually need to keep storage around
|
||||||
Some((analysis, result))
|
// for.
|
||||||
} else {
|
let requires_storage_analysis = RequiresStorage::new(body, &borrowed_locals_result);
|
||||||
None
|
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.
|
// Calculate the liveness of MIR locals ignoring borrows.
|
||||||
let mut live_locals = liveness::LiveVarSet::new_empty(body.local_decls.len());
|
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(),
|
statement_index: data.statements.len(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((ref analysis, ref result)) = borrowed_locals {
|
if !movable {
|
||||||
let borrowed_locals = state_for_location(loc,
|
let borrowed_locals = state_for_location(loc,
|
||||||
analysis,
|
&borrowed_locals_analysis,
|
||||||
result,
|
&borrowed_locals_result,
|
||||||
body);
|
body);
|
||||||
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
|
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
|
||||||
// This is correct for movable generators since borrows cannot live across
|
// 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);
|
liveness.outs[block].union(&borrowed_locals);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut storage_liveness = state_for_location(loc,
|
let storage_liveness = state_for_location(loc,
|
||||||
&storage_live_analysis,
|
&storage_live_analysis,
|
||||||
&storage_live,
|
&storage_live,
|
||||||
body);
|
body);
|
||||||
|
|
||||||
// Store the storage liveness for later use so we can restore the state
|
// Store the storage liveness for later use so we can restore the state
|
||||||
// after a suspension point
|
// after a suspension point
|
||||||
storage_liveness_map.insert(block, storage_liveness.clone());
|
storage_liveness_map.insert(block, storage_liveness.clone());
|
||||||
|
|
||||||
// Mark locals without storage statements as always having live storage
|
let mut storage_required = state_for_location(loc,
|
||||||
storage_liveness.union(&ignored.0);
|
&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
|
// Locals live are live at this point only if they are used across
|
||||||
// suspension points (the `liveness` variable)
|
// suspension points (the `liveness` variable)
|
||||||
// and their storage is live (the `storage_liveness` variable)
|
// and their storage is required (the `storage_required` variable)
|
||||||
let mut live_locals_here = storage_liveness;
|
let mut live_locals_here = storage_required;
|
||||||
live_locals_here.intersect(&liveness.outs[block]);
|
live_locals_here.intersect(&liveness.outs[block]);
|
||||||
|
|
||||||
// The generator argument is ignored
|
// The generator argument is ignored
|
||||||
live_locals_here.remove(self_arg());
|
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
|
// Add the locals live at this suspension point to the set of locals which live across
|
||||||
// any suspension points
|
// any suspension points
|
||||||
live_locals.union(&live_locals_here);
|
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);
|
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
|
// Renumber our liveness_map bitsets to include only the locals we are
|
||||||
// saving.
|
// saving.
|
||||||
|
@ -627,7 +636,7 @@ struct StorageConflictVisitor<'body, 'tcx, 's> {
|
||||||
impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
|
impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
|
||||||
for StorageConflictVisitor<'body, 'tcx, 's>
|
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> {
|
fn body(&self) -> &'body Body<'tcx> {
|
||||||
self.body
|
self.body
|
||||||
|
@ -657,7 +666,7 @@ impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
|
||||||
|
|
||||||
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
|
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
|
||||||
fn apply_state(&mut self,
|
fn apply_state(&mut self,
|
||||||
flow_state: &FlowAtLocationOwned<'tcx, MaybeStorageLive<'body, 'tcx>>,
|
flow_state: &FlowAtLocation<'tcx, MaybeStorageLive<'body, 'tcx>>,
|
||||||
loc: Location) {
|
loc: Location) {
|
||||||
// Ignore unreachable blocks.
|
// Ignore unreachable blocks.
|
||||||
match self.body.basic_blocks()[loc.block].terminator().kind {
|
match self.body.basic_blocks()[loc.block].terminator().kind {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue