Consider the following code snippet:
// Main thread
int non_atom = 0;
bool x = _ // some value not known statically, depends on run-time arguments and complex calculations.
// Thread 1 and 2 are started
// Thread 1
if (!x) {
non_atom = 5; // only write to non_atom if not x
}
// Thread 2
int bar = 0;
if (x) {
bar = non_atom; // only access non_atom if x
}
At runtime only either T1 or T2 will access non-atom, so there should be no data race. But let's say the compiler decides to invert the if-condition in T2 like this:
int bar = non-atom;
if (!x) {
bar = 0;
}
This isn't simply a reordering but rather an inversion of the conditional and its effects. There is now a data-race, because if x==true
both threads access non-atom
without synchronization.
Is the compiler allowed to do this? Let's assume that the compiler cannot statically prove that only one thread accesses x, for example because the code in T1 and T2 is in functions in separate translation units. Is the compiler allowed to introduce a data-race where dynamically, there wasn't one before, or rather, are compiler careful enough not to do such a transformation even if no atomics are involved anywhere? If I encounter such a situation in the real world, should I use an atomic instead just to be safe?
This question is similar to this older question: Data race guarded by if (false)... what does the standard say?
However, in this old question the conditional was statically always false, so it was clear that it should never be evaluated. In this question, the runtime value is not known to the compiler at compile-time, and the optimization is not a simple reordering.
This question was inspired by a post by Russ Cox (ctrl-f "Note that all these optimizations") on the Go Memory Model, in which he claims that such transformations ARE allowed by C++ compilers, in contrast to Go's.