The standard requires that std::atomic<integral-type>::fetch_add does
not have undefined behaviour for signed overflow, instead it wraps like
unsigned integers. The compiler ensures this is true for the atomic
built-ins that std::atomic uses, but it's not currently true for the
__gnu_cxx::__exchange_and_add and __gnu_cxx::__atomic_add functions
defined in libstdc++, which operate on type _Atomic_word.

For the inline __exchange_and_add_single function (used when there's
only one thread in the process), we can copy the value to an unsigned
long and do the addition on that, then assign it back to the
_Atomic_word variable.

The __exchange_and_add in config/cpu/generic/atomicity_mutex/atomicity.h
locks a mutex and then performs exactly the same steps as
__exchange_and_add_single.  Calling __exchange_and_add_single instead of
duplicating the code benefits from the fix just made to
__exchange_and_add_single.

For the remaining config/cpu/$arch/atomicity.h implementations, they
either use inline assembly which uses wrapping instructions (so no
changes needed), or we can fix them by compiling with -fwrapv.

After ths change, UBsan no longer gives an error for:

  _Atomic_word i = INT_MAX;
  __gnu_cxx::__exchange_and_add_dispatch(&i, 1);

/usr/include/c++/14/ext/atomicity.h:85:12: runtime error: signed integer 
overflow: 2147483647 + 1 cannot be represented in type 'int'

libstdc++-v3/ChangeLog:

        PR libstdc++/121148
        * config/cpu/generic/atomicity_mutex/atomicity.h
        (__exchange_and_add): Call __exchange_and_add_single.
        * include/ext/atomicity.h (__exchange_and_add_single): Use an
        unsigned type for the addition.
        * libsupc++/Makefile.am (atomicity.o): Compile with -fwrapv.
        * libsupc++/Makefile.in: Regenerate.
---

Tested x86_64-linux.

This should not affect codegen, except maybe making it a bit faster with
UBsan enabled because there's no instrumentation needed to detect signed
overflows!

 .../cpu/generic/atomicity_mutex/atomicity.h   |  5 +--
 libstdc++-v3/include/ext/atomicity.h          | 35 +++++++++++++++++--
 libstdc++-v3/libsupc++/Makefile.am            |  5 +++
 libstdc++-v3/libsupc++/Makefile.in            |  5 +++
 4 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/libstdc++-v3/config/cpu/generic/atomicity_mutex/atomicity.h 
b/libstdc++-v3/config/cpu/generic/atomicity_mutex/atomicity.h
index b1328c025bcc..7d5772d54840 100644
--- a/libstdc++-v3/config/cpu/generic/atomicity_mutex/atomicity.h
+++ b/libstdc++-v3/config/cpu/generic/atomicity_mutex/atomicity.h
@@ -44,10 +44,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __exchange_and_add(volatile _Atomic_word* __mem, int __val) throw ()
   {
     __gnu_cxx::__scoped_lock sentry(get_atomic_mutex());
-    _Atomic_word __result;
-    __result = *__mem;
-    *__mem += __val;
-    return __result;
+    return __gnu_cxx::__exchange_and_add_single(__mem, __val);
   }
 
   void
diff --git a/libstdc++-v3/include/ext/atomicity.h 
b/libstdc++-v3/include/ext/atomicity.h
index 650b786cd8e6..0b970f33f4b6 100644
--- a/libstdc++-v3/include/ext/atomicity.h
+++ b/libstdc++-v3/include/ext/atomicity.h
@@ -39,6 +39,9 @@
 #if __has_include(<sys/single_threaded.h>)
 # include <sys/single_threaded.h>
 #endif
+#if __cplusplus >= 201103L
+# include <type_traits> // make_unsigned_t
+#endif
 
 namespace __gnu_cxx _GLIBCXX_VISIBILITY(default)
 {
@@ -79,19 +82,47 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   __atomic_add(volatile _Atomic_word*, int) _GLIBCXX_NOTHROW;
 #endif
 
+#if __cplusplus < 201103L
+  // The array bound will be ill-formed in the very unlikely case that
+  // _Atomic_word is wider than long and we need to use unsigned long long
+  // below in __exchange_and_add_single and __atomic_add_single.
+  typedef int
+    _Atomic_word_fits_in_long[sizeof(_Atomic_word) <= sizeof(long) ? 1 : -1];
+#endif
+
   inline _Atomic_word
   __attribute__((__always_inline__))
   __exchange_and_add_single(_Atomic_word* __mem, int __val)
   {
     _Atomic_word __result = *__mem;
-    *__mem += __val;
+    // Do the addition with an unsigned type so that overflow is well defined.
+#if __cplusplus >= 201103L
+    std::make_unsigned<_Atomic_word>::type __u;
+#else
+    // For most targets make_unsigned_t<_Atomic_word> is unsigned int,
+    // but 64-bit sparc uses long for _Atomic_word.
+    // Sign-extending to unsigned long works for both cases.
+    unsigned long __u;
+#endif
+    __u = __result;
+    __u += __val;
+    *__mem = __u;
     return __result;
   }
 
   inline void
   __attribute__((__always_inline__))
   __atomic_add_single(_Atomic_word* __mem, int __val)
-  { *__mem += __val; }
+  {
+#if __cplusplus >= 201103L
+    std::make_unsigned<_Atomic_word>::type __u;
+#else
+    unsigned long __u; // see above
+#endif
+    __u = *__mem;
+    __u += __val;
+    *__mem = __u;
+  }
 
   inline _Atomic_word
   __attribute__ ((__always_inline__))
diff --git a/libstdc++-v3/libsupc++/Makefile.am 
b/libstdc++-v3/libsupc++/Makefile.am
index 60e7d1594e6d..098d93719ce6 100644
--- a/libstdc++-v3/libsupc++/Makefile.am
+++ b/libstdc++-v3/libsupc++/Makefile.am
@@ -132,6 +132,11 @@ atomicity_file = 
${glibcxx_srcdir}/$(ATOMICITY_SRCDIR)/atomicity.h
 atomicity.cc: ${atomicity_file}
        $(LN_S) ${atomicity_file} ./atomicity.cc || true
 
+atomicity.lo: atomicity.cc
+       $(LTCOMPILE) -fwrapv -c $<
+atomicity.o: atomicity.cc
+       $(CXXCOMPILE) -fwrapv -c $<
+
 if OS_IS_DARWIN
 # See PR 112397
 new_opvnt.lo: new_opvnt.cc
diff --git a/libstdc++-v3/libsupc++/Makefile.in 
b/libstdc++-v3/libsupc++/Makefile.in
index 732ab89c8a2e..b691915e396b 100644
--- a/libstdc++-v3/libsupc++/Makefile.in
+++ b/libstdc++-v3/libsupc++/Makefile.in
@@ -973,6 +973,11 @@ cp-demangle.o: cp-demangle.c
 atomicity.cc: ${atomicity_file}
        $(LN_S) ${atomicity_file} ./atomicity.cc || true
 
+atomicity.lo: atomicity.cc
+       $(LTCOMPILE) -fwrapv -c $<
+atomicity.o: atomicity.cc
+       $(CXXCOMPILE) -fwrapv -c $<
+
 # See PR 112397
 @OS_IS_DARWIN_TRUE@new_opvnt.lo: new_opvnt.cc
 @OS_IS_DARWIN_TRUE@    $(LTCXXCOMPILE) -fno-reorder-blocks-and-partition -I. 
-c $<
-- 
2.50.1

Reply via email to