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

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>