[PATCH] libstdc++: Fix atomic CAS hang with C++11 [PR114865]
Tomasz Kaminski
tkaminsk@redhat.com
Fri Apr 11 14:14:23 GMT 2025
On Fri, Apr 11, 2025 at 2:25 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> On Fri, 11 Apr 2025 at 08:23, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
> >
> >
> >
> > On Thu, Apr 10, 2025 at 6:27 PM Jonathan Wakely <jwakely@redhat.com>
> wrote:
> >>
> >> 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.
> >
> > Wouldn't alternative comprehensive fix be equivalent to doing just:
> > diff --git a/libstdc++-v3/include/std/atomic
> b/libstdc++-v3/include/std/atomic
> > index 9b1aca0fc09..238cf739161 100644
> > --- a/libstdc++-v3/include/std/atomic
> > +++ b/libstdc++-v3/include/std/atomic
> > @@ -345,7 +345,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > compare_exchange_weak(_Tp& __e, _Tp __i, memory_order __s,
> > memory_order __f) noexcept
> > {
> > - return __atomic_impl::__compare_exchange(_M_i, __e, __i, true,
> > + return __atomic_impl::__compare_exchange<(__cplusplus <
> 201402L)>(_M_i, __e, __i, true,
> > __s, __f);
>
> If the std::atomic constructor happens in a translation unit compiled
> as C++11 but the call to compare_exchange_weak happens in a
> translation unit compiled as C++14, then padding won't be cleared, but
> this will call __compare_exchange<false> which expects padding to have
> been cleared.
>
I see. This seems to be the same drawback as in case of the current fix,
however
my suggestion guarantees that padding bits are ignored/cleared in program
compiled only in C++11 mode. As I understand the suggested change, make a
comparison exchange
prone to fail due padding bits.
>
>
> > }
> >
> > @@ -353,7 +353,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > compare_exchange_weak(_Tp& __e, _Tp __i, memory_order __s,
> > memory_order __f) volatile noexcept
> > {
> > - return __atomic_impl::__compare_exchange(_M_i, __e, __i, true,
> > + return __atomic_impl::__compare_exchange<(__cplusplus <
> 201402L)>(_M_i, __e, __i, true,
> > __s, __f);
> > }
> >
> > @@ -373,7 +373,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > compare_exchange_strong(_Tp& __e, _Tp __i, memory_order __s,
> > memory_order __f) noexcept
> > {
> > - return __atomic_impl::__compare_exchange(_M_i, __e, __i, false,
> > + return __atomic_impl::__compare_exchange<(__cplusplus <
> 201402L)>(_M_i, __e, __i, false,
> > __s, __f);
> > }
> >
> > @@ -381,7 +381,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > compare_exchange_strong(_Tp& __e, _Tp __i, memory_order __s,
> > memory_order __f) volatile noexcept
> > {
> > - return __atomic_impl::__compare_exchange(_M_i, __e, __i, false,
> > + return __atomic_impl::__compare_exchange<(__cplusplus <
> 201402L)>(_M_i, __e, __i, false,
> > __s, __f);
> > }
> >
> >>
> >> 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
> >>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20250411/7b8a1ea1/attachment-0001.htm>
More information about the Libstdc++
mailing list