On Thu, 4 Dec 2025 at 09:31, LIU Hao <[email protected]> wrote:
>
> The previous patch was incorrect, because it defined static references to 
> thread-local variables and in
> that case they would no longer be thread-local.
>
> Instead of that, those getter functions are called directly in 
> `_Prepare_execution`.
>
> ---
> For Windows, GCC can be configured with `--enable-tls` to enable native TLS.
> The native TLS implementation has a limitation that it is incapable of
> exporting thread-local variables from DLLs. Therefore, they are retrieved
> via getter functions instead.
>
> libstdc++-v3/ChangeLog:
>
>         * config/os/mingw32-w64/os_defines.h 
> (_GLIBCXX_NO_EXTERN_THREAD_LOCAL): New
>         macro.
>         * include/std/mutex (__get_once_callable): Declare function when
>         _GLIBCXX_NO_EXTERN_THREAD_LOCAL.
>         (__get_once_call): Likewise.
>         (once_flag::_Prepare_execution::_Prepare_execution): Retrieve 
> thread-local
>         pointers via functions when _GLIBCXX_NO_EXTERN_THREAD_LOCAL.
>         (once_flag::_Prepare_execution::~_Prepare_execution): Likewise.
>         * src/c++11/mutex.cc (__get_once_callable): Define function when
>         _GLIBCXX_NO_EXTERN_THREAD_LOCAL.
>         (__get_once_call): Likewise.
>
> Signed-off-by: LIU Hao <[email protected]>
> ---
>    .../config/os/mingw32-w64/os_defines.h        |  4 +++
>    libstdc++-v3/include/std/mutex                | 26 +++++++++++++++++--
>    libstdc++-v3/src/c++11/mutex.cc               | 14 ++++++++++
>    3 files changed, 42 insertions(+), 2 deletions(-)
>
> diff --git a/libstdc++-v3/config/os/mingw32-w64/os_defines.h
> b/libstdc++-v3/config/os/mingw32-w64/os_defines.h
> index 893cd704891b..70106fe792c6 100644
> --- a/libstdc++-v3/config/os/mingw32-w64/os_defines.h
> +++ b/libstdc++-v3/config/os/mingw32-w64/os_defines.h
> @@ -96,4 +96,8 @@
>    // See libstdc++/94268
>    #define _GLIBCXX_BUFSIZ 4096
>    +// Use functions to access thread-local variables from a different module.
> +// Windows does not support exporting thread-local data.
> +#define _GLIBCXX_NO_EXTERN_THREAD_LOCAL 1
> +
>    #endif
> diff --git a/libstdc++-v3/include/std/mutex b/libstdc++-v3/include/std/mutex
> index d4fc4c646488..703d8ca69997 100644
> --- a/libstdc++-v3/include/std/mutex
> +++ b/libstdc++-v3/include/std/mutex
> @@ -817,9 +817,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>    # ifdef _GLIBCXX_HAVE_TLS
>      // If TLS is available use thread-local state for the type-erased 
> callable
>      // that is being run by std::call_once in the current thread.
> +#  ifdef _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +
> +  void*&
> +  __get_once_callable() noexcept;
> +
> +  __typeof__(void (*)())&

This is a strange (and unnecessary) way to declare the return type.

Without the typeof, that declarator would be:

void (*&__get_once_call() noexcept)();

but that's horrible.

In C++14 I would declare it like this:

std::add_lvalue_reference_t<void (*)()>
__get_once_call() noexcept;

But for C++11 we don't have add_lvalue_reference, so we need the
slightly uglier form:

std::add_lvalue_reference<void (*)()>::type
__get_once_call() noexcept;




> +  __get_once_call() noexcept;
> +
> +#  else // ! _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +
>      extern __thread void* __once_callable;
>      extern __thread void (*__once_call)();
>    +#  endif // ! _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +
>      // RAII type to set up state for pthread_once call.
>      struct once_flag::_Prepare_execution
>      {
> @@ -827,17 +839,27 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>          explicit
>          _Prepare_execution(_Callable& __c)
>          {
> -       // Store address in thread-local pointer:
> +       // Store address in thread-local pointer, and trampoline function to
> +       // invoke the closure via thread-local pointer.
> +#  ifdef _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +       __get_once_callable() = std::__addressof(__c);
> +       __get_once_call() = [] { 
> (*static_cast<_Callable*>(__get_once_callable()))(); };

It occurs to me that we would only need the first #ifdef group if it
did something like the errno trick:

#define __once_callable __get_once_callable()
#define __once_call __get_once_call

(and #undef at the end of the file).

That way all the later code wouldn't need changes, it would continue
to use __once_callable and __once_call but those would expand to calls
to the getters. I don't love the use of macros with lowercase names,
but it reduces the diffs between Windows and other targets to just
that first #ifdef group.

What do you think?

> +#  else
>         __once_callable = std::__addressof(__c);
> -       // Trampoline function to invoke the closure via thread-local pointer:
>         __once_call = [] { (*static_cast<_Callable*>(__once_callable))(); };
> +#  endif
>          }
>         ~_Prepare_execution()
>        {
>          // PR libstdc++/82481
> +#  ifdef _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +      __get_once_callable() = nullptr;
> +      __get_once_call() = nullptr;
> +#  else
>          __once_callable = nullptr;
>          __once_call = nullptr;
> +#  endif
>        }
>         _Prepare_execution(const _Prepare_execution&) = delete;
> diff --git a/libstdc++-v3/src/c++11/mutex.cc b/libstdc++-v3/src/c++11/mutex.cc
> index d5da5c66ae99..82f0afa4cb4e 100644
> --- a/libstdc++-v3/src/c++11/mutex.cc
> +++ b/libstdc++-v3/src/c++11/mutex.cc
> @@ -34,6 +34,20 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>      __thread void* __once_callable;
>      __thread void (*__once_call)();
>    +# ifdef _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +
> +  // When thread-local variables can't be exported, these functions are 
> called
> +  // to retrieve these variables.
> +  void*&
> +  __get_once_callable() noexcept
> +  { return __once_callable; }
> +
> +  __typeof__(void (*)())&
> +  __get_once_call() noexcept
> +  { return __once_call; }
> +
> +# endif // _GLIBCXX_NO_EXTERN_THREAD_LOCAL
> +
>      extern "C" void __once_proxy()
>      {
>        // The caller stored a function pointer in __once_call. If it requires
> --
> 2.52.0
>

Reply via email to