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?
Rust follow noalias
model for &mut T
from LLVM see Behavior considered undefined:
This indicates that memory locations accessed via pointer values based on the argument or return value are not also accessed, during the execution of the function, via pointer values not based on the argument or return value. This guarantee only holds for memory locations that are modified, by any means, during the execution of the function. The attribute on a return value also has additional semantics described below. The caller shares the responsibility with the callee for ensuring that these requirements are met.
So If I get it correctly that mean simply having i: &mut u32
in argument list expect it should have no alias. Even use an auxiliary function like:
pub unsafe fn assign_mixed(i: &mut u32, j: *mut u32) -> u32 {
aux(i, j)
}
pub unsafe fn aux(i: *mut u32, j: *mut u32) -> u32 {
*i = 42;
*j = 7;
*i
}
Would not work.
I think the only way to have something similar would be to use UnsafeCell
like:
use std::cell::UnsafeCell;
pub unsafe fn assign_mixed(i: &UnsafeCell<u32>, j: *mut u32) -> u32 {
*i.get() = 42;
*j = 7;
*i.get()
}
Would produce the desired assembler code. Be sure to not use &mut UnsafeCell
for this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With