Skip to content

Commit 458a3e7

Browse files
committedMay 22, 2020
Auto merge of #71956 - ecstatic-morse:remove-requires-storage-analysis, r=tmandry
Clean up logic around live locals in generator analysis Resolves #69902. Requires #71893. I've found it difficult to make changes in the logic around live locals in `generator/transform.rs`. It uses a custom dataflow analysis, `MaybeRequiresStorage`, that AFAICT computes whether a local is either initialized or borrowed. That analysis is using `before` effects, which we should try to avoid if possible because they are harder to reason about than ones only using the unprefixed effects. @pnkfelix has suggested removing "before" effects entirely to simplify the dataflow framework, which I might pursue someday. This PR replaces `MaybeRequiresStorage` with a combination of the existing `MaybeBorrowedLocals` and a new `MaybeInitializedLocals`. `MaybeInitializedLocals` is just `MaybeInitializedPlaces` with a coarser resolution: it works on whole locals instead of move paths. As a result, I was able to simplify the logic in `compute_storage_conflicts` and `locals_live_across_suspend_points`. This is not exactly equivalent to the old logic; some generators are now smaller than before. I believe this was because the old logic was too conservative, but I'm not as familiar with the constraints as the original implementers were, so I could be wrong. For example, I don't see a reason the size of the `mixed_sizes` future couldn't be 5K. It went from 7K to 6K in this PR. r? @jonas-schievink @tmandry
2 parents d9417b3 + 3ff9317 commit 458a3e7

File tree

7 files changed

+254
-372
lines changed

7 files changed

+254
-372
lines changed
 

‎src/librustc_mir/dataflow/impls/borrowed_locals.rs

+3
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ impl<K> GenKillAnalysis<'tcx> for MaybeBorrowedLocals<K>
9999
where
100100
K: BorrowAnalysisKind<'tcx>,
101101
{
102+
// The generator transform relies on the fact that this analysis does **not** use "before"
103+
// effects.
104+
102105
fn statement_effect(
103106
&self,
104107
trans: &mut impl GenKill<Self::Idx>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//! A less precise version of `MaybeInitializedPlaces` whose domain is entire locals.
2+
//!
3+
//! A local will be maybe initialized if *any* projections of that local might be initialized.
4+
5+
use crate::dataflow::{self, BottomValue, GenKill};
6+
7+
use rustc_index::bit_set::BitSet;
8+
use rustc_middle::mir::visit::{PlaceContext, Visitor};
9+
use rustc_middle::mir::{self, BasicBlock, Local, Location};
10+
11+
pub struct MaybeInitializedLocals;
12+
13+
impl BottomValue for MaybeInitializedLocals {
14+
/// bottom = uninit
15+
const BOTTOM_VALUE: bool = false;
16+
}
17+
18+
impl dataflow::AnalysisDomain<'tcx> for MaybeInitializedLocals {
19+
type Idx = Local;
20+
21+
const NAME: &'static str = "maybe_init_locals";
22+
23+
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
24+
body.local_decls.len()
25+
}
26+
27+
fn initialize_start_block(&self, body: &mir::Body<'tcx>, entry_set: &mut BitSet<Self::Idx>) {
28+
// Function arguments are initialized to begin with.
29+
for arg in body.args_iter() {
30+
entry_set.insert(arg);
31+
}
32+
}
33+
}
34+
35+
impl dataflow::GenKillAnalysis<'tcx> for MaybeInitializedLocals {
36+
// The generator transform relies on the fact that this analysis does **not** use "before"
37+
// effects.
38+
39+
fn statement_effect(
40+
&self,
41+
trans: &mut impl GenKill<Self::Idx>,
42+
statement: &mir::Statement<'tcx>,
43+
loc: Location,
44+
) {
45+
TransferFunction { trans }.visit_statement(statement, loc)
46+
}
47+
48+
fn terminator_effect(
49+
&self,
50+
trans: &mut impl GenKill<Self::Idx>,
51+
terminator: &mir::Terminator<'tcx>,
52+
loc: Location,
53+
) {
54+
TransferFunction { trans }.visit_terminator(terminator, loc)
55+
}
56+
57+
fn call_return_effect(
58+
&self,
59+
trans: &mut impl GenKill<Self::Idx>,
60+
_block: BasicBlock,
61+
_func: &mir::Operand<'tcx>,
62+
_args: &[mir::Operand<'tcx>],
63+
return_place: mir::Place<'tcx>,
64+
) {
65+
trans.gen(return_place.local)
66+
}
67+
68+
/// See `Analysis::apply_yield_resume_effect`.
69+
fn yield_resume_effect(
70+
&self,
71+
trans: &mut impl GenKill<Self::Idx>,
72+
_resume_block: BasicBlock,
73+
resume_place: mir::Place<'tcx>,
74+
) {
75+
trans.gen(resume_place.local)
76+
}
77+
}
78+
79+
struct TransferFunction<'a, T> {
80+
trans: &'a mut T,
81+
}
82+
83+
impl<T> Visitor<'tcx> for TransferFunction<'a, T>
84+
where
85+
T: GenKill<Local>,
86+
{
87+
fn visit_local(&mut self, &local: &Local, context: PlaceContext, _: Location) {
88+
use rustc_middle::mir::visit::{MutatingUseContext, NonMutatingUseContext, NonUseContext};
89+
match context {
90+
// These are handled specially in `call_return_effect` and `yield_resume_effect`.
91+
PlaceContext::MutatingUse(MutatingUseContext::Call | MutatingUseContext::Yield) => {}
92+
93+
// Otherwise, when a place is mutated, we must consider it possibly initialized.
94+
PlaceContext::MutatingUse(_) => self.trans.gen(local),
95+
96+
// If the local is moved out of, or if it gets marked `StorageDead`, consider it no
97+
// longer initialized.
98+
PlaceContext::NonUse(NonUseContext::StorageDead)
99+
| PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) => self.trans.kill(local),
100+
101+
// All other uses do not affect this analysis.
102+
PlaceContext::NonUse(
103+
NonUseContext::StorageLive
104+
| NonUseContext::AscribeUserTy
105+
| NonUseContext::VarDebugInfo,
106+
)
107+
| PlaceContext::NonMutatingUse(
108+
NonMutatingUseContext::Inspect
109+
| NonMutatingUseContext::Copy
110+
| NonMutatingUseContext::SharedBorrow
111+
| NonMutatingUseContext::ShallowBorrow
112+
| NonMutatingUseContext::UniqueBorrow
113+
| NonMutatingUseContext::AddressOf
114+
| NonMutatingUseContext::Projection,
115+
) => {}
116+
}
117+
}
118+
}

‎src/librustc_mir/dataflow/impls/mod.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,15 @@ use crate::dataflow::drop_flag_effects;
2222

2323
mod borrowed_locals;
2424
pub(super) mod borrows;
25+
mod init_locals;
2526
mod liveness;
2627
mod storage_liveness;
2728

2829
pub use self::borrowed_locals::{MaybeBorrowedLocals, MaybeMutBorrowedLocals};
2930
pub use self::borrows::Borrows;
31+
pub use self::init_locals::MaybeInitializedLocals;
3032
pub use self::liveness::MaybeLiveLocals;
31-
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageLive};
33+
pub use self::storage_liveness::MaybeStorageLive;
3234

3335
/// `MaybeInitializedPlaces` tracks all places that might be
3436
/// initialized upon reaching a particular point in the control flow
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
pub use super::*;
22

33
use crate::dataflow::BottomValue;
4-
use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
4+
use crate::dataflow::{self, GenKill};
55
use crate::util::storage::AlwaysLiveLocals;
6-
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
76
use rustc_middle::mir::*;
8-
use std::cell::RefCell;
97

108
#[derive(Clone)]
119
pub struct MaybeStorageLive {
@@ -78,233 +76,3 @@ impl BottomValue for MaybeStorageLive {
7876
/// bottom = dead
7977
const BOTTOM_VALUE: bool = false;
8078
}
81-
82-
type BorrowedLocalsResults<'a, 'tcx> = ResultsRefCursor<'a, 'a, 'tcx, MaybeBorrowedLocals>;
83-
84-
/// Dataflow analysis that determines whether each local requires storage at a
85-
/// given location; i.e. whether its storage can go away without being observed.
86-
pub struct MaybeRequiresStorage<'mir, 'tcx> {
87-
body: &'mir Body<'tcx>,
88-
borrowed_locals: RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
89-
}
90-
91-
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
92-
pub fn new(
93-
body: &'mir Body<'tcx>,
94-
borrowed_locals: &'mir Results<'tcx, MaybeBorrowedLocals>,
95-
) -> Self {
96-
MaybeRequiresStorage {
97-
body,
98-
borrowed_locals: RefCell::new(ResultsRefCursor::new(&body, borrowed_locals)),
99-
}
100-
}
101-
}
102-
103-
impl<'mir, 'tcx> dataflow::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
104-
type Idx = Local;
105-
106-
const NAME: &'static str = "requires_storage";
107-
108-
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
109-
body.local_decls.len()
110-
}
111-
112-
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
113-
// The resume argument is live on function entry (we don't care about
114-
// the `self` argument)
115-
for arg in body.args_iter().skip(1) {
116-
on_entry.insert(arg);
117-
}
118-
}
119-
}
120-
121-
impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
122-
fn before_statement_effect(
123-
&self,
124-
trans: &mut impl GenKill<Self::Idx>,
125-
stmt: &mir::Statement<'tcx>,
126-
loc: Location,
127-
) {
128-
// If a place is borrowed in a statement, it needs storage for that statement.
129-
self.borrowed_locals.borrow().analysis().statement_effect(trans, stmt, loc);
130-
131-
match &stmt.kind {
132-
StatementKind::StorageDead(l) => trans.kill(*l),
133-
134-
// If a place is assigned to in a statement, it needs storage for that statement.
135-
StatementKind::Assign(box (place, _))
136-
| StatementKind::SetDiscriminant { box place, .. } => {
137-
trans.gen(place.local);
138-
}
139-
StatementKind::LlvmInlineAsm(asm) => {
140-
for place in &*asm.outputs {
141-
trans.gen(place.local);
142-
}
143-
}
144-
145-
// Nothing to do for these. Match exhaustively so this fails to compile when new
146-
// variants are added.
147-
StatementKind::AscribeUserType(..)
148-
| StatementKind::FakeRead(..)
149-
| StatementKind::Nop
150-
| StatementKind::Retag(..)
151-
| StatementKind::StorageLive(..) => {}
152-
}
153-
}
154-
155-
fn statement_effect(
156-
&self,
157-
trans: &mut impl GenKill<Self::Idx>,
158-
_: &mir::Statement<'tcx>,
159-
loc: Location,
160-
) {
161-
// If we move from a place then only stops needing storage *after*
162-
// that statement.
163-
self.check_for_move(trans, loc);
164-
}
165-
166-
fn before_terminator_effect(
167-
&self,
168-
trans: &mut impl GenKill<Self::Idx>,
169-
terminator: &mir::Terminator<'tcx>,
170-
loc: Location,
171-
) {
172-
// If a place is borrowed in a terminator, it needs storage for that terminator.
173-
self.borrowed_locals.borrow().analysis().terminator_effect(trans, terminator, loc);
174-
175-
match &terminator.kind {
176-
TerminatorKind::Call { destination: Some((place, _)), .. } => {
177-
trans.gen(place.local);
178-
}
179-
180-
// Note that we do *not* gen the `resume_arg` of `Yield` terminators. The reason for
181-
// that is that a `yield` will return from the function, and `resume_arg` is written
182-
// only when the generator is later resumed. Unlike `Call`, this doesn't require the
183-
// place to have storage *before* the yield, only after.
184-
TerminatorKind::Yield { .. } => {}
185-
186-
TerminatorKind::InlineAsm { operands, .. } => {
187-
for op in operands {
188-
match op {
189-
InlineAsmOperand::Out { place, .. }
190-
| InlineAsmOperand::InOut { out_place: place, .. } => {
191-
if let Some(place) = place {
192-
trans.gen(place.local);
193-
}
194-
}
195-
InlineAsmOperand::In { .. }
196-
| InlineAsmOperand::Const { .. }
197-
| InlineAsmOperand::SymFn { .. }
198-
| InlineAsmOperand::SymStatic { .. } => {}
199-
}
200-
}
201-
}
202-
203-
// Nothing to do for these. Match exhaustively so this fails to compile when new
204-
// variants are added.
205-
TerminatorKind::Call { destination: None, .. }
206-
| TerminatorKind::Abort
207-
| TerminatorKind::Assert { .. }
208-
| TerminatorKind::Drop { .. }
209-
| TerminatorKind::DropAndReplace { .. }
210-
| TerminatorKind::FalseEdges { .. }
211-
| TerminatorKind::FalseUnwind { .. }
212-
| TerminatorKind::GeneratorDrop
213-
| TerminatorKind::Goto { .. }
214-
| TerminatorKind::Resume
215-
| TerminatorKind::Return
216-
| TerminatorKind::SwitchInt { .. }
217-
| TerminatorKind::Unreachable => {}
218-
}
219-
}
220-
221-
fn terminator_effect(
222-
&self,
223-
trans: &mut impl GenKill<Self::Idx>,
224-
terminator: &mir::Terminator<'tcx>,
225-
loc: Location,
226-
) {
227-
match &terminator.kind {
228-
// For call terminators the destination requires storage for the call
229-
// and after the call returns successfully, but not after a panic.
230-
// Since `propagate_call_unwind` doesn't exist, we have to kill the
231-
// destination here, and then gen it again in `call_return_effect`.
232-
TerminatorKind::Call { destination: Some((place, _)), .. } => {
233-
trans.kill(place.local);
234-
}
235-
236-
// Nothing to do for these. Match exhaustively so this fails to compile when new
237-
// variants are added.
238-
TerminatorKind::Call { destination: None, .. }
239-
| TerminatorKind::Yield { .. }
240-
| TerminatorKind::Abort
241-
| TerminatorKind::Assert { .. }
242-
| TerminatorKind::Drop { .. }
243-
| TerminatorKind::DropAndReplace { .. }
244-
| TerminatorKind::FalseEdges { .. }
245-
| TerminatorKind::FalseUnwind { .. }
246-
| TerminatorKind::GeneratorDrop
247-
| TerminatorKind::Goto { .. }
248-
| TerminatorKind::InlineAsm { .. }
249-
| TerminatorKind::Resume
250-
| TerminatorKind::Return
251-
| TerminatorKind::SwitchInt { .. }
252-
| TerminatorKind::Unreachable => {}
253-
}
254-
255-
self.check_for_move(trans, loc);
256-
}
257-
258-
fn call_return_effect(
259-
&self,
260-
trans: &mut impl GenKill<Self::Idx>,
261-
_block: BasicBlock,
262-
_func: &mir::Operand<'tcx>,
263-
_args: &[mir::Operand<'tcx>],
264-
return_place: mir::Place<'tcx>,
265-
) {
266-
trans.gen(return_place.local);
267-
}
268-
269-
fn yield_resume_effect(
270-
&self,
271-
trans: &mut impl GenKill<Self::Idx>,
272-
_resume_block: BasicBlock,
273-
resume_place: mir::Place<'tcx>,
274-
) {
275-
trans.gen(resume_place.local);
276-
}
277-
}
278-
279-
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
280-
/// Kill locals that are fully moved and have not been borrowed.
281-
fn check_for_move(&self, trans: &mut impl GenKill<Local>, loc: Location) {
282-
let mut visitor = MoveVisitor { trans, borrowed_locals: &self.borrowed_locals };
283-
visitor.visit_location(&self.body, loc);
284-
}
285-
}
286-
287-
impl<'mir, 'tcx> BottomValue for MaybeRequiresStorage<'mir, 'tcx> {
288-
/// bottom = dead
289-
const BOTTOM_VALUE: bool = false;
290-
}
291-
292-
struct MoveVisitor<'a, 'mir, 'tcx, T> {
293-
borrowed_locals: &'a RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
294-
trans: &'a mut T,
295-
}
296-
297-
impl<'a, 'mir, 'tcx, T> Visitor<'tcx> for MoveVisitor<'a, 'mir, 'tcx, T>
298-
where
299-
T: GenKill<Local>,
300-
{
301-
fn visit_local(&mut self, local: &Local, context: PlaceContext, loc: Location) {
302-
if PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) == context {
303-
let mut borrowed_locals = self.borrowed_locals.borrow_mut();
304-
borrowed_locals.seek_before_primary_effect(loc);
305-
if !borrowed_locals.contains(*local) {
306-
self.trans.kill(*local);
307-
}
308-
}
309-
}
310-
}

‎src/librustc_mir/transform/generator.rs

+127-136
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
//! Otherwise it drops all the values in scope at the last suspension point.
5151
5252
use crate::dataflow::impls::{
53-
MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive,
53+
MaybeBorrowedLocals, MaybeInitializedLocals, MaybeLiveLocals, MaybeStorageLive,
5454
};
5555
use crate::dataflow::{self, Analysis};
5656
use crate::transform::no_landing_pads::no_landing_pads;
@@ -444,86 +444,80 @@ fn locals_live_across_suspend_points(
444444
movable: bool,
445445
) -> LivenessInfo {
446446
let def_id = source.def_id();
447-
let body_ref: &Body<'_> = &body;
448447

449448
// Calculate when MIR locals have live storage. This gives us an upper bound of their
450449
// lifetimes.
451450
let mut storage_live = MaybeStorageLive::new(always_live_locals.clone())
452-
.into_engine(tcx, body_ref, def_id)
451+
.into_engine(tcx, body, def_id)
453452
.iterate_to_fixpoint()
454-
.into_results_cursor(body_ref);
455-
456-
// Calculate the MIR locals which have been previously
457-
// borrowed (even if they are still active).
458-
let borrowed_locals_results =
459-
MaybeBorrowedLocals::all_borrows().into_engine(tcx, body_ref, def_id).iterate_to_fixpoint();
460-
461-
let mut borrowed_locals_cursor =
462-
dataflow::ResultsCursor::new(body_ref, &borrowed_locals_results);
463-
464-
// Calculate the MIR locals that we actually need to keep storage around
465-
// for.
466-
let requires_storage_results = MaybeRequiresStorage::new(body, &borrowed_locals_results)
467-
.into_engine(tcx, body_ref, def_id)
468-
.iterate_to_fixpoint();
469-
let mut requires_storage_cursor =
470-
dataflow::ResultsCursor::new(body_ref, &requires_storage_results);
471-
472-
// Calculate the liveness of MIR locals ignoring borrows.
473-
let mut liveness = MaybeLiveLocals
474-
.into_engine(tcx, body_ref, def_id)
453+
.into_results_cursor(body);
454+
455+
let mut init = MaybeInitializedLocals
456+
.into_engine(tcx, body, def_id)
457+
.iterate_to_fixpoint()
458+
.into_results_cursor(body);
459+
460+
let mut live = MaybeLiveLocals
461+
.into_engine(tcx, body, def_id)
462+
.iterate_to_fixpoint()
463+
.into_results_cursor(body);
464+
465+
let mut borrowed = MaybeBorrowedLocals::all_borrows()
466+
.into_engine(tcx, body, def_id)
475467
.iterate_to_fixpoint()
476-
.into_results_cursor(body_ref);
468+
.into_results_cursor(body);
469+
470+
// Liveness across yield points is determined by the following boolean equation, where `live`,
471+
// `init` and `borrowed` come from dataflow and `movable` is a property of the generator.
472+
// Movable generators do not allow borrows to live across yield points, so they don't need to
473+
// store a local simply because it is borrowed.
474+
//
475+
// live_across_yield := (live & init) | (!movable & borrowed)
476+
//
477+
let mut locals_live_across_yield_point = |block| {
478+
live.seek_to_block_end(block);
479+
let mut live_locals = live.get().clone();
480+
481+
init.seek_to_block_end(block);
482+
live_locals.intersect(init.get());
483+
484+
if !movable {
485+
borrowed.seek_to_block_end(block);
486+
live_locals.union(borrowed.get());
487+
}
488+
489+
live_locals
490+
};
477491

478492
let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
479493
let mut live_locals_at_suspension_points = Vec::new();
480494
let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
481495

482496
for (block, data) in body.basic_blocks().iter_enumerated() {
483-
if let TerminatorKind::Yield { .. } = data.terminator().kind {
484-
let loc = Location { block, statement_index: data.statements.len() };
485-
486-
liveness.seek_to_block_end(block);
487-
let mut live_locals = liveness.get().clone();
488-
489-
if !movable {
490-
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
491-
// This is correct for movable generators since borrows cannot live across
492-
// suspension points. However for immovable generators we need to account for
493-
// borrows, so we conseratively assume that all borrowed locals are live until
494-
// we find a StorageDead statement referencing the locals.
495-
// To do this we just union our `liveness` result with `borrowed_locals`, which
496-
// contains all the locals which has been borrowed before this suspension point.
497-
// If a borrow is converted to a raw reference, we must also assume that it lives
498-
// forever. Note that the final liveness is still bounded by the storage liveness
499-
// of the local, which happens using the `intersect` operation below.
500-
borrowed_locals_cursor.seek_before_primary_effect(loc);
501-
live_locals.union(borrowed_locals_cursor.get());
502-
}
503-
504-
// Store the storage liveness for later use so we can restore the state
505-
// after a suspension point
506-
storage_live.seek_before_primary_effect(loc);
507-
storage_liveness_map[block] = Some(storage_live.get().clone());
508-
509-
// Locals live are live at this point only if they are used across
510-
// suspension points (the `liveness` variable)
511-
// and their storage is required (the `storage_required` variable)
512-
requires_storage_cursor.seek_before_primary_effect(loc);
513-
live_locals.intersect(requires_storage_cursor.get());
497+
if !matches!(data.terminator().kind, TerminatorKind::Yield { .. }) {
498+
continue;
499+
}
514500

515-
// The generator argument is ignored.
516-
live_locals.remove(SELF_ARG);
501+
// Store the storage liveness for later use so we can restore the state
502+
// after a suspension point
503+
storage_live.seek_to_block_end(block);
504+
storage_liveness_map[block] = Some(storage_live.get().clone());
517505

518-
debug!("loc = {:?}, live_locals = {:?}", loc, live_locals);
506+
let mut live_locals = locals_live_across_yield_point(block);
519507

520-
// Add the locals live at this suspension point to the set of locals which live across
521-
// any suspension points
522-
live_locals_at_any_suspension_point.union(&live_locals);
508+
// The combination of `MaybeInitializedLocals` and `MaybeBorrowedLocals` should be strictly
509+
// more precise than `MaybeStorageLive` because they handle `StorageDead` themselves. This
510+
// assumes that the MIR forbids locals from being initialized/borrowed before reaching
511+
// `StorageLive`.
512+
debug_assert!(storage_live.get().superset(&live_locals));
523513

524-
live_locals_at_suspension_points.push(live_locals);
525-
}
514+
// Ignore the generator's `self` argument since it is handled seperately.
515+
live_locals.remove(SELF_ARG);
516+
debug!("block = {:?}, live_locals = {:?}", block, live_locals);
517+
live_locals_at_any_suspension_point.union(&live_locals);
518+
live_locals_at_suspension_points.push(live_locals);
526519
}
520+
527521
debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point);
528522

529523
// Renumber our liveness_map bitsets to include only the locals we are
@@ -534,10 +528,11 @@ fn locals_live_across_suspend_points(
534528
.collect();
535529

536530
let storage_conflicts = compute_storage_conflicts(
537-
body_ref,
531+
body,
538532
&live_locals_at_any_suspension_point,
539533
always_live_locals.clone(),
540-
requires_storage_results,
534+
init,
535+
borrowed,
541536
);
542537

543538
LivenessInfo {
@@ -569,6 +564,37 @@ fn renumber_bitset(
569564
out
570565
}
571566

567+
/// Record conflicts between locals at the current dataflow cursor positions.
568+
///
569+
/// You need to seek the cursors before calling this function.
570+
fn record_conflicts_at_curr_loc(
571+
local_conflicts: &mut BitMatrix<Local, Local>,
572+
init: &dataflow::ResultsCursor<'mir, 'tcx, MaybeInitializedLocals>,
573+
borrowed: &dataflow::ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
574+
) {
575+
// A local requires storage if it is initialized or borrowed. For now, a local
576+
// becomes uninitialized if it is moved from, but is still considered "borrowed".
577+
//
578+
// requires_storage := init | borrowed
579+
//
580+
// Just like when determining what locals are live at yield points, there is no need
581+
// to look at storage liveness here, since `init | borrowed` is strictly more precise.
582+
//
583+
// FIXME: This function is called in a loop, so it might be better to pass in a temporary
584+
// bitset rather than cloning here.
585+
let mut requires_storage = init.get().clone();
586+
requires_storage.union(borrowed.get());
587+
588+
for local in requires_storage.iter() {
589+
local_conflicts.union_row_with(&requires_storage, local);
590+
}
591+
592+
// `>1` because the `self` argument always requires storage.
593+
if requires_storage.count() > 1 {
594+
trace!("requires_storage={:?}", requires_storage);
595+
}
596+
}
597+
572598
/// For every saved local, looks for which locals are StorageLive at the same
573599
/// time. Generates a bitset for every local of all the other locals that may be
574600
/// StorageLive simultaneously with that local. This is used in the layout
@@ -577,30 +603,45 @@ fn compute_storage_conflicts(
577603
body: &'mir Body<'tcx>,
578604
stored_locals: &BitSet<Local>,
579605
always_live_locals: storage::AlwaysLiveLocals,
580-
requires_storage: dataflow::Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>,
606+
mut init: dataflow::ResultsCursor<'mir, 'tcx, MaybeInitializedLocals>,
607+
mut borrowed: dataflow::ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
581608
) -> BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal> {
582-
assert_eq!(body.local_decls.len(), stored_locals.domain_size());
583-
584609
debug!("compute_storage_conflicts({:?})", body.span);
585-
debug!("always_live = {:?}", always_live_locals);
586-
587-
// Locals that are always live or ones that need to be stored across
588-
// suspension points are not eligible for overlap.
589-
let mut ineligible_locals = always_live_locals.into_inner();
590-
ineligible_locals.intersect(stored_locals);
610+
assert_eq!(body.local_decls.len(), stored_locals.domain_size());
591611

592-
// Compute the storage conflicts for all eligible locals.
593-
let mut visitor = StorageConflictVisitor {
594-
body,
595-
stored_locals: &stored_locals,
596-
local_conflicts: BitMatrix::from_row_n(&ineligible_locals, body.local_decls.len()),
597-
};
612+
// Locals that are always live conflict with all other locals.
613+
//
614+
// FIXME: Why do we need to handle locals without `Storage{Live,Dead}` specially here?
615+
// Shouldn't it be enough to know whether they are initialized?
616+
let always_live_locals = always_live_locals.into_inner();
617+
let mut local_conflicts = BitMatrix::from_row_n(&always_live_locals, body.local_decls.len());
618+
619+
// Visit every reachable statement and terminator. The exact order does not matter. When two
620+
// locals are live at the same point in time, add an entry in the conflict matrix.
621+
for (block, data) in traversal::preorder(body) {
622+
// Ignore unreachable blocks.
623+
if data.terminator().kind == TerminatorKind::Unreachable {
624+
continue;
625+
}
598626

599-
// Visit only reachable basic blocks. The exact order is not important.
600-
let reachable_blocks = traversal::preorder(body).map(|(bb, _)| bb);
601-
requires_storage.visit_with(body, reachable_blocks, &mut visitor);
627+
// Observe the dataflow state *before* all possible locations (statement or terminator) in
628+
// each basic block...
629+
for statement_index in 0..=data.statements.len() {
630+
let loc = Location { block, statement_index };
631+
trace!("record conflicts at {:?}", loc);
632+
init.seek_before_primary_effect(loc);
633+
borrowed.seek_before_primary_effect(loc);
634+
record_conflicts_at_curr_loc(&mut local_conflicts, &init, &borrowed);
635+
}
602636

603-
let local_conflicts = visitor.local_conflicts;
637+
// ...and then observe the state *after* the terminator effect is applied. As long as
638+
// neither `init` nor `borrowed` has a "before" effect, we will observe all possible
639+
// dataflow states here or in the loop above.
640+
trace!("record conflicts at end of {:?}", block);
641+
init.seek_to_block_end(block);
642+
borrowed.seek_to_block_end(block);
643+
record_conflicts_at_curr_loc(&mut local_conflicts, &init, &borrowed);
644+
}
604645

605646
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
606647
//
@@ -612,7 +653,7 @@ fn compute_storage_conflicts(
612653
let mut storage_conflicts = BitMatrix::new(stored_locals.count(), stored_locals.count());
613654
for (idx_a, local_a) in stored_locals.iter().enumerate() {
614655
let saved_local_a = GeneratorSavedLocal::new(idx_a);
615-
if ineligible_locals.contains(local_a) {
656+
if always_live_locals.contains(local_a) {
616657
// Conflicts with everything.
617658
storage_conflicts.insert_all_into_row(saved_local_a);
618659
} else {
@@ -628,56 +669,6 @@ fn compute_storage_conflicts(
628669
storage_conflicts
629670
}
630671

631-
struct StorageConflictVisitor<'mir, 'tcx, 's> {
632-
body: &'mir Body<'tcx>,
633-
stored_locals: &'s BitSet<Local>,
634-
// FIXME(tmandry): Consider using sparse bitsets here once we have good
635-
// benchmarks for generators.
636-
local_conflicts: BitMatrix<Local, Local>,
637-
}
638-
639-
impl dataflow::ResultsVisitor<'mir, 'tcx> for StorageConflictVisitor<'mir, 'tcx, '_> {
640-
type FlowState = BitSet<Local>;
641-
642-
fn visit_statement_before_primary_effect(
643-
&mut self,
644-
state: &Self::FlowState,
645-
_statement: &'mir Statement<'tcx>,
646-
loc: Location,
647-
) {
648-
self.apply_state(state, loc);
649-
}
650-
651-
fn visit_terminator_before_primary_effect(
652-
&mut self,
653-
state: &Self::FlowState,
654-
_terminator: &'mir Terminator<'tcx>,
655-
loc: Location,
656-
) {
657-
self.apply_state(state, loc);
658-
}
659-
}
660-
661-
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
662-
fn apply_state(&mut self, flow_state: &BitSet<Local>, loc: Location) {
663-
// Ignore unreachable blocks.
664-
if self.body.basic_blocks()[loc.block].terminator().kind == TerminatorKind::Unreachable {
665-
return;
666-
}
667-
668-
let mut eligible_storage_live = flow_state.clone();
669-
eligible_storage_live.intersect(&self.stored_locals);
670-
671-
for local in eligible_storage_live.iter() {
672-
self.local_conflicts.union_row_with(&eligible_storage_live, local);
673-
}
674-
675-
if eligible_storage_live.count() > 1 {
676-
trace!("at {:?}, eligible_storage_live={:?}", loc, eligible_storage_live);
677-
}
678-
}
679-
}
680-
681672
fn compute_layout<'tcx>(
682673
tcx: TyCtxt<'tcx>,
683674
source: MirSource<'tcx>,

‎src/test/ui/async-await/async-fn-size-moved-locals.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,5 @@ fn main() {
114114
assert_eq!(1026, std::mem::size_of_val(&single_with_noop()));
115115
assert_eq!(3078, std::mem::size_of_val(&joined()));
116116
assert_eq!(3079, std::mem::size_of_val(&joined_with_noop()));
117-
assert_eq!(7181, std::mem::size_of_val(&mixed_sizes()));
117+
assert_eq!(6157, std::mem::size_of_val(&mixed_sizes()));
118118
}

‎src/test/ui/generator/size-moved-locals.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,6 @@ fn overlap_x_and_y() -> impl Generator<Yield = (), Return = ()> {
7272
fn main() {
7373
assert_eq!(1025, std::mem::size_of_val(&move_before_yield()));
7474
assert_eq!(1026, std::mem::size_of_val(&move_before_yield_with_noop()));
75-
assert_eq!(2051, std::mem::size_of_val(&overlap_move_points()));
75+
assert_eq!(1027, std::mem::size_of_val(&overlap_move_points()));
7676
assert_eq!(1026, std::mem::size_of_val(&overlap_x_and_y()));
7777
}

0 commit comments

Comments
 (0)
Please sign in to comment.