Skip to content

Commit 458a3e7

Browse files
committed
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-
}

0 commit comments

Comments
 (0)