[RFC PATCH] libstdc++: Provide ABI compatibility for _GLIBCXX_HAVE_TLS

Jonathan Wakely jwakely@redhat.com
Tue Jul 7 17:58:47 GMT 2026


On Tue, 15 Jul 2025 at 01:37 -0400, Keno Fischer wrote:
>The _GLIBCXX_HAVE_TLS flag is an ABI-breaking flag - setting it removes the
>definitions for `set_lock_ptr` (as well as the comptability symbols for
>even older ABIs). Try to improve this situation by changing the flag to
>retain the old symbols, falling back to the previous behavior if __once_call
>is not set. These symbols are used to forward closure data for the
>initialization
>from whichever thread ends up winning the pthread_once. The __once_call
>is set immediately before the pthread_once and unset (in a destructor)
>immediately after. To avoid the case where there may be a nested __once_proxy
>call that uses the old ABI, we also unset __once_call in the success path
>after retrieving the pointer.
>
>Signed-off-by: Keno Fischer <keno@juliahub.com>
>---
>
>Greetings from the Julia Language infrastructure team. For background
>behind this patch, we maintain a large number of pre-built binaries for
>multiple operating systems, including windows. On windows, we generally
>follow the MSYS2-provided mingw ABI for our binaries (even though we
>maintain our own toolchains to actually generate the binaries).
>At some point recently, msys2 switched to building gcc with --enable-tls.
>This caused an ABI break for us, making our old binaries no longer
>work in newer msys2 environments because. I have two distinct questions
>here:
>
>1. Is there something obviously wrong with this approach that I'm
>   missing?

It assumes that the libstdc++ shared library is built without
visibility support, which is not true by default, only if you
configure with --disable-symvers (so I assume you're using that for
your builds?)

To make the symbols like __set_once_functor_lock_ptr available for an
--enable-tls build, they'll need to be exported with global
visibility.

>2. Is this appropriate for upstream inclusion?

It adds additional global symbols to the shared library, which are
only needed for the case where a target has transitioned from
--disable-tls to --enable-tls and wants to preserve this one ABI
property. It doesn't seem very useful for non-mingw* targets.

But if we made it conditional on the target, or via a new configure
option, I think it would be acceptable.

>We're fine if the answer to #2 is no - in which case we'd ship a patched
>libstdc++ version with this patch for a while until all binaries that
>have shipped with the old ABI have filtered out as part of our usual
>replacement cycle. Of course, if this approach doesn't work, we'd
>appreciate any thoughts on how to maintain ABI compatibility here, even
>temporarily.
>
>(I think the diffstat looks messier than it is because of the indentation
>changes - the basic changes is just to merge the two __once_proxy
>calls together, checking if __once_call is set, using it if so and falling
>back to the old ABI if not).
>
> libstdc++-v3/src/c++11/mutex.cc | 111 +++++++++++++++++---------------
> 1 file changed, 59 insertions(+), 52 deletions(-)
>
>diff --git a/libstdc++-v3/src/c++11/mutex.cc b/libstdc++-v3/src/c++11/mutex.cc
>index d5da5c66ae9..4be64633315 100644
>--- a/libstdc++-v3/src/c++11/mutex.cc
>+++ b/libstdc++-v3/src/c++11/mutex.cc
>@@ -23,6 +23,7 @@
> // <http://www.gnu.org/licenses/>.
>
> #include <mutex>
>+#include <bits/std_function.h>  // std::function
>
> #ifdef _GLIBCXX_HAS_GTHREADS
>
>@@ -30,30 +31,17 @@ namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>-#ifdef _GLIBCXX_HAVE_TLS
>-  __thread void* __once_callable;
>-  __thread void (*__once_call)();
>+// Explicit instantiation due to -fno-implicit-instantiation.
>+template class function<void()>;
>
>-  extern "C" void __once_proxy()
>-  {
>-    // The caller stored a function pointer in __once_call. If it requires
>-    // any state, it gets it from __once_callable.
>-    __once_call();
>-  }
>+function<void()> __once_functor;
>
>-#else // ! TLS
>-
>-  // Explicit instantiation due to -fno-implicit-instantiation.
>-  template class function<void()>;
>-
>-  function<void()> __once_functor;
>-
>-  mutex&
>-  __get_once_mutex()
>-  {
>-    static mutex once_mutex;
>-    return once_mutex;
>-  }
>+mutex&
>+__get_once_mutex()
>+{
>+  static mutex once_mutex;
>+  return once_mutex;
>+}
>
> namespace
> {
>@@ -66,41 +54,60 @@ namespace
>   }
> }
>
>-  // code linked against ABI 3.4.12 and later uses this
>-  void
>-  __set_once_functor_lock_ptr(unique_lock<mutex>* __ptr)
>-  {
>-    (void) set_lock_ptr(__ptr);
>-  }
>+// code linked against ABI 3.4.12 and later uses this
>+void
>+__set_once_functor_lock_ptr(unique_lock<mutex>* __ptr)
>+{
>+  (void) set_lock_ptr(__ptr);
>+}
>
>-  // unsafe - retained for compatibility with ABI 3.4.11
>-  unique_lock<mutex>&
>-  __get_once_functor_lock()
>-  {
>-    static unique_lock<mutex> once_functor_lock(__get_once_mutex(),
>defer_lock);
>-    return once_functor_lock;
>-  }
>+// unsafe - retained for compatibility with ABI 3.4.11
>+unique_lock<mutex>&
>+__get_once_functor_lock()
>+{
>+  static unique_lock<mutex> once_functor_lock(__get_once_mutex(), defer_lock);
>+  return once_functor_lock;
>+}
>
>-  // This is called via pthread_once while __get_once_mutex() is locked.
>-  extern "C" void
>-  __once_proxy()
>-  {
>-    // Get the callable out of the global functor.
>-    function<void()> callable = std::move(__once_functor);
>-
>-    // Then unlock the global mutex
>-    if (unique_lock<mutex>* lock = set_lock_ptr(nullptr))
>-    {
>-      // Caller is using the new ABI and provided a pointer to its lock.
>-      lock->unlock();
>+#ifdef _GLIBCXX_HAVE_TLS
>+  __thread void* __once_callable;
>+  __thread void (*__once_call)();
>+#endif
>+
>+extern "C" void
>+__once_proxy()
>+{
>+#ifdef _GLIBCXX_HAVE_TLS
>+    if (__once_call) {

I'd like a [[__likely__]] attribute on the condition above.

>+      void (*this_once_call)() = __once_call;
>+      // Reset __once_call early in case of a nested call to __once_proxy
>+      // with the old ABI.
>+      __once_call = NULL;
>+      return this_once_call();
>     }
>-    else
>-      __get_once_functor_lock().unlock();  // global lock
>
>-    // Finally, invoke the callable.
>-    callable();
>+    // For compatibility with callers linked against non-_GLIBCXX_HAVE_TLS ABI,
>+    // we fall through here to the old behavior.
>+#endif
>+
>+  // If the caller is not using _GLIBCXX_HAVE_TLS, this was called
>+  // via pthread_once while __get_once_mutex() is locked.
>+
>+  // Get the callable out of the global functor.
>+  function<void()> callable = std::move(__once_functor);
>+
>+  // Then unlock the global mutex
>+  if (unique_lock<mutex>* lock = set_lock_ptr(nullptr))
>+  {
>+    // Caller is using the new ABI and provided a pointer to its lock.
>+    lock->unlock();
>   }
>-#endif // ! TLS
>+  else
>+    __get_once_functor_lock().unlock();  // global lock
>+
>+  // Finally, invoke the callable.
>+  callable();
>+}
>
> _GLIBCXX_END_NAMESPACE_VERSION
> } // namespace std
>--
>2.43.0
>



More information about the Libstdc++ mailing list