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 <k...@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?

2. Is this appropriate for upstream inclusion?

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) {
+      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

Reply via email to