[libc-commits] [libc] 7164104 - [libc] ensure tls dtors are called in main thread (#133641)

via libc-commits libc-commits at lists.llvm.org
Wed Sep 3 06:02:09 PDT 2025


Author: Schrodinger ZHU Yifan
Date: 2025-09-03T09:02:03-04:00
New Revision: 71641049a91253f7547f792ec2fcb6609794ea4f

URL: https://github.com/llvm/llvm-project/commit/71641049a91253f7547f792ec2fcb6609794ea4f
DIFF: https://github.com/llvm/llvm-project/commit/71641049a91253f7547f792ec2fcb6609794ea4f.diff

LOG: [libc] ensure tls dtors are called in main thread (#133641)

This is a part of allocator patch since I want to make sure the TLS for
allocators are correctly handled.

TLS dtors are not invoked on exit previously. This departures from major
libc implementations.

This PR fixes the issue by aligning the behavior with bionic.


https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/bionic/exit.cpp

I believe the finalization order is also consistent with glibc now:

On main thread exiting:

- pthread_key dtors are not called (unless exiting with `pthread_exit`)
- `__cxa` based tls dtors are called
- `::__cxa_atexit` and `::atexit` dtors are called
- `.fini` dtors are called


![image](https://github.com/user-attachments/assets/737c4845-cab6-47a9-aa00-32997be141bd)

Added: 
    libc/test/integration/src/__support/threads/double_exit_test.cpp
    libc/test/integration/src/__support/threads/main_exit_test.cpp

Modified: 
    libc/src/__support/threads/thread.cpp
    libc/src/__support/threads/thread.h
    libc/src/stdlib/exit.cpp
    libc/test/integration/src/__support/threads/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp
index 6f6b75be5766d..9618d7829161a 100644
--- a/libc/src/__support/threads/thread.cpp
+++ b/libc/src/__support/threads/thread.cpp
@@ -163,6 +163,8 @@ void call_atexit_callbacks(ThreadAttributes *attrib) {
   }
 }
 
+extern "C" void __cxa_thread_finalize() { call_atexit_callbacks(self.attrib); }
+
 } // namespace internal
 
 cpp::optional<unsigned int> new_tss_key(TSSDtor *dtor) {

diff  --git a/libc/src/__support/threads/thread.h b/libc/src/__support/threads/thread.h
index 114ab4932af72..6806098653b2c 100644
--- a/libc/src/__support/threads/thread.h
+++ b/libc/src/__support/threads/thread.h
@@ -110,7 +110,7 @@ struct alignas(STACK_ALIGNMENT) ThreadAttributes {
   ThreadAtExitCallbackMgr *atexit_callback_mgr;
   void *platform_data;
 
-  constexpr ThreadAttributes()
+  LIBC_INLINE constexpr ThreadAttributes()
       : detach_state(uint32_t(DetachState::DETACHED)), stack(nullptr),
         stacksize(0), guardsize(0), tls(0), tls_size(0), owned_stack(false),
         tid(-1), style(ThreadStyle::POSIX), retval(),

diff  --git a/libc/src/stdlib/exit.cpp b/libc/src/stdlib/exit.cpp
index 28a6f8a63c0c6..ef3b8dd246305 100644
--- a/libc/src/stdlib/exit.cpp
+++ b/libc/src/stdlib/exit.cpp
@@ -15,7 +15,17 @@ namespace LIBC_NAMESPACE_DECL {
 
 extern "C" void __cxa_finalize(void *);
 
+// exit needs to clean up TLS and call associated destructors.
+// TODO: Strictly speaking, it is not valid to call exit in overlay mode
+//       as we have no way to ensure system libc will call the TLS destructors.
+//       We should run exit related tests in hermetic mode but this is currently
+//       blocked by https://github.com/llvm/llvm-project/issues/133925.
+extern "C" [[gnu::weak]] void __cxa_thread_finalize();
+
+// TODO: use recursive mutex to protect this routine.
 [[noreturn]] LLVM_LIBC_FUNCTION(void, exit, (int status)) {
+  if (__cxa_thread_finalize)
+    __cxa_thread_finalize();
   __cxa_finalize(nullptr);
   internal::exit(status);
 }

diff  --git a/libc/test/integration/src/__support/threads/CMakeLists.txt b/libc/test/integration/src/__support/threads/CMakeLists.txt
index 5a12d28ada3fd..40e96681b1207 100644
--- a/libc/test/integration/src/__support/threads/CMakeLists.txt
+++ b/libc/test/integration/src/__support/threads/CMakeLists.txt
@@ -25,3 +25,24 @@ add_integration_test(
   DEPENDS
     libc.src.__support.threads.thread
 )
+
+add_integration_test(
+  main_exit_test
+  SUITE
+    libc-support-threads-integration-tests
+  SRCS
+    main_exit_test.cpp
+  DEPENDS
+    libc.src.__support.threads.thread
+)
+
+add_integration_test(
+  double_exit_test
+  SUITE
+    libc-support-threads-integration-tests
+  SRCS
+    double_exit_test.cpp
+  DEPENDS
+    libc.src.__support.threads.thread
+    libc.src.stdlib.exit
+)

diff  --git a/libc/test/integration/src/__support/threads/double_exit_test.cpp b/libc/test/integration/src/__support/threads/double_exit_test.cpp
new file mode 100644
index 0000000000000..86749d937c10c
--- /dev/null
+++ b/libc/test/integration/src/__support/threads/double_exit_test.cpp
@@ -0,0 +1,33 @@
+//===-- Test handling of thread local data --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/threads/thread.h"
+#include "src/stdlib/exit.h"
+#include "test/IntegrationTest/test.h"
+
+extern "C" {
+[[gnu::weak]]
+void *__dso_handle = nullptr;
+int __cxa_thread_atexit_impl(void (*func)(void *), void *arg, void *dso);
+}
+
+int call_num = 0;
+
+[[gnu::destructor]]
+void check() {
+  // This destructor should be called only once.
+  if (call_num != 1)
+    __builtin_trap();
+}
+
+TEST_MAIN() {
+  __cxa_thread_atexit_impl([](void *) { LIBC_NAMESPACE::exit(0); }, nullptr,
+                           __dso_handle);
+  __cxa_thread_atexit_impl([](void *) { ++call_num; }, nullptr, __dso_handle);
+  LIBC_NAMESPACE::exit(1);
+}

diff  --git a/libc/test/integration/src/__support/threads/main_exit_test.cpp b/libc/test/integration/src/__support/threads/main_exit_test.cpp
new file mode 100644
index 0000000000000..c90e4e569cfba
--- /dev/null
+++ b/libc/test/integration/src/__support/threads/main_exit_test.cpp
@@ -0,0 +1,30 @@
+//===-- Test handling of thread local data --------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "src/__support/threads/thread.h"
+#include "test/IntegrationTest/test.h"
+
+bool called = false;
+
+extern "C" {
+[[gnu::weak]]
+void *__dso_handle = nullptr;
+int __cxa_thread_atexit_impl(void (*func)(void *), void *arg, void *dso);
+}
+
+[[gnu::destructor]]
+void destructor() {
+  if (!called)
+    __builtin_trap();
+}
+
+TEST_MAIN() {
+  __cxa_thread_atexit_impl([](void *) { called = true; }, nullptr,
+                           __dso_handle);
+  return 0;
+}


        


More information about the libc-commits mailing list