Change:
Tested with The following program which stores a generator in a static variable and then resumes it (no indirection / dynamic-dispatch involved): when fully optimized for the target () produces the following machine code for the function: This function resumes the generator stored in the static variable . The generator is extremely simple and contains no explicit panicking branches yet the machine code contains 2 panicking branches which are not necessary for memory safety (more on this later). If the program is slightly modified like this (semantics are not changed): Then is optimized to the following machine code, which is free of panicking branches: This second machine code (the one with 5 instructions) is what one would expect from the first version of the program. Changing the shape / complexity of unrelated code should not introduce panicking branches. The function decides which part of the generator body to execute based on the value of a discriminant. As of nightly-2019-11-04 the code generated for the function contains 2 "built-in" panicking branches, which are present in all generators: The first built-in panicking branch, which corresponds to discriminant , is used to panic a generator that has been -d beyond completion -- a generator that has reached a statement has reached completion. The second built-in branch, which corresponds to discriminant , is used to panic a generator that has been -d after panicking -- I'm not sure when this can occur in practice but looking at the compiler codegen unwinding is required to reach this "poisoned" state. However, neither of these situations can occur in the example. The discriminant of the generator can never become because it does not contain a statement in its body. Its discriminant can not become either because the target has been configured for meaning that unwinding is not supported at the codegen level ( functions are never generated). This can be confirmed by scanning the optimized LLVM IR produced by the compiler: the discriminant is stored in static memory () and the only values written to this memory are and . Naively, I would improve the MIR codegen by: Not producing the branch if the type of the generator is (or, I guess, any other empty type like ). This type indicates that the generator does not contain a statement. Not producing the branch if the target has set its option to . Alternatively, one could check if the unwind-related function is generated and omit the branch if it's not generated. Relevant code: Does these seem like sensible fixes? Steps to produce the above machine code. This is not a minified repro because a certain complexity is required to get the inferior codegen -- trivial programs get properly optimized. <details</details
Do you want to fix this? It should be pretty easy. Don't emit the return case if there's no terminator in MIR. Don't emit the unwind case if there's no terminator in MIR.
I guess the thing is not true since unwinds are implicit and other terminators can unwind too. I guess skipping it if will suffice. This does mean that the generator transformation doesn't poison all the unwind paths currently, just . so that has to be fixed too.
The generator transformation could be fixed by inserting a basic block with just in it at the start of and change cleanup/unwind edges of terminators with to point to the new block.

Discussion:
assert_block } <ins> fn can_return<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { // Returning from a function with an uninhabited return type is undefined behavior. if body.return_ty().conservative_is_privately_uninhabited(tcx) { return false; } // If there's a return terminator the function may return. for block in body.basic_blocks() { if let TerminatorKind::Return = block.terminator().kind { return true; } } // Otherwise the function can't return. false } fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { // Nothing can unwind when landing pads are off. if tcx.sess.no_landing_pads() { return false; } // Unwinds can only start at certain terminators. for block in body.basic_blocks() { match block.terminator().kind { // These never unwind. TerminatorKind::Goto { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Abort | TerminatorKind::Return | TerminatorKind::Unreachable | TerminatorKind::GeneratorDrop | TerminatorKind::FalseEdges { .. } | TerminatorKind::FalseUnwind { .. } => {} // Resume will *continue* unwinding, but if there's no other unwinding terminator it // will never be reached. TerminatorKind::Resume => {} TerminatorKind::Yield { .. } => { unreachable!("`can_unwind` called before generator transform") } // These may unwind. TerminatorKind::Drop { .. } | TerminatorKind::DropAndReplace { .. } | TerminatorKind::Call { .. } | TerminatorKind::Assert { .. } => return true, } } // If we didn't find an unwinding terminator, the function cannot unwind. false } </ins> fn create_generator_resume_function<'tcx>( tcx: TyCtxt<'tcx>, transform: TransformVisitor<'tcx>, def_id: DefId, source: MirSource<'tcx>, body: &mut BodyAndCache<'tcx>, <ins> can_return: bool, </ins> ) { <ins> let can_unwind = can_unwind(tcx, body); </ins> // Poison the generator when it unwinds <del> for block in body.basic_blocks_mut() { let source_info = block.terminator().source_info; if let &TerminatorKind::Resume = &block.terminator().kind { block.statements.push(transform.set_discr(VariantIdx::new(POISONED), source_info)); </del> <ins> if can_unwind { let poison_block = BasicBlock::new(body.basic_blocks().len()); let source_info = source_info(body); body.basic_blocks_mut().push(BasicBlockData { statements: vec![transform.set_discr(VariantIdx::new(POISONED), source_info)], terminator: Some(Terminator { source_info, kind: TerminatorKind::Resume }), is_cleanup: true, }); for (idx, block) in body.basic_blocks_mut().iter_enumerated_mut() { let source_info = block.terminator().source_info; if let TerminatorKind::Resume = block.terminator().kind { // An existing `Resume` terminator is redirected to jump to our dedicated // "poisoning block" above. if idx != poison_block { *block.terminator_mut() = Terminator { source_info, kind: TerminatorKind::Goto { target: poison_block }, }; } } else if !block.is_cleanup { // Any terminators that *can* unwind but don't have an unwind target set are also // pointed at our poisoning block (unless they're part of the cleanup path). if let Some(unwind @ None) = block.terminator_mut().unwind_mut() { *unwind = Some(poison_block); } } </ins> } }

// Panic when resumed on the returned or poisoned state let generator_kind = body.generator_kind.unwrap(); <del> cases.insert(1, (RETURNED, insert_panic_block(tcx, body, ResumedAfterReturn(generator_kind)))); cases.insert(2, (POISONED, insert_panic_block(tcx, body, ResumedAfterPanic(generator_kind)))); </del> <ins> if can_unwind { cases.insert( 1, (POISONED, insert_panic_block(tcx, body, ResumedAfterPanic(generator_kind))), ); } if can_return { cases.insert( 1, (RETURNED, insert_panic_block(tcx, body, ResumedAfterReturn(generator_kind))), ); } </ins> insert_switch(body, cases, &transform, TerminatorKind::Unreachable);

let (remap, layout, storage_liveness) = compute_layout(tcx, source, &upvars, interior, movable, body); <ins> let can_return = can_return(tcx, body); </ins> // Run the transformation which converts Places from Local to generator struct // accesses for locals in `remap`. // It also rewrites `return x` and `yield y` as writing a new generator state and returning

body.generator_drop = Some(box drop_shim); // Create the Generator::resume function <del> create_generator_resume_function(tcx, transform, def_id, source, body); </del> <ins> create_generator_resume_function(tcx, transform, def_id, source, body, can_return); </ins> } }

<ins> //! Tests that generators that cannot return or unwind don't have unnecessary //! panic branches. // compile-flags: -Zno-landing-pads #![feature(generators, generator_trait)] struct HasDrop; impl Drop for HasDrop { fn drop(&mut self) {} } fn callee() {} fn main() { let _gen = |_x: u8| { let _d = HasDrop; loop { yield; callee(); } }; } // END RUST SOURCE // START rustc.main-{{closure}}.generator_resume.0.mir // bb0: { //     ... //     switchInt(move _11) -> [0u32: bb1, 3u32: bb5, otherwise: bb6]; // } // ... // END rustc.main-{{closure}}.generator_resume.0.mir </ins>