https://gcc.gnu.org/bugzilla/show_bug.cgi?id=124822
Bug ID: 124822
Summary: -Winvalid-memory-model warning only appears with
-fsanitize=thread
Product: gcc
Version: 15.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: middle-end
Assignee: unassigned at gcc dot gnu.org
Reporter: aaron.puchert at sap dot com
Target Milestone: ---
The following code:
#include <atomic>
std::atomic<int> x;
void test() {
int y = 0;
x.compare_exchange_weak(y, 0, std::memory_order_relaxed,
std::memory_order_release);
}
produces no warnings with GCC 15.2 (and older) "-std=c++20 -O2 -Wall", but with
"-std=c++20 -O2 -Wall -fsanitize=thread" it produces:
In member function 'bool
std::__atomic_base<_IntTp>::compare_exchange_weak(__int_type&, __int_type,
std::memory_order, std::memory_order) [with _ITp = int]',
inlined from 'void test()' at <source>:7:28:
[...]/include/c++/15.2.0/bits/atomic_base.h:536:43: warning: invalid failure
memory model 'memory_order_release' for 'bool
__atomic_compare_exchange_4(volatile void*, void*, unsigned int, bool, int,
int)' [-Winvalid-memory-model]
536 | return __atomic_compare_exchange_n(&_M_i, &__i1, __i2, 1,
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~
537 | int(__m1), int(__m2));
| ~~~~~~~~~~~~~~~~~~~~~
[...]/include/c++/15.2.0/bits/atomic_base.h:536:43: note: valid failure models
are 'memory_order_relaxed', 'memory_order_seq_cst', 'memory_order_acquire',
'memory_order_consume'
I'm aware that this is a middle end warning, but apart from the
instrumentation, there is no difference between the two command lines. Inlining
is also the same, and the operation is not optimized out:
test():
xorl %eax, %eax
lock cmpxchgl %eax, x(%rip)
ret
So why does the uninstrumented build not show the warning? The only difference
I can find is the __tsan_write4 call, suggesting that y is not promoted to a
register. One might think that the discarded output is the reason, since the
warning appears here without -fsanitize=thread:
void test(int& y) {
x.compare_exchange_weak(y, 0, std::memory_order_relaxed,
std::memory_order_release);
}
But we can observe the same issue in this more complex example:
#include <atomic>
struct Element
{
Element* m_next;
};
class LIFO
{
public:
Element* pop() noexcept {
Element* h = m_head.load(std::memory_order_acquire);
while (h) {
Element* next = h->m_next;
if (m_head.compare_exchange_weak(h, next,
std::memory_order_relaxed, std::memory_order_release))
return h;
}
return nullptr;
}
private:
std::atomic<Element*> m_head{nullptr};
};
void test(LIFO& lifo) {
lifo.pop();
}
This uses the return value and the changed "h" in the failure case. Still, the
warning only appears with -fsanitize=thread.
There is also no warning in GCC trunk, regardless of whether we add
-fsanitize=thread. So at least it's consistent there, although still a missing
warning.