Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Over-aggressive noalias on pointer created from mut ref?

Tags:

rust

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?

like image 803
GManNickG Avatar asked Sep 24 '21 03:09

GManNickG


1 Answers

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.

like image 183
Stargateur Avatar answered Nov 16 '22 01:11

Stargateur