The following example of pointer aliasing:
pub unsafe fn f(a: *mut i32, b: *mut i32, x: *const i32) {
*a = *x;
*b = *x;
}
compiles into the following assembly (with -C opt-level=s):
example::f:
push rbp
mov rbp, rsp
mov eax, dword ptr [rdx]
mov dword ptr [rdi], eax
mov eax, dword ptr [rdx]
mov dword ptr [rsi], eax
pop rbp
ret
Notice that x is being dereferenced twice. LLVM is not treating it as noalias. My first thought was to avoid using pointers in the assignments and instead use safe references (since those "follow LLVM’s scoped noalias model") to give a hint to the optimizer:
pub fn g(a: *mut i32, b: *mut i32, x: *const i32) {
let safe_a = unsafe { &mut *a };
let safe_b = unsafe { &mut *b };
let safe_x = unsafe { &*x };
*safe_a = *safe_x;
*safe_b = *safe_x;
}
But alas, this produces the exact same result. safe_x is still dereferenced twice.
I know this example code is dumb. The parameters could easily be changed to &i32/&mut i32, or I could just dereference x once and store it in a temporary that is used for the assignment. The code here is just meant to be a super simple aliasing test, and I'm interested in the broader picture my question is asking.
There is, wrap the safe reference in a function or closure:
pub unsafe fn f(a: *mut i32, b: *mut i32, x: *const i32) {
(|safe_a: &mut i32, safe_b: &mut i32, safe_x: &i32| {
*safe_a = *safe_x;
*safe_b = *safe_x;
})(&mut *a, &mut *b, &*x)
}
This produces the wanted non-aliasing behavior:
example::f:
movl (%rdx), %eax
movl %eax, (%rdi)
movl %eax, (%rsi)
retq
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