Change:
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.

Discussion:
dividing by zero const_eval_division_overflow = overflow in signed division (dividing MIN by -1) <del> const_eval_double_storage_live = StorageLive on a local that was already live </del> const_eval_dyn_call_not_a_method = `dyn` call trying to call something that is not a method

Operand::Immediate(Immediate::Uninit) }); <del> // StorageLive expects the local to be dead, and marks it live. </del> <ins> // If the local is already live, deallocate its old memory. </ins> let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val); <del> if !matches!(old, LocalValue::Dead) { throw_ub_custom!(fluent::const_eval_double_storage_live); } </del> <ins> self.deallocate_local(old)?; </ins> Ok(()) }

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(())

/// At any point during the execution of a function, each local is either allocated or /// unallocated. Except as noted below, all locals except function parameters are initially /// unallocated. `StorageLive` statements cause memory to be allocated for the local while <del> /// `StorageDead` statements cause the memory to be freed. Using a local in any way (not only /// reading/writing from it) while it is unallocated is UB. </del> <ins> /// `StorageDead` statements cause the memory to be freed. In other words, /// `StorageLive`/`StorageDead` act like the heap operations `allocate`/`deallocate`, but for /// stack-allocated local variables. Using a local in any way (not only reading/writing from it) /// while it is unallocated is UB. </ins> /// /// Some locals have no `StorageLive` or `StorageDead` statements within the entire MIR body. /// These locals are implicitly allocated for the full duration of the function. There is a /// convenience method at `rustc_mir_dataflow::storage::always_storage_live_locals` for /// computing these locals. /// <del> /// If the local is already allocated, calling `StorageLive` again is UB. However, for an /// unallocated local an additional `StorageDead` all is simply a nop. </del> <ins> /// If the local is already allocated, calling `StorageLive` again will implicitly free the /// local and then allocate fresh uninitilized memory. If a local is already deallocated, /// calling `StorageDead` again is a NOP. </ins> StorageLive(Local), /// See `StorageLive` above.

<ins> #![feature(core_intrinsics, custom_mir)] use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime")] fn main() { mir! { let val: i32; { val = 42; //~ERROR: accessing a dead local variable StorageLive(val); // too late... (but needs to be here to make `val` not implicitly live) Return() } } } </ins>

<ins> error: Undefined Behavior: accessing a dead local variable --> $DIR/storage-live-dead-var.rs:LL:CC | LL |             val = 42; |             ^^^^^^^^ accessing a dead local variable | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: = note: inside `main` at $DIR/storage-live-dead-var.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace error: aborting due to 1 previous error </ins>

<ins> #![feature(core_intrinsics, custom_mir)] use std::intrinsics::mir::*; #[custom_mir(dialect = "runtime")] fn main() { mir! { let val: i32; let _val2: i32; { StorageLive(val); val = 42; StorageLive(val); // reset val to `uninit` _val2 = val; //~ERROR: uninitialized Return() } } } </ins>

<ins> error: Undefined Behavior: constructing invalid value: encountered uninitialized memory, but expected an integer --> $DIR/storage-live-resets-var.rs:LL:CC | LL |             _val2 = val; |             ^^^^^^^^^^^ constructing invalid value: encountered uninitialized memory, but expected an integer | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: = note: inside `main` at $DIR/storage-live-resets-var.rs:LL:CC note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace error: aborting due to 1 previous error </ins>