https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111077

            Bug ID: 111077
           Summary: atomic_ref compare_exchange_strong doesn't properly
                    ignore padding bits
           Product: gcc
           Version: unknown
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: comexk at gmail dot com
  Target Milestone: ---

C++20 requires that the functions `std::atomic<T>::compare_exchange_strong` and
`std::atomic_ref<T>::compare_exchange_strong` ignore any padding bits that
exist in T.  libstdc++ implements this by relying on the invariant that
`std::atomic<T>` values in memory always have zeroed padding bits.  All methods
of `std::atomic<T>`  that store values to memory (including the constructor)
zero the padding bits first.  Then, `std::atomic<T>::compare_exchange_strong`
zeroes the padding bits of the `expected` value, allowing it to assume that the
native atomic compare-exchange won't fail due to padding bits.

However, this doesn't work correctly for `std::atomic_ref<T>`.  All methods of
`std::atomic_ref<T>` that store to memory do zero the padding bits.  But what
about the initial value of the object, before the first atomic_ref pointing to
it was created?  That value could be anything.

As a result, this code (compiled with -std=c++20) incorrectly prints 0, even
though the only difference between `value` and `expected` is in a padding byte:

    #include <atomic>
    #include <stdio.h>
    int main() {
        struct HasPadding { char a; short b; };
        HasPadding value = {};
        ((char *)&value)[1] = 0x42; // update padding byte
        HasPadding expected = {};
        printf("%d\n",
            std::atomic_ref(value).compare_exchange_strong(
                expected, expected));
    }

This could be fixed by reimplementing
`std::atomic_ref<T>::compare_exchange_strong` to use a loop, as in this
pseudocode:

    bool compare_exchange_strong(std::atomic_ref<T> ref, T& expected, T
desired) {
        T orig = expected;
        while (1) {
            if (ref.compare_exchange_weak_including_padding(expected, desired))
{
                return true;
            }
            if (!equal_ignoring_padding(orig, expected)) {
                return false;
            }
        }
    }

[See also:
https://github.com/rust-lang/unsafe-code-guidelines/issues/449#issuecomment-1677985851,
which has a comparison of different compilers.]

Reply via email to