Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix generator miscompilations #69302

Merged
merged 7 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/librustc/mir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,12 +519,12 @@ macro_rules! make_mir_visitor {
resume_arg,
drop: _,
} => {
self.visit_operand(value, source_location);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this reordering matter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem to matter from what I've seen, but it aligns the order of Yield with the one for Call. That seems less surprising to me.

self.visit_place(
resume_arg,
PlaceContext::MutatingUse(MutatingUseContext::Store),
source_location,
);
self.visit_operand(value, source_location);
}

}
Expand Down
76 changes: 59 additions & 17 deletions src/librustc_mir/dataflow/impls/storage_liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,18 +118,25 @@ impl<'mir, 'tcx> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx> {
self.borrowed_locals.borrow().analysis().statement_effect(sets, stmt, loc);

// If a place is assigned to in a statement, it needs storage for that statement.
match stmt.kind {
StatementKind::StorageDead(l) => sets.kill(l),
StatementKind::Assign(box (ref place, _))
| StatementKind::SetDiscriminant { box ref place, .. } => {
match &stmt.kind {
StatementKind::StorageDead(l) => sets.kill(*l),
StatementKind::Assign(box (place, _))
| StatementKind::SetDiscriminant { box place, .. } => {
sets.gen(place.local);
}
StatementKind::InlineAsm(box InlineAsm { ref outputs, .. }) => {
StatementKind::InlineAsm(box InlineAsm { outputs, .. }) => {
for place in &**outputs {
sets.gen(place.local);
}
}
_ => (),

// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
StatementKind::AscribeUserType(..)
| StatementKind::FakeRead(..)
| StatementKind::Nop
| StatementKind::Retag(..)
| StatementKind::StorageLive(..) => {}
}
}

Expand All @@ -145,23 +152,58 @@ impl<'mir, 'tcx> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx> {
// If a place is borrowed in a terminator, it needs storage for that terminator.
self.borrowed_locals.borrow().analysis().terminator_effect(sets, terminator, loc);

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

// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
TerminatorKind::Call { destination: None, .. }
| TerminatorKind::Abort
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
| TerminatorKind::FalseEdges { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {}
}
}

fn terminator_effect(&self, sets: &mut GenKillSet<Local>, loc: Location) {
// For call terminators the destination requires storage for the call
// and after the call returns successfully, but not after a panic.
// Since `propagate_call_unwind` doesn't exist, we have to kill the
// destination here, and then gen it again in `propagate_call_return`.
if let TerminatorKind::Call { destination: Some((ref place, _)), .. } =
self.body[loc.block].terminator().kind
{
if let Some(local) = place.as_local() {
sets.kill(local);
match &self.body[loc.block].terminator().kind {
// For call terminators the destination requires storage for the call
// and after the call returns successfully, but not after a panic.
// Since `propagate_call_unwind` doesn't exist, we have to kill the
// destination here, and then gen it again in `propagate_call_return`.
TerminatorKind::Call { destination: Some((Place { local, .. }, _)), .. } => {
sets.kill(*local);
}

// Nothing to do for these. Match exhaustively so this fails to compile when new
// variants are added.
TerminatorKind::Call { destination: None, .. }
| TerminatorKind::Yield { .. }
| TerminatorKind::Abort
| TerminatorKind::Assert { .. }
| TerminatorKind::Drop { .. }
| TerminatorKind::DropAndReplace { .. }
| TerminatorKind::FalseEdges { .. }
| TerminatorKind::FalseUnwind { .. }
| TerminatorKind::GeneratorDrop
| TerminatorKind::Goto { .. }
| TerminatorKind::Resume
| TerminatorKind::Return
| TerminatorKind::SwitchInt { .. }
| TerminatorKind::Unreachable => {}
}

self.check_for_move(sets, loc);
}

Expand Down
2 changes: 1 addition & 1 deletion src/librustc_mir/dataflow/move_paths/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,9 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
}

TerminatorKind::Yield { ref value, resume_arg: ref place, .. } => {
self.gather_operand(value);
self.create_move_path(place);
self.gather_init(place.as_ref(), InitKind::Deep);
self.gather_operand(value);
}

TerminatorKind::Drop { ref location, target: _, unwind: _ } => {
Expand Down
21 changes: 18 additions & 3 deletions src/librustc_mir/transform/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,18 +186,24 @@ fn self_arg() -> Local {
Local::new(1)
}

/// Generator have not been resumed yet
/// Generator has not been resumed yet.
const UNRESUMED: usize = GeneratorSubsts::UNRESUMED;
/// Generator has returned / is completed
/// Generator has returned / is completed.
const RETURNED: usize = GeneratorSubsts::RETURNED;
/// Generator has been poisoned
/// Generator has panicked and is poisoned.
const POISONED: usize = GeneratorSubsts::POISONED;

/// A `yield` point in the generator.
struct SuspensionPoint<'tcx> {
/// State discriminant used when suspending or resuming at this point.
state: usize,
/// The block to jump to after resumption.
resume: BasicBlock,
/// Where to move the resume argument after resumption.
resume_arg: Place<'tcx>,
/// Which block to jump to if the generator is dropped in this state.
drop: Option<BasicBlock>,
/// Set of locals that have live storage while at this suspension point.
storage_liveness: liveness::LiveVarSet,
}

Expand Down Expand Up @@ -325,6 +331,15 @@ impl MutVisitor<'tcx> for TransformVisitor<'tcx> {
// Yield
let state = 3 + self.suspension_points.len();

// The resume arg target location might itself be remapped if its base local is
// live across a yield.
let resume_arg =
if let Some(&(ty, variant, idx)) = self.remap.get(&resume_arg.local) {
self.make_field(variant, idx, ty)
} else {
resume_arg
};

self.suspension_points.push(SuspensionPoint {
state,
resume,
Expand Down
30 changes: 30 additions & 0 deletions src/test/ui/generator/issue-69039.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// run-pass

#![feature(generators, generator_trait)]

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

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())
}
}

fn main() {
let mut my_session = Box::pin(my_scenario());

assert_eq!(
my_session.as_mut().resume("_arg".to_string()),
GeneratorState::Yielded("What is your name?")
);
assert_eq!(
my_session.as_mut().resume("Your Name".to_string()),
GeneratorState::Yielded("How are you feeling?")
);
assert_eq!(
my_session.as_mut().resume("Sensory Organs".to_string()),
GeneratorState::Complete("Your Name is Sensory Organs".to_string())
);
}