Change:
assert!(local != mir::RETURN_PLACE, "Cannot make return place dead"); trace!("{:?} is now dead", local); <del> // It is entirely okay for this local to be already dead (at least that's how we currently generate MIR) </del> <ins> // If the local is already dead, this is a NOP. </ins> let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead); self.deallocate_local(old)?; Ok(())

Discussion:
The inliner strategy for dealing with storage statements is simple. If a callee local already has some storage statements, they are preserved as is when integrating callee into the caller. There are no new storage statements for such locals. Turns out this approach is unsound due to peculiar semantics of MIR. It is well defined to return from a function while there are still some live locals. At the same time it is undefined behaviour to execute StorageLive for already live local. Effectively inliner is obliged to end the storage for locals that are still live when callee returns, which it doesn't do at the moment. Arguably this is more of a bug in MIR semantics, then one in the inliner
hey would you like to first T-opsem discuss this? Maybe have this topic on a meeting and figure out a plan to fix this?
Oh, interesting catch. Cc Maybe we should just allow StorageLive on an already live local (and declare it to first implicitly free the local, and then make it live again)...

Currently, we disallow being called on a local that is already live (both in Miri, and in the MIR docs). This was originally motivated by some very vague wording in LLVM making it unclear whether LLVM would behave properly when there are multiple without intermediate . However, LLVM has in the mean time clarified that such a redundant is fine, and just resets the contents of the storage to be uninitialized. So that we change our StorageLive semantics for the case where the local is already live: this could be be defined behavior, and it will re-allocate the backing local, potentially giving it a new address and resetting its contents to be uninitialized. I do not have any fundamental objections to this, but I also don't know if this could cause other problems, or maybe even help in other situations. Having such a "reallocation" primitive has previously been by ; the proposed semantics would mean we don't need another primitive. But maybe some MIR opts would prefer the stricter interpretation of the storage annotations? sounds like would prefer a stricter interpretation than what we currently implement for StorageDead, so making StorageLive more relaxed might be a problem for them. So, let's collect all the potential benefits of the current semantics of StorageLive before deciding that we change it. (And also all benefits of the change, of course.) Cc
To be clear, for now I don't have a real reason for wanting this besides that it seems like unnecessary UB. I don't know of an optimization that is affected by this in either direction - will think about it though.
Doing this would imply that the compiler consumers (for example, verifiers) would have to compute themselves whether is allocation or reallocation. Compiler consumers can do that, but then they would not be able to check whether the compiler is doing the right thing (for example, I found just by trying to verify a simple example). As a selfish guiding design principle, I personally would prefer for the compiler to be as explicit as possible about what it knows and what it does not know because this helps to ensure that compiler consumers interpret data in the same way as the compiler, which is important for the consumers used for quality assurance in critical fields.
Same here. I just find the current situation unsatisfying -- either we should require proper structure for both StorageLive and StorageDead, or for none of them.

Right now we require it for StorageLive but not for StorageDead (that's ), and that is just inconsistent.
says "This is a MIR semantics question. We're not sure yet if we want to pin the semantics of Rust on MIR, but t-opsem is a good owner for this issue." I'm going to label MIR semantics issues as T-opsem going forward, feel free to speak up if you think isn't a good fit. label T-opsem
is an argument for allowing StorageLive on an already live local.
I propose that we allow StorageLive when the local is already live, with the semantics that the local is implicitly freed and then re-allocated (with fresh, uninitialized contents). Cc Basically, StorageLive will implicitly first execute a StorageDead if needed. This is similar to how we treat StorageDead, which is a NOP when the local is already dead, which is equivalent to implicitly first executing a StorageLive if needed. We have to treat StorageDead that way due to mir-opts are also sometimes with maintaining the subtle invariant that StorageLive on an already live local is UB. And in particular given the fact that we do allow to serve as implicit StorageDead for all locals that are still live, the current rules are causing issues for inlining; that's what is about. Some out-of-tree MIR consumers use StorageLive/StorageDead as exact markers for the lifetime of local variables, but at this point that seems better served by a dedicated analysis than by trying to make all mir-opts ensure this non-trivial invariant. close
Team member has proposed to close this. The next step is review by the rest of the tagged team members: [x] [x] [x] [x] [ ] No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See for info about what commands tagged team members can give me.
What is the impact of this change on inlining? Is it now simply a matter of doing nothing to the StorageLive/StorageDead calls or does the unsoundness issue persist?
I think doing nothing is sound under this proposal. thought about that more than I did, though.

I don't fully grasp the extent of the implications on MIR, but this makes sense to me given the context Ralf provided. reviewed
:bell: This is now entering its final comment period, as per the . :bell:
I have a question, in this case, will a StorageLive imply StorageDead of the initial variable? What happens to a raw pointer that holds the address of the previous local?
My understanding is that yes, a StorageLive implies a StorageDead of the initial variable, and this also means that a raw pointer holding the previous address is no longer pointing to a valid allocation. (I think we want the flexibility to take advantage of the re-initialization to be able to change the storage location.)
Yes, that's what I wrote:
The final comment period, with a disposition to close, as per the , is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

As far as I understand, a has to be dominated by . For this code (which is a simplified version of on ): This does not seem to be the case. I get the following MIR ( produced with ): The execution path executes without executing , which, as far as I understand, is a bug. : <!-- TRIAGEBOTSTART --<!-- TRIAGEBOTASSIGNSTART --<!-- TRIAGEBOTASSIGNDATASTARTTRIAGEBOTASSIGNDATAEND --<!-- TRIAGEBOTASSIGNEND --<!-- TRIAGEBOT_END --
claim
label +T-compiler +A-mir
Ah, I think I just reported the same problem:
In we are considering allowing redundant StorageLive; if we went that way, we would most probably want to keep allowing redundant StorageDead as well.
I'd also be interested to know if you have a reason for wanting this. I can't think of any real benefits myself, but I totally might be missing something
I am working on a verifier for Rust called . Simply speaking, if I can treat as a for stack and as a for stack, it makes my life easy because our infrastructure directly supports working with such concepts. If the use of and does not follow this model, for example, having without preceding is fine, then I have to implement a static analysis in Prusti that maps the more complicated model into the one supported by our infrastructure. In this case, the analysis should be simple to implement, so I am not too worried about that part. The part that worries me is how can I ensure that Prusti interprets MIR in the same way as the Rust compiler, especially over a longer period of time when small changes occur.
So slightly generalizing this, I'm interpreting this as "I want to lower MIR to an IR which does not support these semantics" - this seems like a good reason to me. LLVM is not the only IR that MIR needs to be lowered to, SMIR, gcc, etc. are also there, and this might make sense for that reason. That being said, this is still not enough for the desired conclusion from the title that there is a dominance requirement. Indeed, even if we don't allow redundant storage markers, they are still dynamic statements, and so are allowed to be all kinds of wrong in dead code.

You can submit a PR to add your github tag to be notified whenever a PR changes MIR semantics.
Yes, exactly. For Prusti, dead code is not a problem (as long as it is provably dead) because unreachable code is not analyzed. Whether it would be a problem for other compiler consumers, I do not know and would be open to discuss this. Thanks! .
release-assignment