From: Tomasz Kamiński Date: Thu, 29 Jan 2026 17:14:47 +0000 (+0100) Subject: libstdc++: Allow constant initialization of std::atomic of types with padding [PR123875] X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6b550d69fe7cb62ea6e240ce7a4ba29ce33aa1b1;p=thirdparty%2Fgcc.git libstdc++: Allow constant initialization of std::atomic of types with padding [PR123875] Currently for the types T that contains padding bits, std::atomic(T) constructor was not usable at compile-time in C++14 or later modes. This regression caused by use of __builtin_clear_padding introduced in r13-2548-g157236dbd62164. This leads to two regressions when switching from C++11 to C++14 standard (or switching from GCC-12 to later version for C++14 standard), where for type X that contains padding * constexpr std::atomic cx(X(...)) becomes ill-formed, * std::atomic gx(X(...)) with static storage duration, switch from static to dynamic initialization. The latter breakage is silent and may introduced very hard to localize order of initialization issues. This patch mitigates above issue by not invoking the __builtin_clear_padding, during constant initialization (std::__is_constant_evaluated() is false). This is considered to be safe, as: * for objects with static storage duration, padding bits are already cleared by zero-initialization * for constexpr objects with non-static storage duration, there is no API that would allow user to observe padding bits on const atomic objects To elaborate on the second point, values of padding bits in atomic can be observed by: * The compare_exchange_weak/compare_exchange_strong operations are mutating, so cannot be invoked on const objects. * As atomic is not required to store actual object of type X, observing its object representation does (via bitcast, memcpy), does not provide values of object representation of X. Furthermore, the operations are defined only for trivially_copyable types, and atomic specializations meets above requirement only due to bug in libstdc++ (see PR67572). Note that above will no longer hold, and the solution will need to be revisited during implementation of C++26 paper P3309R3: constexpr atomic and atomic_ref (it will be possible to call compare_exchange during constant evaluation). PR libstdc++/123875 libstdc++-v3/ChangeLog: * include/bits/atomic_base.h (__atomic_impl::__clear_padding): Use if constexpr unconditionally. (__atomic_float<_Fp>::__atomic_float(_Fp)): Skip __clear_padding call for constant evaluation. * include/std/atomic (atomic<_Tp>::atomic(_Tp)): Likewise. * testsuite/29_atomics/atomic/cons/static_zero_padding.cc: New test. Reviewed-by: Patrick Palka Reviewed-by: Jonathan Wakely Signed-off-by: Tomasz Kamiński --- diff --git a/libstdc++-v3/include/bits/atomic_base.h b/libstdc++-v3/include/bits/atomic_base.h index bc4778f08cb..79c1b31ccc4 100644 --- a/libstdc++-v3/include/bits/atomic_base.h +++ b/libstdc++-v3/include/bits/atomic_base.h @@ -1003,21 +1003,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION #endif } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wc++17-extensions" + template _GLIBCXX_ALWAYS_INLINE _GLIBCXX14_CONSTEXPR _Tp* __clear_padding(_Tp& __val) noexcept { auto* __ptr = std::__addressof(__val); #if __has_builtin(__builtin_clear_padding) - if _GLIBCXX17_CONSTEXPR (__atomic_impl::__maybe_has_padding<_Tp>()) + if constexpr (__atomic_impl::__maybe_has_padding<_Tp>()) __builtin_clear_padding(__ptr); #endif return __ptr; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wc++17-extensions" - template _GLIBCXX_ALWAYS_INLINE bool __compare_exchange(_Tp& __val, _Val<_Tp>& __e, _Val<_Tp>& __i, @@ -1392,7 +1392,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION constexpr __atomic_float(_Fp __t) : _M_fp(__t) - { __atomic_impl::__clear_padding(_M_fp); } + { + if (!std::__is_constant_evaluated()) + __atomic_impl::__clear_padding(_M_fp); + } __atomic_float(const __atomic_float&) = delete; __atomic_float& operator=(const __atomic_float&) = delete; diff --git a/libstdc++-v3/include/std/atomic b/libstdc++-v3/include/std/atomic index 46d2bbb509e..56dbe7bf5b9 100644 --- a/libstdc++-v3/include/std/atomic +++ b/libstdc++-v3/include/std/atomic @@ -251,7 +251,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { #if __cplusplus >= 201402L && __has_builtin(__builtin_clear_padding) if _GLIBCXX17_CONSTEXPR (__atomic_impl::__maybe_has_padding<_Tp>()) - __builtin_clear_padding(std::__addressof(_M_i)); + if (!std::__is_constant_evaluated()) + __builtin_clear_padding(std::__addressof(_M_i)); #endif } diff --git a/libstdc++-v3/testsuite/29_atomics/atomic/cons/static_zero_padding.cc b/libstdc++-v3/testsuite/29_atomics/atomic/cons/static_zero_padding.cc new file mode 100644 index 00000000000..506513647c6 --- /dev/null +++ b/libstdc++-v3/testsuite/29_atomics/atomic/cons/static_zero_padding.cc @@ -0,0 +1,106 @@ +// { dg-do run { target c++11 } } +// { dg-require-thread-fence "" } +// { dg-add-options no_pch } + +#include +#include +#include + +struct TailPadding { int i; char c; }; +TailPadding ztail{1, 2}; // zeroed-padding +constexpr std::atomic ctail(TailPadding{1,2}); +std::atomic gtail(TailPadding{1,2}); + +struct MidPadding { char c; int x; }; +MidPadding zmid{1, 2}; // zeroed-padding +constexpr std::atomic cmid(MidPadding{1,2}); +std::atomic gmid(MidPadding{1,2}); + +struct BitPadding { int : 4; int i : 5; int : 4; int j : 5; int : 4; }; +BitPadding zbit{1, 2}; // zeroed-padding +constexpr std::atomic cbit(BitPadding{1,2}); +std::atomic gbit(BitPadding{1,2}); + +struct Ctor +{ + Ctor() = default; + + constexpr Ctor(char pc, char pi) + : c(pc), i(pi), d(pc) + {} + + char c; + int i; + char d; +}; + +Ctor zctor{1, 2}; // zeroed-padding +constexpr std::atomic cctor(Ctor{1,2}); +std::atomic gctor(Ctor{1,2}); + +template +void test_struct(std::atomic& g, const T& zp) +{ + T const d{3, 4}; + T t; + + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( g.compare_exchange_strong(t, d) ); + + static std::atomic st(T{1, 2}); + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( st.compare_exchange_strong(t, d) ); + + thread_local std::atomic tl(T{1, 2}); + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( tl.compare_exchange_strong(t, d) ); + + std::atomic l(T{1, 2}); + std::memcpy(&t, &zp, sizeof(T)); +#if __cplusplus >= 201402L // Remove once PR114865 is fixed + VERIFY( l.compare_exchange_strong(t, d) ); +#endif + + constexpr std::atomic cl(T{1, 2}); +} + +#if __cplusplus >= 202002L +long double zld(10.5); +constexpr std::atomic cld(10.5); +std::atomic gld(10.5); + +template +void test_floating(std::atomic& g, const T& zp) +{ + T const d = T(7.5); + T t; + + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( g.compare_exchange_strong(t, d) ); + + static std::atomic st(T(10.5)); + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( st.compare_exchange_strong(t, d) ); + + thread_local std::atomic tl(T(10.5)); + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( tl.compare_exchange_strong(t, d) ); + + std::atomic l(T(10.5)); + std::memcpy(&t, &zp, sizeof(T)); + VERIFY( l.compare_exchange_strong(t, d) ); + + constexpr std::atomic cl(T(10.5)); +} +#endif + +int main() +{ + test_struct(gtail, ztail); + test_struct(gmid, zmid); + test_struct(gbit, zbit); + test_struct(gctor, zctor); +#if __cplusplus >= 202002L + test_floating(gld, zld); +#endif +}