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

Jonathan Wakely jwakely@redhat.com
Tue Jul 7 18:07:02 GMT 2026


On Tue, 07 Jul 2026 at 18:58 +0100, Jonathan Wakely wrote:
>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?)

See https://gcc.gnu.org/PR116159 and https://gcc.gnu.org/PR123908 for
relevant discussion around the --enable-symvers / --disable-symvers
topic.


>
>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