Skip to content

Commit

Permalink
Auto merge of #69716 - jonas-schievink:generator-size, r=tmandry
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
bors committed Mar 14, 2020
2 parents be055d9 + b26e27c commit 5ed3453
Show file tree
Hide file tree
Showing 11 changed files with 120 additions and 31 deletions.
50 changes: 29 additions & 21 deletions src/librustc_mir/dataflow/generic/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::borrow::Borrow;

use rustc::mir::{self, BasicBlock, Location};
use rustc::mir::{self, BasicBlock, Location, TerminatorKind};
use rustc_index::bit_set::BitSet;

use super::{Analysis, Results};
Expand All @@ -29,14 +29,14 @@ where

pos: CursorPosition,

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

impl<'mir, 'tcx, A, R> ResultsCursor<'mir, 'tcx, A, R>
Expand All @@ -50,7 +50,7 @@ where
body,
pos: CursorPosition::BlockStart(mir::START_BLOCK),
state: results.borrow().entry_sets[mir::START_BLOCK].clone(),
call_return_effect_applied: false,
success_effect_applied: false,
results,
}
}
Expand All @@ -76,14 +76,14 @@ where
pub fn seek_to_block_start(&mut self, block: BasicBlock) {
self.state.overwrite(&self.results.borrow().entry_sets[block]);
self.pos = CursorPosition::BlockStart(block);
self.call_return_effect_applied = false;
self.success_effect_applied = false;
}

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

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

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

self.seek_(target, true);

if target != terminator_loc {
if target != terminator_loc || self.success_effect_applied {
return;
}

// Apply the effect of the "success" path of the terminator.

self.success_effect_applied = true;
let terminator = self.body.basic_blocks()[target.block].terminator();
if let mir::TerminatorKind::Call {
destination: Some((return_place, _)), func, args, ..
} = &terminator.kind
{
if !self.call_return_effect_applied {
self.call_return_effect_applied = true;
match &terminator.kind {
TerminatorKind::Call { destination: Some((return_place, _)), func, args, .. } => {
self.results.borrow().analysis.apply_call_return_effect(
&mut self.state,
target.block,
Expand All @@ -138,6 +138,14 @@ where
return_place,
);
}
TerminatorKind::Yield { resume, resume_arg, .. } => {
self.results.borrow().analysis.apply_yield_resume_effect(
&mut self.state,
*resume,
resume_arg,
);
}
_ => {}
}
}

Expand Down Expand Up @@ -172,7 +180,7 @@ where
self.seek_to_block_start(target.block)
}

// N.B., `call_return_effect_applied` is checked in `seek_after`, not here.
// N.B., `success_effect_applied` is checked in `seek_after`, not here.
_ => (),
}

Expand Down
9 changes: 6 additions & 3 deletions src/librustc_mir/dataflow/generic/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,15 +218,18 @@ where

Goto { target }
| Assert { target, cleanup: None, .. }
| Yield { resume: target, drop: None, .. }
| Drop { target, location: _, unwind: None }
| DropAndReplace { target, value: _, location: _, unwind: None } => {
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list)
}

Yield { resume: target, drop: Some(drop), .. } => {
Yield { resume: target, drop, resume_arg, .. } => {
if let Some(drop) = drop {
self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
}

self.analysis.apply_yield_resume_effect(in_out, target, &resume_arg);
self.propagate_bits_into_entry_set_for(in_out, target, dirty_list);
self.propagate_bits_into_entry_set_for(in_out, drop, dirty_list);
}

Assert { target, cleanup: Some(unwind), .. }
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/dataflow/generic/graphviz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ where
)?;

let state_on_unwind = this.results.get().clone();
this.results.seek_after_assume_call_returns(terminator_loc);
this.results.seek_after_assume_success(terminator_loc);
write_diff(w, this.results.analysis(), &state_on_unwind, this.results.get())?;

write!(w, "</td>")
Expand Down
32 changes: 32 additions & 0 deletions src/librustc_mir/dataflow/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,20 @@ pub trait Analysis<'tcx>: AnalysisDomain<'tcx> {
return_place: &mir::Place<'tcx>,
);

/// Updates the current dataflow state with the effect of resuming from a `Yield` terminator.
///
/// This is similar to `apply_call_return_effect` in that it only takes place after the
/// generator is resumed, not when it is dropped.
///
/// By default, no effects happen.
fn apply_yield_resume_effect(
&self,
_state: &mut BitSet<Self::Idx>,
_resume_block: BasicBlock,
_resume_place: &mir::Place<'tcx>,
) {
}

/// Updates the current dataflow state with the effect of taking a particular branch in a
/// `SwitchInt` terminator.
///
Expand Down Expand Up @@ -284,6 +298,15 @@ pub trait GenKillAnalysis<'tcx>: Analysis<'tcx> {
return_place: &mir::Place<'tcx>,
);

/// See `Analysis::apply_yield_resume_effect`.
fn yield_resume_effect(
&self,
_trans: &mut BitSet<Self::Idx>,
_resume_block: BasicBlock,
_resume_place: &mir::Place<'tcx>,
) {
}

/// See `Analysis::apply_discriminant_switch_effect`.
fn discriminant_switch_effect(
&self,
Expand Down Expand Up @@ -347,6 +370,15 @@ where
self.call_return_effect(state, block, func, args, return_place);
}

fn apply_yield_resume_effect(
&self,
state: &mut BitSet<Self::Idx>,
resume_block: BasicBlock,
resume_place: &mir::Place<'tcx>,
) {
self.yield_resume_effect(state, resume_block, resume_place);
}

fn apply_discriminant_switch_effect(
&self,
state: &mut BitSet<Self::Idx>,
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_mir/dataflow/generic/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ fn cursor_seek() {

cursor.seek_after(call_terminator_loc);
assert!(!cursor.get().contains(call_return_effect));
cursor.seek_after_assume_call_returns(call_terminator_loc);
cursor.seek_after_assume_success(call_terminator_loc);
assert!(cursor.get().contains(call_return_effect));

let every_target = || {
Expand All @@ -310,7 +310,7 @@ fn cursor_seek() {
BlockStart(block) => cursor.seek_to_block_start(block),
Before(loc) => cursor.seek_before(loc),
After(loc) => cursor.seek_after(loc),
AfterAssumeCallReturns(loc) => cursor.seek_after_assume_call_returns(loc),
AfterAssumeCallReturns(loc) => cursor.seek_after_assume_success(loc),
}

assert_eq!(cursor.get(), &cursor.analysis().expected_state_at_target(targ));
Expand Down
18 changes: 16 additions & 2 deletions src/librustc_mir/dataflow/impls/storage_liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,16 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir,
self.borrowed_locals.borrow().analysis().terminator_effect(trans, terminator, loc);

match &terminator.kind {
TerminatorKind::Call { destination: Some((place, _)), .. }
| TerminatorKind::Yield { resume_arg: place, .. } => {
TerminatorKind::Call { destination: Some((place, _)), .. } => {
trans.gen(place.local);
}

// Note that we do *not* gen the `resume_arg` of `Yield` terminators. The reason for
// that is that a `yield` will return from the function, and `resume_arg` is written
// only when the generator is later resumed. Unlike `Call`, this doesn't require the
// place to have storage *before* the yield, only after.
TerminatorKind::Yield { .. } => {}

// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
TerminatorKind::Call { destination: None, .. }
Expand Down Expand Up @@ -230,6 +235,15 @@ impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir,
) {
trans.gen(return_place.local);
}

fn yield_resume_effect(
&self,
trans: &mut BitSet<Self::Idx>,
_resume_block: BasicBlock,
resume_place: &mir::Place<'tcx>,
) {
trans.gen(resume_place.local);
}
}

impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/transform/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ fn locals_live_across_suspend_points(
let mut live_locals_here = storage_required;
live_locals_here.intersect(&liveness.outs[block]);

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

debug!("loc = {:?}, live_locals_here = {:?}", loc, live_locals_here);
Expand Down
6 changes: 5 additions & 1 deletion src/test/ui/generator/issue-69039.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@

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

fn mkstr(my_name: String, my_mood: String) -> String {
format!("{} is {}", my_name.trim(), my_mood.trim())
}

fn my_scenario() -> impl Generator<String, Yield = &'static str, Return = String> {
|_arg: String| {
let my_name = yield "What is your name?";
let my_mood = yield "How are you feeling?";
format!("{} is {}", my_name.trim(), my_mood.trim())
mkstr(my_name, my_mood)
}
}

Expand Down
28 changes: 28 additions & 0 deletions src/test/ui/generator/resume-arg-size.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![feature(generators)]

// run-pass

use std::mem::size_of_val;

fn main() {
// Generator taking a `Copy`able resume arg.
let gen_copy = |mut x: usize| {
loop {
drop(x);
x = yield;
}
};

// Generator taking a non-`Copy` resume arg.
let gen_move = |mut x: Box<usize>| {
loop {
drop(x);
x = yield;
}
};

// Neither of these generators have the resume arg live across the `yield`, so they should be
// 4 Bytes in size (only storing the discriminant)
assert_eq!(size_of_val(&gen_copy), 4);
assert_eq!(size_of_val(&gen_move), 4);
}

0 comments on commit 5ed3453

Please sign in to comment.