The std::atomic constructor clears padding bits so that compare_exchange will not fail due to differences in padding bits. But we can only do that for C++14 and later, because in C++11 a constexpr constructor must have an empty body. However, the code in compare_exchange_strong assumes that padding is always cleared, and so it fails in C++11 due to non-zero padding.
Since we can't clear the padding in C++11 mode, we shouldn't assume it's been cleared when in C++11 mode. This fixes the reported bug. However, the fix fails to handle the case where the std::atomic is constructed in C++11 code (and so doesn't zero padding) but the CAS happens in C++14 code (and so assumes padding has been zeroed). We might need to use the same loop as atomic_ref::compare_exchange_strong to properly fix this bug for that case. Although the mixed C++11 / C++14 case isn't fixed, this is still an incremental improvement. It fixes the pure-C++11 case and doesn't preclude a more comprehensive fix later. libstdc++-v3/ChangeLog: PR libstdc++/114865 * include/bits/atomic_base.h (__maybe_has_padding): Return false for C++11. * include/std/atomic (atomic::atomic(T)): Add comment. * testsuite/29_atomics/atomic/114865.cc: New test. --- Tested x86_64-linux. libstdc++-v3/include/bits/atomic_base.h | 4 +- libstdc++-v3/include/std/atomic | 2 + .../testsuite/29_atomics/atomic/114865.cc | 49 +++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 libstdc++-v3/testsuite/29_atomics/atomic/114865.cc diff --git a/libstdc++-v3/include/bits/atomic_base.h b/libstdc++-v3/include/bits/atomic_base.h index 92d1269493f..19fc7a77c1b 100644 --- a/libstdc++-v3/include/bits/atomic_base.h +++ b/libstdc++-v3/include/bits/atomic_base.h @@ -954,7 +954,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr bool __maybe_has_padding() { -#if ! __has_builtin(__builtin_clear_padding) + // We cannot clear padding in the constructor for C++11, + // so return false here to disable all code for zeroing padding. +#if __cplusplus < 201402L || ! __has_builtin(__builtin_clear_padding) return false; #elif __has_builtin(__has_unique_object_representations) return !__has_unique_object_representations(_Tp) diff --git a/libstdc++-v3/include/std/atomic b/libstdc++-v3/include/std/atomic index 9b1aca0fc09..949a9017862 100644 --- a/libstdc++-v3/include/std/atomic +++ b/libstdc++-v3/include/std/atomic @@ -243,6 +243,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr atomic(_Tp __i) noexcept : _M_i(__i) { + // A constexpr constructor must be empty in C++11, + // so we can only clear padding for C++14 and later. #if __cplusplus >= 201402L && __has_builtin(__builtin_clear_padding) if _GLIBCXX17_CONSTEXPR (__atomic_impl::__maybe_has_padding<_Tp>()) __builtin_clear_padding(std::__addressof(_M_i)); diff --git a/libstdc++-v3/testsuite/29_atomics/atomic/114865.cc b/libstdc++-v3/testsuite/29_atomics/atomic/114865.cc new file mode 100644 index 00000000000..577cd480915 --- /dev/null +++ b/libstdc++-v3/testsuite/29_atomics/atomic/114865.cc @@ -0,0 +1,49 @@ +// { dg-do run { target c++11_only } } +// { dg-require-atomic-cmpxchg-word "" } +// { dg-add-options libatomic } + +// Bug 114865 +// std::atomic<X>::compare_exchange_strong seems to hang under GCC 13 for C++11 + +#include <atomic> +#include <cstdint> + +struct type +{ + std::uint32_t u32; + std::uint16_t u16; +}; + +[[gnu::noipa,gnu::noinline,gnu::optimize("O0")]] +type next(const type& old) +{ + auto t = old; + ++t.u16; + return t; +} + +[[gnu::noipa,gnu::noinline,gnu::optimize("O0")]] +void +test_pr116440() +{ + constexpr auto mo = std::memory_order_relaxed; + + type t; + t.u32 = t.u16 = 0; + std::atomic<type> a(t); + + auto old = a.load(mo); + + while (true) + { + auto t = next(old); + + if (a.compare_exchange_strong(old, t, mo, mo)) + return; + } +} + +int main() +{ + test_pr116440(); +} -- 2.49.0