From 03ba3a395e72b2a1cedebe57a75493b53425ea8c Mon Sep 17 00:00:00 2001 From: Sam Clegg Date: Tue, 10 Mar 2026 11:33:30 -0700 Subject: [PATCH] Replace `call_once` usage in libc++ with atomics This avoid a dependency on pthread API (which are used to implement call_once under the hood) which makes libc++ much more usable from Wasm Workers (there pthreads are not available). The downside here is that when thread race to be first one to use a given facet we would "leak" an ID which could cause the `facets_` vector to become more spare that it otherwise would be. IIUC this `facets_` vector can already be sparse so it not clear to me what impact this would have in practice. We could make this change only for the Wasm Workers build of libc++ but I'd like to get as much test coverage of it as I can. See: #26375 --- system/lib/libcxx/include/__locale | 2 ++ system/lib/libcxx/src/locale.cpp | 17 +++++++++++++++++ test/codesize/hello_libcxx.cpp | 1 - test/test_other.py | 3 +++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/system/lib/libcxx/include/__locale b/system/lib/libcxx/include/__locale index 757a53951f66e..3dbb55ef20af4 100644 --- a/system/lib/libcxx/include/__locale +++ b/system/lib/libcxx/include/__locale @@ -146,7 +146,9 @@ private: }; class _LIBCPP_EXPORTED_FROM_ABI locale::id { +#if !defined(__EMSCRIPTEN__) || !defined(__EMSCRIPTEN_WASM_WORKERS__) once_flag __flag_; +#endif int32_t __id_; static int32_t __next_id; diff --git a/system/lib/libcxx/src/locale.cpp b/system/lib/libcxx/src/locale.cpp index da735865c322c..c4b5383889161 100644 --- a/system/lib/libcxx/src/locale.cpp +++ b/system/lib/libcxx/src/locale.cpp @@ -589,7 +589,24 @@ void locale::facet::__on_zero_shared() noexcept { delete this; } constinit int32_t locale::id::__next_id = 0; long locale::id::__get() { +#if defined(__EMSCRIPTEN__) && defined(__EMSCRIPTEN_WASM_WORKERS__) + // Avoid `call_once` under Emscripten since we want to avoid the pthread + // dependenency that it comes with. + // TODO(https://github.com/emscripten-core/emscripten/issues/26426): + // Remove this patch once we have some kind of locking primitive + // in Wasm Worker that can be used to implement call_once (or + // __libcpp_mutex_t/__libcpp_condvar_t). + if (__libcpp_atomic_load(&__id_) == 0) { + int32_t proposed_id = __libcpp_atomic_add(&__next_id, 1); + int32_t expected = 0; + // If we race with another thread here the CAS will fail and + // the proposed_id will be leaked, but __id_ will be non-zero + // in either case. + __libcpp_atomic_compare_exchange(&__id_, &expected, proposed_id); + } +#else call_once(__flag_, [&] { __id_ = __libcpp_atomic_add(&__next_id, 1); }); +#endif return __id_ - 1; } diff --git a/test/codesize/hello_libcxx.cpp b/test/codesize/hello_libcxx.cpp index dfe21e184e8ec..8275e0fee6305 100644 --- a/test/codesize/hello_libcxx.cpp +++ b/test/codesize/hello_libcxx.cpp @@ -9,4 +9,3 @@ int main() { std::cout << "hello, world!" << std::endl; return 0; } - diff --git a/test/test_other.py b/test/test_other.py index 7ba92eb07b506..f15f227bef075 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -13537,6 +13537,9 @@ def test_wasm_worker_pthread_api_usage(self): def test_wasm_worker_cxx_init(self): self.do_run_in_out_file_test('wasm_worker/wasm_worker_cxx_init.cpp', cflags=['-sWASM_WORKERS']) + def test_wasm_worker_hello_libcxx(self): + self.do_runf('codesize/hello_libcxx.cpp', 'hello, world!\n', cflags=['-sWASM_WORKERS']) + @parameterized({ # we will warn here since -O2 runs the optimizer and -g enables DWARF 'O2_g': (True, ['-O2', '-g']),