Change:
I tried this code: I expected to see see the output "Person is Mood", instead, I got a segfault: : <details<summaryBacktrace</summary<p</p</details
Heh, looks like my "fix" for the miscompilation I found while implementing this wasn't actually sufficient. Reduced to: Looking at the MIR, you can see: The first leads to , which stores the resume argument from in the generator state as and advances to the next state (discriminant 3). On the next call it resumes at , which doesn't store the resume argument in the state (it only moves it to ). Here, the argument should have ended up in the generator state as some field. It then sets the discriminant to 4 to resume at . The next call thinks is still live and uses it to call the function. MIR of (minus storage statements):
Hmm, on second thought, it seems weird that is not put in the generator state at all, I would expect the existing transform infrastructure to handle this. cc
Looking at the MIR generated for reduced test case we can see that the writes that should have been to is instead done to the generator : So this seems to be a problem in MIR construction.
Hmm: I assume this isn't correct anymore? ( probably should have been replaced with the newly introduced field in ? )
Well that would explain why the MIR looks wrong.
I suspect this is because doesn't consider terminators as writes.
Thought I'd fix the printing real quick I'll look into the real bug tomorrow or some time next week, but the pass does indeed look wrong now.
I don't know much about rustc internals but it seems these places seem relevant somehow?
Yes, that's the issue I was referring to.
This patch makes the generator layout look more reasonable (actually containing slots for the s), but doesn't fix the crash. In fact, it makes the test miscompile too:
Well, afaict the main issue here is that the transformation which replaces the s with generator struct accesses is run before creating the actual resume function which emits the store of the resume argument to a local which should've been replaced. This happens: before this: I've tested the follwing patch (Hacky, we probably want to use the somehow): (Most of the diff is not relevant to the patch, contains the fix). And it produces correct MIR code: EDIT: The original code contains a logic bug, every loop iteration a new empty is created , which is probably not intended, instead moving that binding to the outer scope should "fix" it. EDIT: And passes too. EDIT: Also I've just stupidly realized that the unwrap for should probably be avoided too since a local may not live across a suspension point.
I probably would have noticed that if not for that pesky segfault
That's a great start! I've opened a fix (+ cleanup + more docs) based on the same idea in

Discussion:
resume_arg, drop: _, } => { <ins> self.visit_operand(value, source_location); </ins> self.visit_place( resume_arg, PlaceContext::MutatingUse(MutatingUseContext::Store), source_location, ); <del> self.visit_operand(value, source_location); </del> } }

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. <del> match stmt.kind { StatementKind::StorageDead(l) => sets.kill(l), StatementKind::Assign(box (ref place, _)) | StatementKind::SetDiscriminant { box ref place, .. } => { </del> <ins> match &stmt.kind { StatementKind::StorageDead(l) => sets.kill(*l), StatementKind::Assign(box (place, _)) | StatementKind::SetDiscriminant { box place, .. } => { </ins> sets.gen(place.local); } <del> StatementKind::InlineAsm(box InlineAsm { ref outputs, .. }) => { </del> <ins> StatementKind::InlineAsm(box InlineAsm { outputs, .. }) => { </ins> for place in &**outputs { sets.gen(place.local); } } <del> _ => (), </del> <ins> // 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(..) => {} </ins> } }

// If a place is borrowed in a terminator, it needs storage for that terminator. self.borrowed_locals.borrow().analysis().terminator_effect(sets, terminator, loc); <del> if let TerminatorKind::Call { destination: Some((place, _)), .. } = terminator.kind { sets.gen(place.local); </del> <ins> 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 => {} </ins> } } fn terminator_effect(&self, sets: &mut GenKillSet<Local>, loc: Location) { <del> // 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); </del> <ins> 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); </ins> } <ins> // 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 => {} </ins> } self.check_for_move(sets, loc); }

} TerminatorKind::Yield { ref value, resume_arg: ref place, .. } => { <ins> self.gather_operand(value); </ins> self.create_move_path(place); self.gather_init(place.as_ref(), InitKind::Deep); <del> self.gather_operand(value); </del> } TerminatorKind::Drop { ref location, target: _, unwind: _ } => {

Local::new(1) } <del> /// Generator have not been resumed yet </del> <ins> /// Generator has not been resumed yet. </ins> const UNRESUMED: usize = GeneratorSubsts::UNRESUMED; <del> /// Generator has returned / is completed </del> <ins> /// Generator has returned / is completed. </ins> const RETURNED: usize = GeneratorSubsts::RETURNED; <del> /// Generator has been poisoned </del> <ins> /// Generator has panicked and is poisoned. </ins> const POISONED: usize = GeneratorSubsts::POISONED; <ins> /// A `yield` point in the generator. </ins> struct SuspensionPoint<'tcx> { <ins> /// State discriminant used when suspending or resuming at this point. </ins> state: usize, <ins> /// The block to jump to after resumption. </ins> resume: BasicBlock, <ins> /// Where to move the resume argument after resumption. </ins> resume_arg: Place<'tcx>, <ins> /// Which block to jump to if the generator is dropped in this state. </ins> drop: Option<BasicBlock>, <ins> /// Set of locals that have live storage while at this suspension point. </ins> storage_liveness: liveness::LiveVarSet, }

// Yield let state = 3 + self.suspension_points.len(); <ins> // 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 }; </ins> self.suspension_points.push(SuspensionPoint { state, resume,

<ins> // 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()) ); } </ins>