Consider the following (on Rust >= 1.54):
pub fn assign_refs(i: &mut u32, j: &mut u32) -> u32 {
*i = 42;
*j = 7;
*i
}
Per no aliasing among mutable refs, this compiles to:
mov dword ptr [rdi], 42
mov dword ptr [rsi], 7
mov eax, 42
ret
Now consider:
pub fn assign_ptrs(i: *mut u32, j: *mut u32) -> u32 {
*unsafe {&mut *i} = 42;
*unsafe {&mut *j} = 7;
unsafe {*i}
}
(Note only one mutable ref exists at once, so this is not undefined behavior if i == j
).
Since pointers may alias, the last expression must reload:
mov dword ptr [rdi], 42
mov dword ptr [rsi], 7
mov eax, dword ptr [rdi]
ret
This next example is undefined behavior if j
points at i
:
pub fn assign_undefined_behavior_if_same(i: &mut u32, j: *mut u32) -> u32 {
*i = 42;
*unsafe {&mut *j} = 7; // UB if j points to i, second mut ref.
*i
}
For that reason, it compiles to the same code as assign_refs
, returning the "wrong" value.
My question is regarding:
pub fn assign_mixed(i: &mut u32, j: *mut u32) -> u32 {
*i = 42;
let i_ptr = i as *mut u32;
std::convert::identity(i); // *Not* a reborrow, a move and drop.
// i no longer exists.
// *i = 42; // use of moved value: `i`
// At this point, why not the same as assign_ptrs?
*unsafe {&mut *j} = 7;
// Assumes that i_ptr is not aliased just because it came from a &mut?
unsafe {*i_ptr}
}
This compiles to the same thing as assign_refs
, and I find that surprising.
The unique aliasing reference i
ends halfway through the function. At that point, why are i_ptr
and j
not treated identically as if we were in assign_ptrs
? Pointers are allowed to alias, so j
could point at i
/i_ptr
and i
no longer exists.
For reference, one can call this like:
fn test() {
let mut i = 0;
let mut i_ref = &mut i;
let i_ptr = i_ref as *mut u32;
assign_mixed(i_ref, i_ptr);
}
Is this an over-aggressive noalias propagation?