Generally _Atomic
does not imply semantics of volatile
, i.e. operations on the atomic object are not observable side effects that the compiler needs to preserve.
As a consequence the compiler can optimize e.g.
void f(_Atomic int* x) {
atomic_fetch_add_explicit(x, 0, memory_order_relaxed);
}
to a no-op. And indeed Clang does perform this optimization, while GCC and MSVC do not.
I think it is expected that _Atomic
and volatile
semantics can be combined by combining the qualifiers, i.e. my expectation is that
void f(volatile _Atomic int* x) {
atomic_fetch_add_explicit(x, 0, memory_order_relaxed);
}
forces the compiler to emit an instruction in f
that implements a read-modify-write operation on x
.
But interestingly Clang only behaves partially in the way I would expect it to. Clang emits:
f:
mfence
mov eax, dword ptr [rdi]
ret
There is a load instruction, but no store to the memory. In particular this is not an atomic RMW.
While this may simply be a bug, I wonder what guarantees one should have on a volatile atomic RMW operation. Volatile access shall be evaluated strictly according to the rules of the abstract machine, however “access” is defined in §3.1 of ISO C draft N3220 as:
<execution-time action> to read or modify the value of an object
which seemingly does not take into account an atomic RMW operation as an individual access.
Is the implementation supposed to guarantee that an atomic volatile RMW operation will result in both the equivalent of a volatile read and a volatile modification on the atomic object? And are they supposed to be guaranteed to be combined into one RMW access (in an implementation-defined manner) rather than an individual read and write? Or is all of this implementation-defined?
I posted an analogues question for C++ here, although there are other specification issue in the C++ case which that question focuses on.