26

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.

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
Cornstalks
  • 37,137
  • 18
  • 79
  • 144
  • 2
    Interestingly, if I use `&mut i32, &mut i32, &i32` as the prototype, the `noalias` attribute is tacked on `x` instead. I seem to remember that `noalias` is not actively passed to LLVM as there are concerns that the `unsafe` semantics need be clarified first, and optimizations applies second, lest LLVM "breaks" the code. – Matthieu M. Jan 09 '17 at 07:45
  • Maybe relevant: [`Unique`](https://doc.rust-lang.org/std/ptr/struct.Unique.html) – Lukas Kalbertodt Jan 09 '17 at 09:29
  • 2
    For the record, I reported the lack of optimization here as [issue #38941](https://github.com/rust-lang/rust/issues/38941). – Cornstalks Jan 09 '17 at 16:11
  • 2
    @MatthieuM.: Are you perhaps thinking of [issue #31681](https://github.com/rust-lang/rust/issues/31681)? Indeed, some references aren't marked as `noalias` due to a bug in LLVM (which has recently been fixed), but not all. Some are still able to be marked as `noalias`. – Cornstalks Jan 09 '17 at 16:13

1 Answers1

3

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
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
orlp
  • 112,504
  • 36
  • 218
  • 315
  • The function / closure is unnecessary. [All you are doing is converting the raw pointers to references](https://play.rust-lang.org/?gist=394715120674a36188bd6e8ac7ff0cd8&version=stable&mode=release&edition=2015). This doesn't really show how to treat *pointers* as non-aliasing, just how to convert pointers to references. – Shepmaster Sep 21 '18 at 13:12
  • @Shepmaster That's just not true - compare the assembly. In your example `safe_x` is loaded twice - evidence the references are **not** considered non-aliasing. The closure is necessary. – orlp Sep 21 '18 at 13:42
  • Hmm, I see what you mean. That seems like a bug (limitation?) By definition, references should not alias. Looking for an issue, I found [the one filed by OP](https://github.com/rust-lang/rust/issues/38941)! – Shepmaster Sep 21 '18 at 13:48
  • Yeah, sorry, I should have made that more explicit in my OP (this is what I meant by "The parameters could easily be changed to `&i32`/`&mut i32`" and what [Matthieu M.'s comment refers to](https://stackoverflow.com/questions/41541835/is-there-a-way-to-get-rust-to-treat-pointers-as-non-aliasing-so-it-can-mark-the#comment70290954_41541835)). Currently `noalias` is only applied to reference function arguments as far as I can tell. I suppose this is a perfectly valid answer to the question, though. – Cornstalks Sep 21 '18 at 14:49