[libcxx-commits] [libcxxabi] [libc++abi] Define `__cxa_thread_atexit` on platforms that need it (PR #186054)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 6 06:51:54 PDT 2026


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/186054

>From 46301f073b8cc85ad0d2f1dcdda923209bff173b Mon Sep 17 00:00:00 2001
From: Catherine <whitequark at whitequark.org>
Date: Thu, 12 Mar 2026 08:19:49 +0000
Subject: [PATCH 1/7] [libc++abi] Revert gating of `__cxa_thread_atexit` on
 Wasm/WASI

This was done in the commit 3c100d5d548d with the description
"Enable -Wmissing-prototypes" which seems incongruent to me.

Since then it's made its way into a release and broke the use of
`thread_local` variables with destructors on Wasm/WASI:

```cc
// repro.cc
struct c { ~c() {} };
thread_local c v;
int main() { (void)v; }
```

```console
$ ./wasi-sdk-31.0-x86_64-linux/bin/clang++ repro.cc
wasm-ld: error: /tmp/repro-dd1ad7.o: undefined symbol: __cxa_thread_atexit
clang++: error: linker command failed with exit code 1 (use -v to see invocation)
```
---
 libcxxabi/src/cxa_thread_atexit.cpp | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libcxxabi/src/cxa_thread_atexit.cpp b/libcxxabi/src/cxa_thread_atexit.cpp
index 402a52c741012..099be242c6dc6 100644
--- a/libcxxabi/src/cxa_thread_atexit.cpp
+++ b/libcxxabi/src/cxa_thread_atexit.cpp
@@ -106,7 +106,7 @@ namespace {
 
 #endif // HAVE___CXA_THREAD_ATEXIT_IMPL
 
-#if defined(__linux__) || defined(__Fuchsia__)
+#if defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
 extern "C" {
 
   _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(Dtor dtor, void* obj, void* dso_symbol) throw() {
@@ -141,6 +141,7 @@ extern "C" {
     }
 #endif // HAVE___CXA_THREAD_ATEXIT_IMPL
   }
+
 } // extern "C"
-#endif // defined(__linux__) || defined(__Fuchsia__)
+#endif // defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
 } // namespace __cxxabiv1

>From c15c52ff34166b3ad5deb6633a897c2e6e5f2b0f Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 3 Aug 2026 16:40:57 -0400
Subject: [PATCH 2/7] Make header consistent with .cpp file

---
 libcxxabi/include/cxxabi.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxxabi/include/cxxabi.h b/libcxxabi/include/cxxabi.h
index 66301d308282d..c8a2c3b7b8935 100644
--- a/libcxxabi/include/cxxabi.h
+++ b/libcxxabi/include/cxxabi.h
@@ -179,7 +179,7 @@ __cxa_decrement_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT;
 extern _LIBCXXABI_FUNC_VIS bool __cxa_uncaught_exception() _LIBCXXABI_NOEXCEPT;
 extern _LIBCXXABI_FUNC_VIS unsigned int __cxa_uncaught_exceptions() _LIBCXXABI_NOEXCEPT;
 
-#if defined(__linux__) || defined(__Fuchsia__)
+#if defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
 // Linux and Fuchsia TLS support. Not yet an official part of the Itanium ABI.
 // https://sourceware.org/glibc/wiki/Destructor%20support%20for%20thread_local%20variables
 extern _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(void (*)(void *), void *,

>From edecf53f872925bb01316509a016b7d0ea783a67 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 3 Aug 2026 16:47:31 -0400
Subject: [PATCH 3/7] Move logic to source file, not CMake

---
 libcxxabi/src/CMakeLists.txt        |  8 +-------
 libcxxabi/src/cxa_thread_atexit.cpp | 17 ++++++++++-------
 2 files changed, 11 insertions(+), 14 deletions(-)

diff --git a/libcxxabi/src/CMakeLists.txt b/libcxxabi/src/CMakeLists.txt
index dd23e9bb5bf79..8f779d913e0a9 100644
--- a/libcxxabi/src/CMakeLists.txt
+++ b/libcxxabi/src/CMakeLists.txt
@@ -9,6 +9,7 @@ set(LIBCXXABI_SOURCES
   cxa_handlers.cpp
   cxa_vector.cpp
   cxa_virtual.cpp
+  cxa_thread_atexit.cpp
   # C++ STL files
   stdlib_exception.cpp
   stdlib_stdexcept.cpp
@@ -36,13 +37,6 @@ else()
   )
 endif()
 
-if (LIBCXXABI_ENABLE_THREADS AND (UNIX OR FUCHSIA) AND NOT (APPLE OR CYGWIN)
-    AND NOT ("${CMAKE_SYSTEM_NAME}" MATCHES "AIX"))
-  list(APPEND LIBCXXABI_SOURCES
-    cxa_thread_atexit.cpp
-  )
-endif()
-
 set(LIBCXXABI_HEADERS
   ../include/cxxabi.h
 )
diff --git a/libcxxabi/src/cxa_thread_atexit.cpp b/libcxxabi/src/cxa_thread_atexit.cpp
index 099be242c6dc6..66c86426ba204 100644
--- a/libcxxabi/src/cxa_thread_atexit.cpp
+++ b/libcxxabi/src/cxa_thread_atexit.cpp
@@ -6,16 +6,18 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include "abort_message.h"
 #include "cxxabi.h"
+
+#if !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
+
 #include <__thread/support.h>
-#ifndef _LIBCXXABI_HAS_NO_THREADS
+#include <stdlib.h>
+
+#include "abort_message.h"
+
 #if defined(__ELF__) && defined(_LIBCXXABI_LINK_PTHREAD_LIB)
 #pragma comment(lib, "pthread")
 #endif
-#endif
-
-#include <stdlib.h>
 
 namespace __cxxabiv1 {
 
@@ -106,7 +108,6 @@ namespace {
 
 #endif // HAVE___CXA_THREAD_ATEXIT_IMPL
 
-#if defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
 extern "C" {
 
   _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(Dtor dtor, void* obj, void* dso_symbol) throw() {
@@ -143,5 +144,7 @@ extern "C" {
   }
 
 } // extern "C"
-#endif // defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
+
 } // namespace __cxxabiv1
+
+#endif // !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)

>From 07bda8b5a8bad05073af7f7b5f87915083dae872 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 3 Aug 2026 17:09:29 -0400
Subject: [PATCH 4/7] Opt-out instead of opt-in

---
 libcxxabi/include/__cxxabi_config.h | 8 ++++++++
 libcxxabi/src/cxa_thread_atexit.cpp | 4 ++--
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/libcxxabi/include/__cxxabi_config.h b/libcxxabi/include/__cxxabi_config.h
index 6ec10e469387e..01037fdbe8949 100644
--- a/libcxxabi/include/__cxxabi_config.h
+++ b/libcxxabi/include/__cxxabi_config.h
@@ -134,4 +134,12 @@
 #  define _LIBCXXABI_DISABLE_POINTER_FIELD_PROTECTION
 #endif
 
+// On Apple, Clang generates calls to _tlv_atexit and __cxa_thread_atexit is defined
+// in libc instead.
+//
+// AIX uses a different mechanism for registering thread local destructors (__pt_atexit_np).
+#if !defined(__APPLE__) && !defined(_AIX)
+#  define _LIBCXXABI_DEFINE_THREAD_ATEXIT
+#endif
+
 #endif // ____CXXABI_CONFIG_H
diff --git a/libcxxabi/src/cxa_thread_atexit.cpp b/libcxxabi/src/cxa_thread_atexit.cpp
index 66c86426ba204..cf186ddc80718 100644
--- a/libcxxabi/src/cxa_thread_atexit.cpp
+++ b/libcxxabi/src/cxa_thread_atexit.cpp
@@ -8,7 +8,7 @@
 
 #include "cxxabi.h"
 
-#if !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
+#if !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(_LIBCXXABI_DEFINE_THREAD_ATEXIT)
 
 #include <__thread/support.h>
 #include <stdlib.h>
@@ -147,4 +147,4 @@ extern "C" {
 
 } // namespace __cxxabiv1
 
-#endif // !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
+#endif // !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(_LIBCXXABI_DEFINE_THREAD_ATEXIT)

>From ab564448f85ccb31499bb3b50613a260844d5742 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Mon, 3 Aug 2026 17:11:01 -0400
Subject: [PATCH 5/7] Update header

---
 libcxxabi/include/cxxabi.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/libcxxabi/include/cxxabi.h b/libcxxabi/include/cxxabi.h
index c8a2c3b7b8935..f12ce8a01576b 100644
--- a/libcxxabi/include/cxxabi.h
+++ b/libcxxabi/include/cxxabi.h
@@ -179,8 +179,8 @@ __cxa_decrement_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT;
 extern _LIBCXXABI_FUNC_VIS bool __cxa_uncaught_exception() _LIBCXXABI_NOEXCEPT;
 extern _LIBCXXABI_FUNC_VIS unsigned int __cxa_uncaught_exceptions() _LIBCXXABI_NOEXCEPT;
 
-#if defined(__linux__) || defined(__Fuchsia__) || defined(__wasm__)
-// Linux and Fuchsia TLS support. Not yet an official part of the Itanium ABI.
+#if defined(_LIBCXXABI_DEFINE_THREAD_ATEXIT)
+// Register a thread local destructor. This is not yet an official part of the Itanium ABI.
 // https://sourceware.org/glibc/wiki/Destructor%20support%20for%20thread_local%20variables
 extern _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(void (*)(void *), void *,
                                                    void *) _LIBCXXABI_NOEXCEPT;

>From 9eb67b35749639f66e8ebd98e72072bd9639f963 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 4 Aug 2026 09:52:37 -0400
Subject: [PATCH 6/7] Use 0-1 macro

---
 libcxxabi/include/__cxxabi_config.h | 11 +++++++----
 libcxxabi/include/cxxabi.h          |  2 +-
 libcxxabi/src/cxa_thread_atexit.cpp |  4 ++--
 3 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/libcxxabi/include/__cxxabi_config.h b/libcxxabi/include/__cxxabi_config.h
index 01037fdbe8949..1b40ccf7d0a55 100644
--- a/libcxxabi/include/__cxxabi_config.h
+++ b/libcxxabi/include/__cxxabi_config.h
@@ -135,11 +135,14 @@
 #endif
 
 // On Apple, Clang generates calls to _tlv_atexit and __cxa_thread_atexit is defined
-// in libc instead.
+// in libc instead. AIX uses a different mechanism for registering thread local
+// destructors (__pt_atexit_np).
 //
-// AIX uses a different mechanism for registering thread local destructors (__pt_atexit_np).
-#if !defined(__APPLE__) && !defined(_AIX)
-#  define _LIBCXXABI_DEFINE_THREAD_ATEXIT
+// On other platforms, libc++abi provides __cxa_thread_atexit.
+#if defined(__APPLE__) || defined(_AIX)
+#  define _LIBCXXABI_DEFINE_THREAD_ATEXIT 0
+#else
+#  define _LIBCXXABI_DEFINE_THREAD_ATEXIT 1
 #endif
 
 #endif // ____CXXABI_CONFIG_H
diff --git a/libcxxabi/include/cxxabi.h b/libcxxabi/include/cxxabi.h
index f12ce8a01576b..5d0513c4ccd66 100644
--- a/libcxxabi/include/cxxabi.h
+++ b/libcxxabi/include/cxxabi.h
@@ -179,7 +179,7 @@ __cxa_decrement_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT;
 extern _LIBCXXABI_FUNC_VIS bool __cxa_uncaught_exception() _LIBCXXABI_NOEXCEPT;
 extern _LIBCXXABI_FUNC_VIS unsigned int __cxa_uncaught_exceptions() _LIBCXXABI_NOEXCEPT;
 
-#if defined(_LIBCXXABI_DEFINE_THREAD_ATEXIT)
+#if _LIBCXXABI_DEFINE_THREAD_ATEXIT
 // Register a thread local destructor. This is not yet an official part of the Itanium ABI.
 // https://sourceware.org/glibc/wiki/Destructor%20support%20for%20thread_local%20variables
 extern _LIBCXXABI_FUNC_VIS int __cxa_thread_atexit(void (*)(void *), void *,
diff --git a/libcxxabi/src/cxa_thread_atexit.cpp b/libcxxabi/src/cxa_thread_atexit.cpp
index cf186ddc80718..3ddca2ddfb229 100644
--- a/libcxxabi/src/cxa_thread_atexit.cpp
+++ b/libcxxabi/src/cxa_thread_atexit.cpp
@@ -8,7 +8,7 @@
 
 #include "cxxabi.h"
 
-#if !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(_LIBCXXABI_DEFINE_THREAD_ATEXIT)
+#if !defined(_LIBCXXABI_HAS_NO_THREADS) && _LIBCXXABI_DEFINE_THREAD_ATEXIT
 
 #include <__thread/support.h>
 #include <stdlib.h>
@@ -147,4 +147,4 @@ extern "C" {
 
 } // namespace __cxxabiv1
 
-#endif // !defined(_LIBCXXABI_HAS_NO_THREADS) && defined(_LIBCXXABI_DEFINE_THREAD_ATEXIT)
+#endif // !defined(_LIBCXXABI_HAS_NO_THREADS) && _LIBCXXABI_DEFINE_THREAD_ATEXIT

>From ad6774640b01aaab0c7d7ea40c5f229174c9a986 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 4 Aug 2026 10:26:04 -0400
Subject: [PATCH 7/7] Disable on Windows too.

As a drive-by, add missing _LIBCPP_TLS_DESTRUCTOR_CC annotation. This
was noticed when building on Windows. It's semantically the right thing
to use here but it expands to nothing on all platforms where we provide
that function (for now).
---
 libcxxabi/include/__cxxabi_config.h | 10 +++++++---
 libcxxabi/src/cxa_thread_atexit.cpp |  4 +++-
 2 files changed, 10 insertions(+), 4 deletions(-)

diff --git a/libcxxabi/include/__cxxabi_config.h b/libcxxabi/include/__cxxabi_config.h
index 1b40ccf7d0a55..7dafa976d6de2 100644
--- a/libcxxabi/include/__cxxabi_config.h
+++ b/libcxxabi/include/__cxxabi_config.h
@@ -135,11 +135,15 @@
 #endif
 
 // On Apple, Clang generates calls to _tlv_atexit and __cxa_thread_atexit is defined
-// in libc instead. AIX uses a different mechanism for registering thread local
-// destructors (__pt_atexit_np).
+// in libc instead.
+//
+// AIX uses a different mechanism for registering thread local destructors (__pt_atexit_np).
+//
+// On Windows, Clang either doesn't use __cxa_thread_atexit at all (in the MSVC ABI),
+// or (on MinGW) the function is already provided by C runtime.
 //
 // On other platforms, libc++abi provides __cxa_thread_atexit.
-#if defined(__APPLE__) || defined(_AIX)
+#if defined(__APPLE__) || defined(_AIX) || defined(_WIN32)
 #  define _LIBCXXABI_DEFINE_THREAD_ATEXIT 0
 #else
 #  define _LIBCXXABI_DEFINE_THREAD_ATEXIT 1
diff --git a/libcxxabi/src/cxa_thread_atexit.cpp b/libcxxabi/src/cxa_thread_atexit.cpp
index 3ddca2ddfb229..366480f9437da 100644
--- a/libcxxabi/src/cxa_thread_atexit.cpp
+++ b/libcxxabi/src/cxa_thread_atexit.cpp
@@ -75,7 +75,9 @@ namespace {
   // Used to trigger destructors on thread exit; value is ignored
   std::__libcpp_tls_key dtors_key;
 
-  void run_dtors(void*) {
+  // The calling convention of TLS destructors is dictated by the underlying
+  // threading API.
+  void _LIBCPP_TLS_DESTRUCTOR_CC run_dtors(void*) {
     while (auto head = dtors) {
       dtors = head->next;
       head->dtor(head->obj);



More information about the libcxx-commits mailing list