I'm looking for a workaround, which will probably involve patching libstdc++ headers. Preserving binary compatibility is preferred but not obligatory, since I'm not using any precompiled C++ code except libstdc++.
I want to keep the std::call_once
interface, since I'm trying to compile third-party code that uses is, which I don't want to change.
Here's my code:
#include <iostream>
#include <mutex>
int main()
{
std::once_flag flag;
std::call_once(flag, []{std::cout << "Once!\n";});
}
Running it causes a segmentation fault.
I cross-compile it to Windows from Ubuntu using Clang 12, using the standard library from MSYS2 GCC 10.2. Then I test the result with Wine (a quick test showed that it crashes on a VM too). But you should be able to reproduce the results by compiling on Windows natively (using the official Clang binaries + MSYS2 GCC, since MSYS2 doesn't have Clang 12 yet).
I compile it like this:
clang++-12 1.cpp --target=x86_64-w64-mingw32 --sysroot=/mingw64 -pthread -femulated-tls
If I add -g
, GDB shows following:
Program received signal SIGSEGV, Segmentation fault.
0x00000001e014dc4a in ?? () from Z:\home\holyblackcat\Sandbox\2\libgcc_s_seh-1.dll
(gdb) bt
#0 0x00000001e014dc4a in ?? () from Z:\home\holyblackcat\Sandbox\2\libgcc_s_seh-1.dll
#1 0x00000000004015f3 in std::call_once<main::$_0> (__once=..., __f=...) at /mingw64/include/c++/10.2.0/mutex:721
#2 0x00000000004015b5 in main () at 1.cpp:8
(gdb) f 1
#1 0x00000000004015f3 in std::call_once<main::$_0> (__once=..., __f=...) at /mingw64/include/c++/10.2.0/mutex:721
721 __once_callable = std::__addressof(__callable);
(gdb) list
716 auto __callable = [&] {
717 std::__invoke(std::forward<_Callable>(__f),
718 std::forward<_Args>(__args)...);
719 };
720 #ifdef _GLIBCXX_HAVE_TLS
721 __once_callable = std::__addressof(__callable);
722 __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
723 #else
724 unique_lock<mutex> __functor_lock(__get_once_mutex());
725 __once_functor = __callable;
Clang version is:
# clang++-12 --version --target=x86_64-w64-mingw32 --sysroot=/mingw64 -pthread -femulated-tls
Ubuntu clang version 12.0.1-++20210423082613+072c90a863aa-1~exp1~20210423063319.76
Target: x86_64-w64-windows-gnu
Thread model: posix
GCC version (which provides libstdc++) is:
# g++ --version
g++.exe (Rev10, Built by MSYS2 project) 10.2.0
Compiling the code with this GCC (which is native, not cross-compiling), produces a working code.
What's going on here? Are there any workarounds, or do I have to downgrade to Clang 11?
I reported a Clang bug.
This bug looks related.
Here is the current segfaulting implementation of call_once
, after preprocessing:
struct once_flag
{
private:
typedef __gthread_once_t __native_type;
__native_type _M_once = 0;
public:
constexpr once_flag() noexcept = default;
once_flag(const once_flag &) = delete;
once_flag &operator=(const once_flag &) = delete;
template <typename _Callable, typename... _Args>
friend void call_once(once_flag &__once, _Callable &&__f, _Args &&...__args);
};
extern __thread void *__once_callable;
extern __thread void (*__once_call)();
extern "C" void __once_proxy(void);
template <typename _Callable, typename... _Args>
void call_once(once_flag &__once, _Callable &&__f, _Args &&...__args)
{
auto __callable = [&]
{
std::__invoke(std::forward<_Callable>(__f), std::forward<_Args>(__args)...);
};
__once_callable = std::__addressof(__callable);
__once_call = []{(*(decltype(__callable) *)__once_callable)();};
int __e = __gthread_once(&__once._M_once, &__once_proxy);
if (__e)
__throw_system_error(__e);
}
This was fixed in Clang 13, in commit 0e4cf80
. (Thanks @mstorsjo.)
If you're stuck with Clang 12, you can patch libstdc++ headers as a workaround. The patch below replaces global thread-local variables with static
function-local thread-local variables, which are not affected by the bug.
To apply, save following to patch.txt
, then do patch /mingw64/include/c++/10.3.0/mutex patch.txt
.
@@ -691,8 +691,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
/// @cond undocumented
#ifdef _GLIBCXX_HAVE_TLS
- extern __thread void* __once_callable;
- extern __thread void (*__once_call)();
+ inline void *&__once_callable_get() {static __thread void *__ret; return __ret;}
+ inline void (*&__once_call_get())() {static __thread void (*__ret)(); return __ret;}
#else
extern function<void()> __once_functor;
@@ -703,7 +703,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__get_once_mutex();
#endif
- extern "C" void __once_proxy(void);
+ extern "C" inline void __once_proxy_inline(void) {__once_call_get()();}
/// @endcond
/// Invoke a callable and synchronize with other calls using the same flag
@@ -718,15 +718,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
std::forward<_Args>(__args)...);
};
#ifdef _GLIBCXX_HAVE_TLS
- __once_callable = std::__addressof(__callable); // NOLINT: PR 82481
- __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
+ __once_callable_get() = std::__addressof(__callable); // NOLINT: PR 82481
+ __once_call_get() = []{ (*(decltype(__callable)*)__once_callable_get())(); };
#else
unique_lock<mutex> __functor_lock(__get_once_mutex());
__once_functor = __callable;
__set_once_functor_lock_ptr(&__functor_lock);
#endif
- int __e = __gthread_once(&__once._M_once, &__once_proxy);
+ int __e = __gthread_once(&__once._M_once, &__once_proxy_inline);
#ifndef _GLIBCXX_HAVE_TLS
if (__functor_lock)
And here is an equivalent patch for GCC 10.2 (above is for 10.3):
@@ -691,8 +691,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
/// @cond undocumented
#ifdef _GLIBCXX_HAVE_TLS
- extern __thread void* __once_callable;
- extern __thread void (*__once_call)();
+ inline void *&__once_callable_get() {static __thread void *__ret; return __ret;}
+ inline void (*&__once_call_get())() {static __thread void (*__ret)(); return __ret;}
#else
extern function<void()> __once_functor;
@@ -703,7 +703,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
__get_once_mutex();
#endif
- extern "C" void __once_proxy(void);
+ extern "C" inline void __once_proxy_inline(void) {__once_call_get()();}
/// @endcond
/// Invoke a callable and synchronize with other calls using the same flag
@@ -718,15 +718,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
std::forward<_Args>(__args)...);
};
#ifdef _GLIBCXX_HAVE_TLS
- __once_callable = std::__addressof(__callable);
- __once_call = []{ (*(decltype(__callable)*)__once_callable)(); };
+ __once_callable_get() = std::__addressof(__callable);
+ __once_call_get() = []{ (*(decltype(__callable)*)__once_callable_get())(); };
#else
unique_lock<mutex> __functor_lock(__get_once_mutex());
__once_functor = __callable;
__set_once_functor_lock_ptr(&__functor_lock);
#endif
- int __e = __gthread_once(&__once._M_once, &__once_proxy);
+ int __e = __gthread_once(&__once._M_once, &__once_proxy_inline);
#ifndef _GLIBCXX_HAVE_TLS
if (__functor_lock)
@@ -735,8 +735,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
#ifdef __clang_analyzer__
// PR libstdc++/82481
- __once_callable = nullptr;
- __once_call = nullptr;
+ __once_callable_get() = nullptr;
+ __once_call_get() = nullptr;
#endif
if (__e)
One way you might work around this is to take advantage of the fact that static
variables are, since C++11, initialised in a threadsafe way. Example:
#include <iostream>
void test ()
{
std::cout << "test\n";
static bool once = [] { std::cout << "Once!\n"; return true; } ();
(void) once;
}
int main()
{
test ();
test ();
}
Output:
test
Once!
test
As you can see, the lambda is called the first time the static variable comes into scope (and will only be called once).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With