Yep, it doesn’t:
use std::sync::atomic::{Ordering::*, AtomicUsize};
pub fn load_twice(x: &AtomicUsize) -> (usize, usize) {
let a = x.load(Relaxed);
let b = x.load(Relaxed);
(a, b)
}
->
playground::load_twice:
movq (%rdi), %rax
movq (%rdi), %rdx
retq
That sucks. It may be possible to partially work around this on rustc’s end by marking the load function with LLVM’s readonly attribute, which is equivalent to __attribute__((pure)) in C. The semantics are that the function can read through its arguments and global state, but must not modify global state and must be deterministic. This means the compiler can fold multiple calls with the same arguments into one if there were no intervening writes. And, based on this C equivalent, LLVM does do so, even if the function is marked always_inline (in which case I feared it would inline the calls before it had a chance to merge them).
In theory we’d like a stricter semantics, where the function can read through its arguments but not read global state. That way, LLVM could theoretically merge reads even if there were intervening writes to other locations, if it could prove the addresses didn’t alias. (This would only be valid for relaxed loads.) Unfortunately, the only stricter option is readnone, aka __attribute__((const)) in C, which requires that the function not read anything from memory, not even from its arguments.
Anyway, as for actually fixing the issue in LLVM, this bug report looks on point:
https://bugs.llvm.org/show_bug.cgi?id=37716
I went ahead and left a comment there.