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.

Reply via email to