Skip to content

Commit 31d892a

Browse files
committed
Fix liveness analysis for yield terminators
A resume place is evaluated and assigned to only after a yield terminator resumes. Ensure that locals used when evaluating the resume place are live across the yield.
1 parent 5462da5 commit 31d892a

File tree

1 file changed

+61
-32
lines changed

1 file changed

+61
-32
lines changed

compiler/rustc_mir_dataflow/src/impls/liveness.rs

+61-32
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,6 @@ use crate::{Analysis, AnalysisDomain, Backward, CallReturnPlaces, GenKill, GenKi
2323
/// [liveness]: https://en.wikipedia.org/wiki/Live_variable_analysis
2424
pub struct MaybeLiveLocals;
2525

26-
impl MaybeLiveLocals {
27-
fn transfer_function<'a, T>(&self, trans: &'a mut T) -> TransferFunction<'a, T> {
28-
TransferFunction(trans)
29-
}
30-
}
31-
3226
impl<'tcx> AnalysisDomain<'tcx> for MaybeLiveLocals {
3327
type Domain = ChunkedBitSet<Local>;
3428
type Direction = Backward;
@@ -54,7 +48,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
5448
statement: &mir::Statement<'tcx>,
5549
location: Location,
5650
) {
57-
self.transfer_function(trans).visit_statement(statement, location);
51+
TransferFunction(trans).visit_statement(statement, location);
5852
}
5953

6054
fn terminator_effect(
@@ -63,7 +57,7 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
6357
terminator: &mir::Terminator<'tcx>,
6458
location: Location,
6559
) {
66-
self.transfer_function(trans).visit_terminator(terminator, location);
60+
TransferFunction(trans).visit_terminator(terminator, location);
6761
}
6862

6963
fn call_return_effect(
@@ -85,9 +79,11 @@ impl<'tcx> GenKillAnalysis<'tcx> for MaybeLiveLocals {
8579
_resume_block: mir::BasicBlock,
8680
resume_place: mir::Place<'tcx>,
8781
) {
88-
if let Some(local) = resume_place.as_local() {
89-
trans.kill(local);
90-
}
82+
YieldResumeEffect(trans).visit_place(
83+
&resume_place,
84+
PlaceContext::MutatingUse(MutatingUseContext::Yield),
85+
Location::START,
86+
)
9187
}
9288
}
9389

@@ -98,23 +94,58 @@ where
9894
T: GenKill<Local>,
9995
{
10096
fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
101-
let local = place.local;
97+
if let PlaceContext::MutatingUse(MutatingUseContext::Yield) = context {
98+
// The resume place is evaluated and assigned to only after generator resumes, so its
99+
// effect is handled separately in `yield_resume_effect`.
100+
return;
101+
}
102+
103+
match DefUse::for_place(*place, context) {
104+
Some(DefUse::Def) => {
105+
if let PlaceContext::MutatingUse(
106+
MutatingUseContext::Call | MutatingUseContext::AsmOutput,
107+
) = context
108+
{
109+
// For the associated terminators, this is only a `Def` when the terminator returns
110+
// "successfully." As such, we handle this case separately in `call_return_effect`
111+
// above. However, if the place looks like `*_5`, this is still unconditionally a use of
112+
// `_5`.
113+
} else {
114+
self.0.kill(place.local);
115+
}
116+
}
117+
Some(DefUse::Use) => self.0.gen(place.local),
118+
None => {}
119+
}
102120

103-
// We purposefully do not call `super_place` here to avoid calling `visit_local` for this
104-
// place with one of the `Projection` variants of `PlaceContext`.
105121
self.visit_projection(place.as_ref(), context, location);
122+
}
106123

107-
match DefUse::for_place(*place, context) {
124+
fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
125+
match DefUse::for_place(local.into(), context) {
108126
Some(DefUse::Def) => self.0.kill(local),
109127
Some(DefUse::Use) => self.0.gen(local),
110128
None => {}
111129
}
112130
}
131+
}
132+
133+
struct YieldResumeEffect<'a, T>(&'a mut T);
134+
135+
impl<'tcx, T> Visitor<'tcx> for YieldResumeEffect<'_, T>
136+
where
137+
T: GenKill<Local>,
138+
{
139+
fn visit_place(&mut self, place: &mir::Place<'tcx>, context: PlaceContext, location: Location) {
140+
match DefUse::for_place(*place, context) {
141+
Some(DefUse::Def) => self.0.kill(place.local),
142+
Some(DefUse::Use) => self.0.gen(place.local),
143+
None => {}
144+
}
145+
self.visit_projection(place.as_ref(), context, location);
146+
}
113147

114148
fn visit_local(&mut self, local: Local, context: PlaceContext, _: Location) {
115-
// Because we do not call `super_place` above, `visit_local` is only called for locals that
116-
// do not appear as part of a `Place` in the MIR. This handles cases like the implicit use
117-
// of the return place in a `Return` terminator or the index in an `Index` projection.
118149
match DefUse::for_place(local.into(), context) {
119150
Some(DefUse::Def) => self.0.kill(local),
120151
Some(DefUse::Use) => self.0.gen(local),
@@ -134,7 +165,13 @@ impl DefUse {
134165
match context {
135166
PlaceContext::NonUse(_) => None,
136167

137-
PlaceContext::MutatingUse(MutatingUseContext::Store | MutatingUseContext::Deinit) => {
168+
PlaceContext::MutatingUse(
169+
MutatingUseContext::Call
170+
| MutatingUseContext::Yield
171+
| MutatingUseContext::AsmOutput
172+
| MutatingUseContext::Store
173+
| MutatingUseContext::Deinit,
174+
) => {
138175
if place.is_indirect() {
139176
// Treat derefs as a use of the base local. `*p = 4` is not a def of `p` but a
140177
// use.
@@ -152,16 +189,6 @@ impl DefUse {
152189
place.is_indirect().then_some(DefUse::Use)
153190
}
154191

155-
// For the associated terminators, this is only a `Def` when the terminator returns
156-
// "successfully." As such, we handle this case separately in `call_return_effect`
157-
// above. However, if the place looks like `*_5`, this is still unconditionally a use of
158-
// `_5`.
159-
PlaceContext::MutatingUse(
160-
MutatingUseContext::Call
161-
| MutatingUseContext::Yield
162-
| MutatingUseContext::AsmOutput,
163-
) => place.is_indirect().then_some(DefUse::Use),
164-
165192
// All other contexts are uses...
166193
PlaceContext::MutatingUse(
167194
MutatingUseContext::AddressOf
@@ -290,8 +317,10 @@ impl<'a, 'tcx> Analysis<'tcx> for MaybeTransitiveLiveLocals<'a> {
290317
_resume_block: mir::BasicBlock,
291318
resume_place: mir::Place<'tcx>,
292319
) {
293-
if let Some(local) = resume_place.as_local() {
294-
trans.remove(local);
295-
}
320+
YieldResumeEffect(trans).visit_place(
321+
&resume_place,
322+
PlaceContext::MutatingUse(MutatingUseContext::Yield),
323+
Location::START,
324+
)
296325
}
297326
}

0 commit comments

Comments
 (0)