Change:
This code compiles to On every run through the loop, is executed, and there is no matching . The intended semantics of multiple on the same location are unclear. However, evidence suggests that LLVM considers a variable dead before any , in particular, in , LLVM feels free to treat that load as yielding . The reference manual says so (however, it doesn't say anything about being fine, so it seems incomplete), and managed to actually obtain UB from a redundant in If you comment out the , this prints some a's. If you leave it in, nothing is printed. This suggests we should rather not permit "redundant" , and hence the program at the beginning of this report should be translated such that is marked live before the loop, rather than inside the loop.
I suppose a somewhat weaker interpretation would be for to kill the current value in the memory, even if it is already live. (This has been suggested by Then the code above would be correct. I guess part of the question is how conservative we want to be here.
FYI, I implemented the above suggestion in Miri, and the test suite passes. Now that's not a whole lot of code on the grand scheme of things, but this does invoke some interesting unsafe code in Vec and the lang item, so it's better than nothing.
Accidental triage (because I forgot about this and just rediscovered the issue^^): the situation is unchanged.
I don't think this is fixed. I am still seeing already-live locals marked as live if I make Miri check for that. You can see that in : the MIR with your patch is <details<summaryExpand MIR</summary</detailsYou can see that there is a each time around the loop, and no corresponding .
In fact, MIR generation in that example does not seem to change at all with your PR.
Note-mostly-to-self: the branch to make Miri check if this issue is resolved is at
Alive2 is a tool to check LLVM optimizations correct, and it has to give an operational semantics to LLVM IR. What is interesting: If alloca has any uses with lifetime intrinsics, it is truly allocated at Before that, it is assigned a block id, but it does not occupy the space and accessing it is UB. If alloca is not used by any lifetime intrinsics, it is immediately allocated at the definition. This is close to what Miri does, except Miri re-assigns a new block ID and address each time the lifetime of a local ends and starts again. (Compiling from Miri to Alive2 semantics is correct but not the other way around, so we are good.) Also: and
Hi, maybe a silly question - is there a reason that Rust emits at bb0 instead of bb1? I'm wondering whether it makes sense for LLVM to typecheck whether a program has lifetime intrinsics arranged within well-known patterns only. If the usage pattern was too complex, LLVM would already have given up using the pairs for optimization.
You mean the other way around? It seems to be in bb1. I'm afraid I do not know why we emit the way we do. Cc
The marks a stack slot as in-scope, and overwrites it with , so a direct translation of the example MIR to LLVM would be correct. BTW, StackColoring, which is primary user of lifetime intrinsics, describes the effects of those intrinsics in more detail than the reference. The MIR now has a before next iteration of the loop, regardless.
Would be good to have the LangRef updated then... where can I read more about StackColoring? Oh, nice! I'll try to see if I can now adjust to be UB when called on an already-live stack slot.
I love the introduction^^ Seems like everyone is very sure what these things do... doesn't make me worried about LLVM as the foundation of Rust at all...^^
Currently this fails to evaluate many constants; I suspect that is related to I'll try again once that landed. For future reference, .
(I'm not sure whether here is a good place to ask this, but) can Rust generate with non-zero offset? I found this old bug report from LLVM bugzilla: . Its fix patch shows a simple example. Rust was fiercely changing at that point; I wonder whether this is still happening.
Syntactically, the Rust/MIR equivalent of these intrinsics can only be applied to locals, not to a part of a place/local. However, fixing might require adding offsets to dynamically-sized alloca to make sure the alignment is right. Not sure if there is a way to make the lifetime intrinsics still refer to the original alloca as opposed to the properly aligned ptr.
Okay, I understood the issue. I feel like this should be allowed, thanks.

Discussion:
Ok(()) } <del> /// Mark a storage as live, killing the previous content and returning it. /// Remember to deallocate that! pub fn storage_live( &mut self, local: mir::Local, ) -> InterpResult<'tcx, LocalValue<M::PointerTag>> { </del> <ins> /// Mark a storage as live, killing the previous content. pub fn storage_live(&mut self, local: mir::Local) -> InterpResult<'tcx> { </ins> assert!(local != mir::RETURN_PLACE, "Cannot make return place live"); trace!("{:?} is now live", local); let local_val = LocalValue::Uninitialized; <del> // StorageLive *always* kills the value that's currently stored. // However, we do not error if the variable already is live; // see <https://github.com/rust-lang/rust/issues/42371>. Ok(mem::replace(&mut self.frame_mut().locals[local].value, local_val)) </del> <ins> // StorageLive expects the local to be dead, and marks it live. let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val); if !matches!(old, LocalValue::Dead) { throw_ub_format!("StorageLive on a local that was already live"); } Ok(()) </ins> } <del> /// Returns the old value of the local. /// Remember to deallocate that! pub fn storage_dead(&mut self, local: mir::Local) -> LocalValue<M::PointerTag> { </del> <ins> pub fn storage_dead(&mut self, local: mir::Local) -> InterpResult<'tcx> { </ins> assert!(local != mir::RETURN_PLACE, "Cannot make return place dead"); trace!("{:?} is now dead", local); <del> mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead) </del> <ins> // It is entirely okay for this local to be already dead (at least that's how we currently generate MIR) let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead); self.deallocate_local(old)?; Ok(()) </ins> } <del> pub(super) fn deallocate_local( &mut self, local: LocalValue<M::PointerTag>, ) -> InterpResult<'tcx> { // FIXME: should we tell the user that there was a local which was never written to? </del> <ins> fn deallocate_local(&mut self, local: LocalValue<M::PointerTag>) -> InterpResult<'tcx> { </ins> if let LocalValue::Live(Operand::Indirect(MemPlace { ptr, .. })) = local { // All locals have a backing allocation, even if the allocation is empty // due to the local having ZST type.

// Mark locals as alive StorageLive(local) => { <del> let old_val = self.storage_live(*local)?; self.deallocate_local(old_val)?; </del> <ins> self.storage_live(*local)?; </ins> } // Mark locals as dead StorageDead(local) => { <del> let old_val = self.storage_dead(*local); self.deallocate_local(old_val)?; </del> <ins> self.storage_dead(*local)?; </ins> } // No dynamic semantics attached to `FakeRead`; MIR