Auto merge of #69302 - jonas-schievink:yield-needs-storage, r=Zoxc
Fix generator miscompilations Fixes https://github.com/rust-lang/rust/issues/69039 r? @Zoxc
This commit is contained in:
commit
d735ede6eb
|
@ -519,12 +519,12 @@ macro_rules! make_mir_visitor {
|
||||||
resume_arg,
|
resume_arg,
|
||||||
drop: _,
|
drop: _,
|
||||||
} => {
|
} => {
|
||||||
|
self.visit_operand(value, source_location);
|
||||||
self.visit_place(
|
self.visit_place(
|
||||||
resume_arg,
|
resume_arg,
|
||||||
PlaceContext::MutatingUse(MutatingUseContext::Store),
|
PlaceContext::MutatingUse(MutatingUseContext::Store),
|
||||||
source_location,
|
source_location,
|
||||||
);
|
);
|
||||||
self.visit_operand(value, source_location);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,18 +118,25 @@ impl<'mir, 'tcx> BitDenotation<'tcx> for RequiresStorage<'mir, 'tcx> {
|
||||||
self.borrowed_locals.borrow().analysis().statement_effect(sets, stmt, loc);
|
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.
|
// If a place is assigned to in a statement, it needs storage for that statement.
|
||||||
match stmt.kind {
|
match &stmt.kind {
|
||||||
StatementKind::StorageDead(l) => sets.kill(l),
|
StatementKind::StorageDead(l) => sets.kill(*l),
|
||||||
StatementKind::Assign(box (ref place, _))
|
StatementKind::Assign(box (place, _))
|
||||||
| StatementKind::SetDiscriminant { box ref place, .. } => {
|
| StatementKind::SetDiscriminant { box place, .. } => {
|
||||||
sets.gen(place.local);
|
sets.gen(place.local);
|
||||||
}
|
}
|
||||||
StatementKind::InlineAsm(box InlineAsm { ref outputs, .. }) => {
|
StatementKind::InlineAsm(box InlineAsm { outputs, .. }) => {
|
||||||
for place in &**outputs {
|
for place in &**outputs {
|
||||||
sets.gen(place.local);
|
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(..) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
// If a place is borrowed in a terminator, it needs storage for that terminator.
|
||||||
self.borrowed_locals.borrow().analysis().terminator_effect(sets, terminator, loc);
|
self.borrowed_locals.borrow().analysis().terminator_effect(sets, terminator, loc);
|
||||||
|
|
||||||
if let TerminatorKind::Call { destination: Some((place, _)), .. } = terminator.kind {
|
match &terminator.kind {
|
||||||
sets.gen(place.local);
|
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) {
|
fn terminator_effect(&self, sets: &mut GenKillSet<Local>, loc: Location) {
|
||||||
// For call terminators the destination requires storage for the call
|
match &self.body[loc.block].terminator().kind {
|
||||||
// and after the call returns successfully, but not after a panic.
|
// For call terminators the destination requires storage for the call
|
||||||
// Since `propagate_call_unwind` doesn't exist, we have to kill the
|
// and after the call returns successfully, but not after a panic.
|
||||||
// destination here, and then gen it again in `propagate_call_return`.
|
// Since `propagate_call_unwind` doesn't exist, we have to kill the
|
||||||
if let TerminatorKind::Call { destination: Some((ref place, _)), .. } =
|
// destination here, and then gen it again in `propagate_call_return`.
|
||||||
self.body[loc.block].terminator().kind
|
TerminatorKind::Call { destination: Some((Place { local, .. }, _)), .. } => {
|
||||||
{
|
sets.kill(*local);
|
||||||
if let Some(local) = place.as_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);
|
self.check_for_move(sets, loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -381,9 +381,9 @@ impl<'b, 'a, 'tcx> Gatherer<'b, 'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminatorKind::Yield { ref value, resume_arg: ref place, .. } => {
|
TerminatorKind::Yield { ref value, resume_arg: ref place, .. } => {
|
||||||
|
self.gather_operand(value);
|
||||||
self.create_move_path(place);
|
self.create_move_path(place);
|
||||||
self.gather_init(place.as_ref(), InitKind::Deep);
|
self.gather_init(place.as_ref(), InitKind::Deep);
|
||||||
self.gather_operand(value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminatorKind::Drop { ref location, target: _, unwind: _ } => {
|
TerminatorKind::Drop { ref location, target: _, unwind: _ } => {
|
||||||
|
|
|
@ -186,18 +186,24 @@ fn self_arg() -> Local {
|
||||||
Local::new(1)
|
Local::new(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generator have not been resumed yet
|
/// Generator has not been resumed yet.
|
||||||
const UNRESUMED: usize = GeneratorSubsts::UNRESUMED;
|
const UNRESUMED: usize = GeneratorSubsts::UNRESUMED;
|
||||||
/// Generator has returned / is completed
|
/// Generator has returned / is completed.
|
||||||
const RETURNED: usize = GeneratorSubsts::RETURNED;
|
const RETURNED: usize = GeneratorSubsts::RETURNED;
|
||||||
/// Generator has been poisoned
|
/// Generator has panicked and is poisoned.
|
||||||
const POISONED: usize = GeneratorSubsts::POISONED;
|
const POISONED: usize = GeneratorSubsts::POISONED;
|
||||||
|
|
||||||
|
/// A `yield` point in the generator.
|
||||||
struct SuspensionPoint<'tcx> {
|
struct SuspensionPoint<'tcx> {
|
||||||
|
/// State discriminant used when suspending or resuming at this point.
|
||||||
state: usize,
|
state: usize,
|
||||||
|
/// The block to jump to after resumption.
|
||||||
resume: BasicBlock,
|
resume: BasicBlock,
|
||||||
|
/// Where to move the resume argument after resumption.
|
||||||
resume_arg: Place<'tcx>,
|
resume_arg: Place<'tcx>,
|
||||||
|
/// Which block to jump to if the generator is dropped in this state.
|
||||||
drop: Option<BasicBlock>,
|
drop: Option<BasicBlock>,
|
||||||
|
/// Set of locals that have live storage while at this suspension point.
|
||||||
storage_liveness: liveness::LiveVarSet,
|
storage_liveness: liveness::LiveVarSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -325,6 +331,15 @@ impl MutVisitor<'tcx> for TransformVisitor<'tcx> {
|
||||||
// Yield
|
// Yield
|
||||||
let state = 3 + self.suspension_points.len();
|
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 {
|
self.suspension_points.push(SuspensionPoint {
|
||||||
state,
|
state,
|
||||||
resume,
|
resume,
|
||||||
|
|
|
@ -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())
|
||||||
|
);
|
||||||
|
}
|
Loading…
Reference in New Issue