Skip to content

Commit 9969417

Browse files
committed
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.
1 parent 4a8a552 commit 9969417

File tree

4 files changed

+165
-26
lines changed

4 files changed

+165
-26
lines changed

src/librustc_mir/dataflow/at_location.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ pub trait FlowsAtLocation {
6161
/// (e.g., via `reconstruct_statement_effect` and
6262
/// `reconstruct_terminator_effect`; don't forget to call
6363
/// `apply_local_effect`).
64+
#[derive(Clone)]
6465
pub struct FlowAtLocation<'tcx, BD, DR = DataflowResults<'tcx, BD>>
6566
where
6667
BD: BitDenotation<'tcx>,

src/librustc_mir/dataflow/impls/storage_liveness.rs

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
pub use super::*;
22

33
use rustc::mir::*;
4+
use rustc::mir::visit::{
5+
PlaceContext, Visitor, NonMutatingUseContext,
6+
};
7+
use std::cell::RefCell;
48
use crate::dataflow::BitDenotation;
9+
use crate::dataflow::HaveBeenBorrowedLocals;
10+
use crate::dataflow::{DataflowResults, DataflowResultsCursor, DataflowResultsRefCursor};
511

612
#[derive(Copy, Clone)]
713
pub struct MaybeStorageLive<'a, 'tcx> {
@@ -63,3 +69,126 @@ impl<'a, 'tcx> BottomValue for MaybeStorageLive<'a, 'tcx> {
6369
/// bottom = dead
6470
const BOTTOM_VALUE: bool = false;
6571
}
72+
73+
/// Dataflow analysis that determines whether each local requires storage at a
74+
/// given location; i.e. whether its storage can go away without being observed.
75+
///
76+
/// In the case of a movable generator, borrowed_locals can be `None` and we
77+
/// will not consider borrows in this pass. This relies on the fact that we only
78+
/// use this pass at yield points for these generators.
79+
#[derive(Clone)]
80+
pub struct RequiresStorage<'mir, 'tcx, 'b> {
81+
body: &'mir Body<'tcx>,
82+
borrowed_locals:
83+
RefCell<DataflowResultsRefCursor<'mir, 'tcx, 'b, HaveBeenBorrowedLocals<'mir, 'tcx>>>,
84+
}
85+
86+
impl<'mir, 'tcx: 'mir, 'b> RequiresStorage<'mir, 'tcx, 'b> {
87+
pub fn new(
88+
body: &'mir Body<'tcx>,
89+
borrowed_locals: &'b DataflowResults<'tcx, HaveBeenBorrowedLocals<'mir, 'tcx>>,
90+
) -> Self {
91+
RequiresStorage {
92+
body,
93+
borrowed_locals: RefCell::new(DataflowResultsCursor::new(borrowed_locals, body)),
94+
}
95+
}
96+
97+
pub fn body(&self) -> &Body<'tcx> {
98+
self.body
99+
}
100+
}
101+
102+
impl<'mir, 'tcx, 'b> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx, 'b> {
103+
type Idx = Local;
104+
fn name() -> &'static str { "requires_storage" }
105+
fn bits_per_block(&self) -> usize {
106+
self.body.local_decls.len()
107+
}
108+
109+
fn start_block_effect(&self, _sets: &mut BitSet<Local>) {
110+
// Nothing is live on function entry
111+
}
112+
113+
fn statement_effect(&self,
114+
sets: &mut GenKillSet<Local>,
115+
loc: Location) {
116+
self.check_for_move(sets, loc);
117+
self.check_for_borrow(sets, loc);
118+
119+
let stmt = &self.body[loc.block].statements[loc.statement_index];
120+
match stmt.kind {
121+
StatementKind::StorageLive(l) => sets.gen(l),
122+
StatementKind::StorageDead(l) => sets.kill(l),
123+
StatementKind::Assign(ref place, _)
124+
| StatementKind::SetDiscriminant { ref place, .. } => {
125+
place.base_local().map(|l| sets.gen(l));
126+
}
127+
StatementKind::InlineAsm(box InlineAsm { ref outputs, .. }) => {
128+
for p in &**outputs {
129+
p.base_local().map(|l| sets.gen(l));
130+
}
131+
}
132+
_ => (),
133+
}
134+
}
135+
136+
fn terminator_effect(&self,
137+
sets: &mut GenKillSet<Local>,
138+
loc: Location) {
139+
self.check_for_move(sets, loc);
140+
self.check_for_borrow(sets, loc);
141+
}
142+
143+
fn propagate_call_return(
144+
&self,
145+
in_out: &mut BitSet<Local>,
146+
_call_bb: mir::BasicBlock,
147+
_dest_bb: mir::BasicBlock,
148+
dest_place: &mir::Place<'tcx>,
149+
) {
150+
dest_place.base_local().map(|l| in_out.insert(l));
151+
}
152+
}
153+
154+
impl<'mir, 'tcx, 'b> RequiresStorage<'mir, 'tcx, 'b> {
155+
/// Kill locals that are fully moved and have not been borrowed.
156+
fn check_for_move(&self, sets: &mut GenKillSet<Local>, loc: Location) {
157+
let mut visitor = MoveVisitor {
158+
sets,
159+
borrowed_locals: &self.borrowed_locals,
160+
};
161+
visitor.visit_location(self.body, loc);
162+
}
163+
164+
/// Gen locals that are newly borrowed. This includes borrowing any part of
165+
/// a local (we rely on this behavior of `HaveBeenBorrowedLocals`).
166+
fn check_for_borrow(&self, sets: &mut GenKillSet<Local>, loc: Location) {
167+
let mut borrowed_locals = self.borrowed_locals.borrow_mut();
168+
borrowed_locals.seek(loc);
169+
borrowed_locals.each_gen_bit(|l| sets.gen(l));
170+
}
171+
}
172+
173+
impl<'mir, 'tcx, 'b> BottomValue for RequiresStorage<'mir, 'tcx, 'b> {
174+
/// bottom = dead
175+
const BOTTOM_VALUE: bool = false;
176+
}
177+
178+
struct MoveVisitor<'a, 'b, 'mir, 'tcx> {
179+
borrowed_locals:
180+
&'a RefCell<DataflowResultsRefCursor<'mir, 'tcx, 'b, HaveBeenBorrowedLocals<'mir, 'tcx>>>,
181+
sets: &'a mut GenKillSet<Local>,
182+
}
183+
184+
impl<'a, 'b, 'mir: 'a, 'tcx> Visitor<'tcx> for MoveVisitor<'a, 'b, 'mir, 'tcx> {
185+
fn visit_local(&mut self, local: &Local, context: PlaceContext, loc: Location) {
186+
if PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) == context {
187+
let mut borrowed_locals = self.borrowed_locals.borrow_mut();
188+
borrowed_locals.seek(loc);
189+
if !borrowed_locals.contains(*local) {
190+
self.sets.kill(*local);
191+
}
192+
}
193+
}
194+
}

src/librustc_mir/dataflow/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use std::io;
1717
use std::path::PathBuf;
1818
use std::usize;
1919

20-
pub use self::impls::{MaybeStorageLive};
20+
pub use self::impls::{MaybeStorageLive, RequiresStorage};
2121
pub use self::impls::{MaybeInitializedPlaces, MaybeUninitializedPlaces};
2222
pub use self::impls::DefinitelyInitializedPlaces;
2323
pub use self::impls::EverInitializedPlaces;

src/librustc_mir/transform/generator.rs

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ use std::mem;
6666
use crate::transform::{MirPass, MirSource};
6767
use crate::transform::simplify;
6868
use crate::transform::no_landing_pads::no_landing_pads;
69-
use crate::dataflow::{DataflowResults, DataflowResultsConsumer, FlowAtLocation, FlowAtLocationOwned};
69+
use crate::dataflow::{DataflowResults, DataflowResultsConsumer, FlowAtLocation};
7070
use crate::dataflow::{do_dataflow, DebugFormatted, state_for_location};
71-
use crate::dataflow::{MaybeStorageLive, HaveBeenBorrowedLocals};
71+
use crate::dataflow::{MaybeStorageLive, HaveBeenBorrowedLocals, RequiresStorage};
7272
use crate::util::dump_mir;
7373
use crate::util::liveness;
7474

@@ -437,16 +437,17 @@ fn locals_live_across_suspend_points(
437437

438438
// Calculate the MIR locals which have been previously
439439
// borrowed (even if they are still active).
440-
// This is only used for immovable generators.
441-
let borrowed_locals = if !movable {
442-
let analysis = HaveBeenBorrowedLocals::new(body);
443-
let result =
444-
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, analysis,
445-
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
446-
Some((analysis, result))
447-
} else {
448-
None
449-
};
440+
let borrowed_locals_analysis = HaveBeenBorrowedLocals::new(body);
441+
let borrowed_locals_result =
442+
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, borrowed_locals_analysis,
443+
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
444+
445+
// Calculate the MIR locals that we actually need to keep storage around
446+
// for.
447+
let requires_storage_analysis = RequiresStorage::new(body, &borrowed_locals_result);
448+
let requires_storage =
449+
do_dataflow(tcx, body, def_id, &[], &dead_unwinds, requires_storage_analysis.clone(),
450+
|bd, p| DebugFormatted::new(&bd.body().local_decls[p]));
450451

451452
// Calculate the liveness of MIR locals ignoring borrows.
452453
let mut live_locals = liveness::LiveVarSet::new_empty(body.local_decls.len());
@@ -471,10 +472,10 @@ fn locals_live_across_suspend_points(
471472
statement_index: data.statements.len(),
472473
};
473474

474-
if let Some((ref analysis, ref result)) = borrowed_locals {
475+
if !movable {
475476
let borrowed_locals = state_for_location(loc,
476-
analysis,
477-
result,
477+
&borrowed_locals_analysis,
478+
&borrowed_locals_result,
478479
body);
479480
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
480481
// This is correct for movable generators since borrows cannot live across
@@ -489,34 +490,42 @@ fn locals_live_across_suspend_points(
489490
liveness.outs[block].union(&borrowed_locals);
490491
}
491492

492-
let mut storage_liveness = state_for_location(loc,
493-
&storage_live_analysis,
494-
&storage_live,
495-
body);
493+
let storage_liveness = state_for_location(loc,
494+
&storage_live_analysis,
495+
&storage_live,
496+
body);
496497

497498
// Store the storage liveness for later use so we can restore the state
498499
// after a suspension point
499500
storage_liveness_map.insert(block, storage_liveness.clone());
500501

501-
// Mark locals without storage statements as always having live storage
502-
storage_liveness.union(&ignored.0);
502+
let mut storage_required = state_for_location(loc,
503+
&requires_storage_analysis,
504+
&requires_storage,
505+
body);
506+
507+
// Mark locals without storage statements as always requiring storage
508+
storage_required.union(&ignored.0);
503509

504510
// Locals live are live at this point only if they are used across
505511
// suspension points (the `liveness` variable)
506-
// and their storage is live (the `storage_liveness` variable)
507-
let mut live_locals_here = storage_liveness;
512+
// and their storage is required (the `storage_required` variable)
513+
let mut live_locals_here = storage_required;
508514
live_locals_here.intersect(&liveness.outs[block]);
509515

510516
// The generator argument is ignored
511517
live_locals_here.remove(self_arg());
512518

519+
debug!("loc = {:?}, live_locals_here = {:?}", loc, live_locals_here);
520+
513521
// Add the locals live at this suspension point to the set of locals which live across
514522
// any suspension points
515523
live_locals.union(&live_locals_here);
516524

517525
live_locals_at_suspension_points.push(live_locals_here);
518526
}
519527
}
528+
debug!("live_locals = {:?}", live_locals);
520529

521530
// Renumber our liveness_map bitsets to include only the locals we are
522531
// saving.
@@ -627,7 +636,7 @@ struct StorageConflictVisitor<'body, 'tcx, 's> {
627636
impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
628637
for StorageConflictVisitor<'body, 'tcx, 's>
629638
{
630-
type FlowState = FlowAtLocationOwned<'tcx, MaybeStorageLive<'body, 'tcx>>;
639+
type FlowState = FlowAtLocation<'tcx, MaybeStorageLive<'body, 'tcx>>;
631640

632641
fn body(&self) -> &'body Body<'tcx> {
633642
self.body
@@ -657,7 +666,7 @@ impl<'body, 'tcx, 's> DataflowResultsConsumer<'body, 'tcx>
657666

658667
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
659668
fn apply_state(&mut self,
660-
flow_state: &FlowAtLocationOwned<'tcx, MaybeStorageLive<'body, 'tcx>>,
669+
flow_state: &FlowAtLocation<'tcx, MaybeStorageLive<'body, 'tcx>>,
661670
loc: Location) {
662671
// Ignore unreachable blocks.
663672
match self.body.basic_blocks()[loc.block].terminator().kind {

0 commit comments

Comments
 (0)