Can non-atomic-load be reordered after atomic-acquire-load?
I believe this is the correct way to reason about your example within the C++ standard:
X.load(std::memory_order_acquire)
(let's call it "operation(A)
") may synchronize with a certain release operation onX
(operation(R)
) - roughly, the operation that assigned the value toX
that(A)
is reading.
[atomics.order]/2 An atomic operation
A
that performs a release operation on an atomic objectM
synchronizes with an atomic operationB
that performs an acquire operation onM
and takes its value from any side effect in the release sequence headed byA
.
This synchronizes-with relationship may help establish a happens-before relationship between some modification of
L
and the assignmentlocal2 = L
. If that modification ofL
happens-before(R)
, then, due to the fact that(R)
synchronizes-with(A)
and(A)
is sequenced-before the read ofL
, that modification ofL
happens-before this read ofL
.But
(A)
has no effect whatsoever on the assignmentlocal1 = L
. It neither causes data races involving this assignment, nor helps prevent them. If the program is race-free, then it must necessarily employ some other mechanism to ensure that modifications ofL
are synchronized with this read (and if it's not race-free, then it exhibits undefined behavior and the standard has nothing further to say about it).
It is meaningless to talk about "instruction reordering" within the four corners of the C++ standard. One may talk about machine instructions generated by a particular compiler, or the way those instructions are executed by a particular CPU. But from the standard's standpoint, these are merely irrelevant implementation details, as long as that compiler and that CPU produce observable behavior consistent with one possible execution path of an abstract machine described by the standard (the As-If rule).
The reference you cited is pretty clear: you can't move reads before this load. In your example:
static std::atomic<int> X;
static int L;
void thread_func()
{
int local1 = L; // (1)
int x_local = X.load(std::memory_order_acquire); // (2)
int local2 = L; // (3)
}
memory_order_acquire
means that (3) cannot happen before (2) (the load in (2) is sequenced before thr load in (3)). It says nothing about the relationship between (1) and (2).