Skip to content

Commit 5ed3453

Browse files
committed
Auto merge of #69716 - jonas-schievink:generator-size, r=tmandry
Don't store locals in generators that are immediately overwritten with the resume argument This fixes #69672 and makes #69033 pass the async fn size tests again (in other words, there will be no size regression of async fn if both this and #69033 land). ~~This is a small botch and I'd rather have a more precise analysis, but that seems much harder to pull off, so this special-cases `Yield` terminators that store the resume argument into a simple local (ie. without any field projections) and explicitly marks that local as "not live" in the suspend point of that yield. We know that this local does not need to be stored in the generator for this suspend point because the next resume would immediately overwrite it with the passed-in resume argument anyways. The local might still end up in the state if it is used across another yield.~~ (this now properly updates the dataflow framework to handle this case)
2 parents be055d9 + b26e27c commit 5ed3453

11 files changed

+120
-31
lines changed

src/librustc_mir/dataflow/generic/cursor.rs

+29-21
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
33
use std::borrow::Borrow;
44

5-
use rustc::mir::{self, BasicBlock, Location};
5+
use rustc::mir::{self, BasicBlock, Location, TerminatorKind};
66
use rustc_index::bit_set::BitSet;
77

88
use super::{Analysis, Results};
@@ -29,14 +29,14 @@ where
2929

3030
pos: CursorPosition,
3131

32-
/// When this flag is set, the cursor is pointing at a `Call` terminator whose call return
33-
/// effect has been applied to `state`.
32+
/// When this flag is set, the cursor is pointing at a `Call` or `Yield` terminator whose call
33+
/// return or resume effect has been applied to `state`.
3434
///
35-
/// This flag helps to ensure that multiple calls to `seek_after_assume_call_returns` with the
35+
/// This flag helps to ensure that multiple calls to `seek_after_assume_success` with the
3636
/// same target will result in exactly one invocation of `apply_call_return_effect`. It is
3737
/// sufficient to clear this only in `seek_to_block_start`, since seeking away from a
3838
/// terminator will always require a cursor reset.
39-
call_return_effect_applied: bool,
39+
success_effect_applied: bool,
4040
}
4141

4242
impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
@@ -50,7 +50,7 @@ where
5050
body,
5151
pos: CursorPosition::BlockStart(mir::START_BLOCK),
5252
state: results.borrow().entry_sets[mir::START_BLOCK].clone(),
53-
call_return_effect_applied: false,
53+
success_effect_applied: false,
5454
results,
5555
}
5656
}
@@ -76,14 +76,14 @@ where
7676
pub fn seek_to_block_start(&mut self, block: BasicBlock) {
7777
self.state.overwrite(&self.results.borrow().entry_sets[block]);
7878
self.pos = CursorPosition::BlockStart(block);
79-
self.call_return_effect_applied = false;
79+
self.success_effect_applied = false;
8080
}
8181

8282
/// Advances the cursor to hold all effects up to and including to the "before" effect of the
8383
/// statement (or terminator) at the given location.
8484
///
8585
/// If you wish to observe the full effect of a statement or terminator, not just the "before"
86-
/// effect, use `seek_after` or `seek_after_assume_call_returns`.
86+
/// effect, use `seek_after` or `seek_after_assume_success`.
8787
pub fn seek_before(&mut self, target: Location) {
8888
assert!(target <= self.body.terminator_loc(target.block));
8989
self.seek_(target, false);
@@ -93,15 +93,15 @@ where
9393
/// terminators) up to and including the `target`.
9494
///
9595
/// If the `target` is a `Call` terminator, any call return effect for that terminator will
96-
/// **not** be observed. Use `seek_after_assume_call_returns` if you wish to observe the call
96+
/// **not** be observed. Use `seek_after_assume_success` if you wish to observe the call
9797
/// return effect.
9898
pub fn seek_after(&mut self, target: Location) {
9999
assert!(target <= self.body.terminator_loc(target.block));
100100

101101
// If we have already applied the call return effect, we are currently pointing at a `Call`
102102
// terminator. Unconditionally reset the dataflow cursor, since there is no way to "undo"
103103
// the call return effect.
104-
if self.call_return_effect_applied {
104+
if self.success_effect_applied {
105105
self.seek_to_block_start(target.block);
106106
}
107107

@@ -111,25 +111,25 @@ where
111111
/// Advances the cursor to hold all effects up to and including of the statement (or
112112
/// terminator) at the given location.
113113
///
114-
/// If the `target` is a `Call` terminator, any call return effect for that terminator will
115-
/// be observed. Use `seek_after` if you do **not** wish to observe the call return effect.
116-
pub fn seek_after_assume_call_returns(&mut self, target: Location) {
114+
/// If the `target` is a `Call` or `Yield` terminator, any call return or resume effect for that
115+
/// terminator will be observed. Use `seek_after` if you do **not** wish to observe the
116+
/// "success" effect.
117+
pub fn seek_after_assume_success(&mut self, target: Location) {
117118
let terminator_loc = self.body.terminator_loc(target.block);
118119
assert!(target.statement_index <= terminator_loc.statement_index);
119120

120121
self.seek_(target, true);
121122

122-
if target != terminator_loc {
123+
if target != terminator_loc || self.success_effect_applied {
123124
return;
124125
}
125126

127+
// Apply the effect of the "success" path of the terminator.
128+
129+
self.success_effect_applied = true;
126130
let terminator = self.body.basic_blocks()[target.block].terminator();
127-
if let mir::TerminatorKind::Call {
128-
destination: Some((return_place, _)), func, args, ..
129-
} = &terminator.kind
130-
{
131-
if !self.call_return_effect_applied {
132-
self.call_return_effect_applied = true;
131+
match &terminator.kind {
132+
TerminatorKind::Call { destination: Some((return_place, _)), func, args, .. } => {
133133
self.results.borrow().analysis.apply_call_return_effect(
134134
&mut self.state,
135135
target.block,
@@ -138,6 +138,14 @@ where
138138
return_place,
139139
);
140140
}
141+
TerminatorKind::Yield { resume, resume_arg, .. } => {
142+
self.results.borrow().analysis.apply_yield_resume_effect(
143+
&mut self.state,
144+
*resume,
145+
resume_arg,
146+
);
147+
}
148+
_ => {}
141149
}
142150
}
143151

@@ -172,7 +180,7 @@ where
172180
self.seek_to_block_start(target.block)
173181
}
174182

175-
// N.B., `call_return_effect_applied` is checked in `seek_after`, not here.
183+
// N.B., `success_effect_applied` is checked in `seek_after`, not here.
176184
_ => (),
177185
}
178186

src/librustc_mir/dataflow/generic/engine.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,18 @@ where
218218

219219
Goto { target }
220220
| Assert { target, cleanup: None, .. }
221-
| Yield { resume: target, drop: None, .. }
222221
| Drop { target, location: _, unwind: None }
223222
| DropAndReplace { target, value: _, location: _, unwind: None } => {
224223
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list)
225224
}
226225

227-
Yield { resume: target, drop: Some(drop), .. } => {
226+
Yield { resume: target, drop, resume_arg, .. } => {
227+
if let Some(drop) = drop {
228+
self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
229+
}
230+
231+
self.analysis.apply_yield_resume_effect(in_out, target, &resume_arg);
228232
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
229-
self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
230233
}
231234

232235
Assert { target, cleanup: Some(unwind), .. }

src/librustc_mir/dataflow/generic/graphviz.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ where
241241
)?;
242242

243243
let state_on_unwind = this.results.get().clone();
244-
this.results.seek_after_assume_call_returns(terminator_loc);
244+
this.results.seek_after_assume_success(terminator_loc);
245245
write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;
246246

247247
write!(w, "</td>")

src/librustc_mir/dataflow/generic/mod.rs

+32
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,20 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
191191
return_place: &mir::Place<'tcx>,
192192
);
193193

194+
/// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
195+
///
196+
/// This is similar to `apply_call_return_effect` in that it only takes place after the
197+
/// generator is resumed, not when it is dropped.
198+
///
199+
/// By default, no effects happen.
200+
fn apply_yield_resume_effect(
201+
&self,
202+
_state: &mut BitSet<Self::Idx>,
203+
_resume_block: BasicBlock,
204+
_resume_place: &mir::Place<'tcx>,
205+
) {
206+
}
207+
194208
/// Updates the current dataflow state with the effect of taking a particular branch in a
195209
/// `SwitchInt` terminator.
196210
///
@@ -284,6 +298,15 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
284298
return_place: &mir::Place<'tcx>,
285299
);
286300

301+
/// See `Analysis::apply_yield_resume_effect`.
302+
fn yield_resume_effect(
303+
&self,
304+
_trans: &mut BitSet<Self::Idx>,
305+
_resume_block: BasicBlock,
306+
_resume_place: &mir::Place<'tcx>,
307+
) {
308+
}
309+
287310
/// See `Analysis::apply_discriminant_switch_effect`.
288311
fn discriminant_switch_effect(
289312
&self,
@@ -347,6 +370,15 @@ where
347370
self.call_return_effect(state, block, func, args, return_place);
348371
}
349372

373+
fn apply_yield_resume_effect(
374+
&self,
375+
state: &mut BitSet<Self::Idx>,
376+
resume_block: BasicBlock,
377+
resume_place: &mir::Place<'tcx>,
378+
) {
379+
self.yield_resume_effect(state, resume_block, resume_place);
380+
}
381+
350382
fn apply_discriminant_switch_effect(
351383
&self,
352384
state: &mut BitSet<Self::Idx>,

src/librustc_mir/dataflow/generic/tests.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ fn cursor_seek() {
294294

295295
cursor.seek_after(call_terminator_loc);
296296
assert!(!cursor.get().contains(call_return_effect));
297-
cursor.seek_after_assume_call_returns(call_terminator_loc);
297+
cursor.seek_after_assume_success(call_terminator_loc);
298298
assert!(cursor.get().contains(call_return_effect));
299299

300300
let every_target = || {
@@ -310,7 +310,7 @@ fn cursor_seek() {
310310
BlockStart(block) => cursor.seek_to_block_start(block),
311311
Before(loc) => cursor.seek_before(loc),
312312
After(loc) => cursor.seek_after(loc),
313-
AfterAssumeCallReturns(loc) => cursor.seek_after_assume_call_returns(loc),
313+
AfterAssumeCallReturns(loc) => cursor.seek_after_assume_success(loc),
314314
}
315315

316316
assert_eq!(cursor.get(), &cursor.analysis().expected_state_at_target(targ));

src/librustc_mir/dataflow/impls/storage_liveness.rs

+16-2
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,16 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir,
161161
self.borrowed_locals.borrow().analysis().terminator_effect(trans, terminator, loc);
162162

163163
match &terminator.kind {
164-
TerminatorKind::Call { destination: Some((place, _)), .. }
165-
| TerminatorKind::Yield { resume_arg: place, .. } => {
164+
TerminatorKind::Call { destination: Some((place, _)), .. } => {
166165
trans.gen(place.local);
167166
}
168167

168+
// Note that we do *not* gen the `resume_arg` of `Yield` terminators. The reason for
169+
// that is that a `yield` will return from the function, and `resume_arg` is written
170+
// only when the generator is later resumed. Unlike `Call`, this doesn't require the
171+
// place to have storage *before* the yield, only after.
172+
TerminatorKind::Yield { .. } => {}
173+
169174
// Nothing to do for these. Match exhaustively so this fails to compile when new
170175
// variants are added.
171176
TerminatorKind::Call { destination: None, .. }
@@ -230,6 +235,15 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir,
230235
) {
231236
trans.gen(return_place.local);
232237
}
238+
239+
fn yield_resume_effect(
240+
&self,
241+
trans: &mut BitSet<Self::Idx>,
242+
_resume_block: BasicBlock,
243+
resume_place: &mir::Place<'tcx>,
244+
) {
245+
trans.gen(resume_place.local);
246+
}
233247
}
234248

235249
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {

src/librustc_mir/transform/generator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ fn locals_live_across_suspend_points(
539539
let mut live_locals_here = storage_required;
540540
live_locals_here.intersect(&liveness.outs[block]);
541541

542-
// The generator argument is ignored
542+
// The generator argument is ignored.
543543
live_locals_here.remove(self_arg());
544544

545545
debug!("loc = {:?}, live_locals_here = {:?}", loc, live_locals_here);

src/test/ui/generator/issue-69039.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,15 @@
44

55
use std::ops::{Generator, GeneratorState};
66

7+
fn mkstr(my_name: String, my_mood: String) -> String {
8+
format!("{} is {}", my_name.trim(), my_mood.trim())
9+
}
10+
711
fn my_scenario() -> impl Generator<String, Yield = &'static str, Return = String> {
812
|_arg: String| {
913
let my_name = yield "What is your name?";
1014
let my_mood = yield "How are you feeling?";
11-
format!("{} is {}", my_name.trim(), my_mood.trim())
15+
mkstr(my_name, my_mood)
1216
}
1317
}
1418

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#![feature(generators)]
2+
3+
// run-pass
4+
5+
use std::mem::size_of_val;
6+
7+
fn main() {
8+
// Generator taking a `Copy`able resume arg.
9+
let gen_copy = |mut x: usize| {
10+
loop {
11+
drop(x);
12+
x = yield;
13+
}
14+
};
15+
16+
// Generator taking a non-`Copy` resume arg.
17+
let gen_move = |mut x: Box<usize>| {
18+
loop {
19+
drop(x);
20+
x = yield;
21+
}
22+
};
23+
24+
// Neither of these generators have the resume arg live across the `yield`, so they should be
25+
// 4 Bytes in size (only storing the discriminant)
26+
assert_eq!(size_of_val(&gen_copy), 4);
27+
assert_eq!(size_of_val(&gen_move), 4);
28+
}

0 commit comments

Comments
 (0)