[libc-commits] [libc] [llvm] [libc] Implement pthread_cleanup_push and pthread_cleanup_pop (PR #223427)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Mon Sep 21 07:49:18 PDT 2026


https://github.com/labath updated https://github.com/llvm/llvm-project/pull/223427

>From 1f5784917b4bf304067c27072b85d19c93b3b6ff Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Mon, 14 Sep 2026 12:00:59 +0000
Subject: [PATCH] [libc] Implement pthread_cleanup_push and pthread_cleanup_pop

This patch implements the core POSIX thread cleanup mechanism
(pthread_cleanup_push and pthread_cleanup_pop).

POSIX allows these functions to be implemented as macros opening
and closing a lexical block. We allocate an automatic
__pthread_cleanup_frame structure on the stack in the push macro and
unlink it in the pop macro, calling helper functions
(__pthread_cleanup_push and __pthread_cleanup_pop) to link/unlink
the frame on the thread's cleanup_stack.

The stack is drained in LIFO order during thread termination in
call_atexit_callbacks before running __cxa_thread_atexit and TSS key
destructors.

It's worth noting that GLIBC implements this functionality in a much
more complicated way. The cleanup functions are registered with the
unwinding machinery so that throwing an exception (or even just
returning) from the push/pop blocks triggers evaluation of the
cleanup handlers. This behavior is not required (though not forbidden
either) by POSIX, which states that all of these result in undefined
behavior.  The cleanup handlers are only run upon explicit
pthread_cleanup_pop(non_zero) or pthread_exit. POSIX also requires
running them on thread cancellation, but we don't implement
cancellation (yet).

*Another* thing worth noting is that this PR causes a couple of headers
to not compile in overlay mode (because I do not provide a definition of
__pthread_cleanup_frame in that case). These are threading headers which
are never supposed to be used in overlay mode (we can't overlay that),
and this is most visible in the bazel build, where I had to mark those
targets as incompatible. There are ways to work around this (the
simplest being providing a placeholder definition of the type for
overlay mode), but that's something I'd like to avoid if possible.

Assisted-by: Gemini
---
 libc/config/linux/aarch64/entrypoints.txt     |   2 +
 libc/config/linux/riscv/entrypoints.txt       |   2 +
 libc/config/linux/x86_64/entrypoints.txt      |   2 +
 libc/hdr/types/CMakeLists.txt                 |   8 +
 .../types/struct___pthread_cleanup_frame.h    |  27 ++
 libc/include/CMakeLists.txt                   |   1 +
 .../include/llvm-libc-macros/pthread-macros.h |  10 +
 libc/include/llvm-libc-types/CMakeLists.txt   |   1 +
 .../struct___pthread_cleanup_frame.h          |  23 ++
 libc/include/pthread.yaml                     |  21 ++
 libc/src/__support/threads/CMakeLists.txt     |  17 +-
 libc/src/__support/threads/cleanup_stack.h    |  45 +++
 libc/src/__support/threads/thread.cpp         |  15 +-
 .../src/__support/threads/thread_attributes.h |   2 +
 libc/src/pthread/CMakeLists.txt               |  28 ++
 libc/src/pthread/__pthread_cleanup_pop.cpp    |  31 +++
 libc/src/pthread/__pthread_cleanup_pop.h      |  25 ++
 libc/src/pthread/__pthread_cleanup_push.cpp   |  33 +++
 libc/src/pthread/__pthread_cleanup_push.h     |  27 ++
 .../integration/src/pthread/CMakeLists.txt    |  24 ++
 .../src/pthread/pthread_cleanup_test.cpp      | 261 ++++++++++++++++++
 libc/utils/docgen/pthread.yaml                |   8 +-
 .../llvm-project-overlay/libc/BUILD.bazel     |  38 ++-
 .../libc/libc_build_rules.bzl                 |   4 +
 .../libc/startup/linux/BUILD.bazel            |   4 -
 25 files changed, 645 insertions(+), 14 deletions(-)
 create mode 100644 libc/hdr/types/struct___pthread_cleanup_frame.h
 create mode 100644 libc/include/llvm-libc-types/struct___pthread_cleanup_frame.h
 create mode 100644 libc/src/__support/threads/cleanup_stack.h
 create mode 100644 libc/src/pthread/__pthread_cleanup_pop.cpp
 create mode 100644 libc/src/pthread/__pthread_cleanup_pop.h
 create mode 100644 libc/src/pthread/__pthread_cleanup_push.cpp
 create mode 100644 libc/src/pthread/__pthread_cleanup_push.h
 create mode 100644 libc/test/integration/src/pthread/pthread_cleanup_test.cpp

diff --git a/libc/config/linux/aarch64/entrypoints.txt b/libc/config/linux/aarch64/entrypoints.txt
index f55ac4b16cfd2..840defcd21563 100644
--- a/libc/config/linux/aarch64/entrypoints.txt
+++ b/libc/config/linux/aarch64/entrypoints.txt
@@ -1129,6 +1129,8 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.netinet.in6addr_loopback
 
     # pthread.h entrypoints
+    libc.src.pthread.__pthread_cleanup_pop
+    libc.src.pthread.__pthread_cleanup_push
     libc.src.pthread.pthread_atfork
     libc.src.pthread.pthread_attr_destroy
     libc.src.pthread.pthread_attr_getdetachstate
diff --git a/libc/config/linux/riscv/entrypoints.txt b/libc/config/linux/riscv/entrypoints.txt
index 921de1c649fb4..de7a8af9d9b8a 100644
--- a/libc/config/linux/riscv/entrypoints.txt
+++ b/libc/config/linux/riscv/entrypoints.txt
@@ -1317,6 +1317,8 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.dirent.fdopendir
 
     # pthread.h entrypoints
+    libc.src.pthread.__pthread_cleanup_pop
+    libc.src.pthread.__pthread_cleanup_push
     libc.src.pthread.pthread_atfork
     libc.src.pthread.pthread_attr_destroy
     libc.src.pthread.pthread_attr_getdetachstate
diff --git a/libc/config/linux/x86_64/entrypoints.txt b/libc/config/linux/x86_64/entrypoints.txt
index 165484987f16d..496530a90ec0b 100644
--- a/libc/config/linux/x86_64/entrypoints.txt
+++ b/libc/config/linux/x86_64/entrypoints.txt
@@ -1333,6 +1333,8 @@ if(LLVM_LIBC_FULL_BUILD)
     libc.src.dirent.fdopendir
 
     # pthread.h entrypoints
+    libc.src.pthread.__pthread_cleanup_pop
+    libc.src.pthread.__pthread_cleanup_push
     libc.src.pthread.pthread_atfork
     libc.src.pthread.pthread_attr_destroy
     libc.src.pthread.pthread_attr_getdetachstate
diff --git a/libc/hdr/types/CMakeLists.txt b/libc/hdr/types/CMakeLists.txt
index 6949e73a78daa..505c2d9fa6579 100644
--- a/libc/hdr/types/CMakeLists.txt
+++ b/libc/hdr/types/CMakeLists.txt
@@ -57,6 +57,14 @@ add_proxy_header_library(
     libc.include.llvm-libc-types.struct_link_map
 )
 
+add_proxy_header_library(
+  struct___pthread_cleanup_frame
+  HDRS
+    struct___pthread_cleanup_frame.h
+  FULL_BUILD_DEPENDS
+    libc.include.llvm-libc-types.struct___pthread_cleanup_frame
+)
+
 add_proxy_header_library(
   struct_r_debug
   HDRS
diff --git a/libc/hdr/types/struct___pthread_cleanup_frame.h b/libc/hdr/types/struct___pthread_cleanup_frame.h
new file mode 100644
index 0000000000000..c499344515714
--- /dev/null
+++ b/libc/hdr/types/struct___pthread_cleanup_frame.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Proxy header for struct __pthread_cleanup_frame.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_HDR_TYPES_STRUCT___PTHREAD_CLEANUP_FRAME_H
+#define LLVM_LIBC_HDR_TYPES_STRUCT___PTHREAD_CLEANUP_FRAME_H
+
+#ifdef LIBC_FULL_BUILD
+
+#include "include/llvm-libc-types/struct___pthread_cleanup_frame.h"
+
+#else // Overlay mode
+
+#error "pthread_cleanup functionality not available in overlay mode"
+
+#endif // LIBC_FULL_BUILD
+
+#endif // LLVM_LIBC_HDR_TYPES_STRUCT___PTHREAD_CLEANUP_FRAME_H
diff --git a/libc/include/CMakeLists.txt b/libc/include/CMakeLists.txt
index a45026de31ee5..f63e7e3dc6a73 100644
--- a/libc/include/CMakeLists.txt
+++ b/libc/include/CMakeLists.txt
@@ -584,6 +584,7 @@ add_header_macro(
     .llvm-libc-types.__pthread_once_func_t
     .llvm-libc-types.__pthread_start_t
     .llvm-libc-types.__pthread_tss_dtor_t
+    .llvm-libc-types.struct___pthread_cleanup_frame
     .llvm-libc-types.clockid_t
     .llvm-libc-types.pthread_attr_t
     .llvm-libc-types.pthread_cond_t
diff --git a/libc/include/llvm-libc-macros/pthread-macros.h b/libc/include/llvm-libc-macros/pthread-macros.h
index 5f96305468297..33d80291fb120 100644
--- a/libc/include/llvm-libc-macros/pthread-macros.h
+++ b/libc/include/llvm-libc-macros/pthread-macros.h
@@ -77,6 +77,16 @@
       /* .__write_tid = */ 0,                                                  \
   }
 
+#define pthread_cleanup_push(routine, arg)                                     \
+  do {                                                                         \
+    struct __pthread_cleanup_frame __cleanup_frame;                            \
+  __pthread_cleanup_push(&__cleanup_frame, (routine), (arg))
+
+#define pthread_cleanup_pop(execute)                                           \
+  __pthread_cleanup_pop((execute));                                            \
+  }                                                                            \
+  while (0)
+
 // glibc extensions
 #define PTHREAD_STACK_MIN (1 << 14) // 16KB
 #define PTHREAD_RWLOCK_PREFER_READER_NP 0
diff --git a/libc/include/llvm-libc-types/CMakeLists.txt b/libc/include/llvm-libc-types/CMakeLists.txt
index cc7b4df1202ff..ea710ef48857c 100644
--- a/libc/include/llvm-libc-types/CMakeLists.txt
+++ b/libc/include/llvm-libc-types/CMakeLists.txt
@@ -60,6 +60,7 @@ add_header(__barrier_type HDR __barrier_type.h)
 add_header(__pthread_once_func_t HDR __pthread_once_func_t.h)
 add_header(__pthread_start_t HDR __pthread_start_t.h)
 add_header(__pthread_tss_dtor_t HDR __pthread_tss_dtor_t.h)
+add_header(struct___pthread_cleanup_frame HDR struct___pthread_cleanup_frame.h)
 add_header(__qsortcompare_t HDR __qsortcompare_t.h)
 add_header(__qsortrcompare_t HDR __qsortrcompare_t.h)
 add_header(__thread_type HDR __thread_type.h)
diff --git a/libc/include/llvm-libc-types/struct___pthread_cleanup_frame.h b/libc/include/llvm-libc-types/struct___pthread_cleanup_frame.h
new file mode 100644
index 0000000000000..0623830f9b05a
--- /dev/null
+++ b/libc/include/llvm-libc-types/struct___pthread_cleanup_frame.h
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Definition of struct __pthread_cleanup_frame.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_TYPES_STRUCT___PTHREAD_CLEANUP_FRAME_H
+#define LLVM_LIBC_TYPES_STRUCT___PTHREAD_CLEANUP_FRAME_H
+
+struct __pthread_cleanup_frame {
+  void (*__routine)(void *);
+  void *__arg;
+  struct __pthread_cleanup_frame *__next;
+};
+
+#endif // LLVM_LIBC_TYPES_STRUCT___PTHREAD_CLEANUP_FRAME_H
diff --git a/libc/include/pthread.yaml b/libc/include/pthread.yaml
index 875dec99f7fdf..23f9dc4926e36 100644
--- a/libc/include/pthread.yaml
+++ b/libc/include/pthread.yaml
@@ -62,6 +62,10 @@ macros:
     standards:
       - llvm_libc_ext
     macro_header: pthread-macros.h
+  - macro_name: "pthread_cleanup_pop"
+    macro_header: pthread-macros.h
+  - macro_name: "pthread_cleanup_push"
+    macro_header: pthread-macros.h
 types:
   - type_name: pthread_t
   - type_name: pthread_once_t
@@ -79,7 +83,24 @@ types:
   - type_name: pthread_id_np_t
     standards:
       - llvm_libc_ext
+  - type_name: struct___pthread_cleanup_frame
+    standards:
+      - llvm_libc_ext
 functions:
+  - name: __pthread_cleanup_pop
+    standards:
+      - llvm_libc_ext
+    return_type: void
+    arguments:
+      - type: int
+  - name: __pthread_cleanup_push
+    standards:
+      - llvm_libc_ext
+    return_type: void
+    arguments:
+      - type: struct __pthread_cleanup_frame *
+      - type: void (*)(void *)
+      - type: void *
   - name: pthread_atfork
     return_type: int
     arguments:
diff --git a/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt
index dde3ee34f3a3a..57f11a849d3c1 100644
--- a/libc/src/__support/threads/CMakeLists.txt
+++ b/libc/src/__support/threads/CMakeLists.txt
@@ -19,11 +19,22 @@ add_header_library(
     libc.src.__support.CPP.atomic
 )
 
+add_header_library(
+  cleanup_stack
+  HDRS
+    cleanup_stack.h
+  DEPENDS
+    libc.hdr.types.struct___pthread_cleanup_frame
+    libc.src.__support.macros.attributes
+    libc.src.__support.macros.config
+)
+
 add_header_library(
   thread_attributes
   HDRS
     thread_attributes.h
   DEPENDS
+    .cleanup_stack
     libc.hdr.stdint_proxy
     libc.hdr.types.size_t
     libc.src.__support.CPP.atomic
@@ -153,14 +164,16 @@ if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.thread)
     SRCS
       thread.cpp
     DEPENDS
+      .cleanup_stack
       .mutex
       .${LIBC_TARGET_OS}.thread
-      libc.hdr.stdint_proxy
-      libc.src.__support.common
+      libc.hdr.types.struct___pthread_cleanup_frame
       libc.src.__support.fixedvector
       libc.src.__support.CPP.array
       libc.src.__support.CPP.mutex
       libc.src.__support.CPP.optional
+      libc.src.__support.macros.attributes
+      libc.src.__support.macros.config
   )
 endif()
 
diff --git a/libc/src/__support/threads/cleanup_stack.h b/libc/src/__support/threads/cleanup_stack.h
new file mode 100644
index 0000000000000..e2fecf648bb00
--- /dev/null
+++ b/libc/src/__support/threads/cleanup_stack.h
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Management of thread-specific cleanup handlers.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_CLEANUP_STACK_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_CLEANUP_STACK_H
+
+#include "hdr/types/struct___pthread_cleanup_frame.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+class CleanupStack {
+public:
+  LIBC_INLINE void push(__pthread_cleanup_frame *frame) {
+    frame->__next = top;
+    top = frame;
+  }
+
+  LIBC_INLINE __pthread_cleanup_frame *pop() {
+    __pthread_cleanup_frame *frame = top;
+    if (frame) {
+      top = frame->__next;
+      frame->__next = nullptr;
+    }
+    return frame;
+  }
+
+private:
+  __pthread_cleanup_frame *top = nullptr;
+};
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_CLEANUP_STACK_H
diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp
index abcf7e399c16e..bd090449dbd29 100644
--- a/libc/src/__support/threads/thread.cpp
+++ b/libc/src/__support/threads/thread.cpp
@@ -7,14 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "src/__support/threads/thread.h"
-#include "src/__support/macros/config.h"
-#include "src/__support/threads/mutex.h"
-
+#include "hdr/types/struct___pthread_cleanup_frame.h"
 #include "src/__support/CPP/array.h"
 #include "src/__support/CPP/mutex.h" // lock_guard
 #include "src/__support/CPP/optional.h"
 #include "src/__support/fixedvector.h"
 #include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/cleanup_stack.h"
+#include "src/__support/threads/mutex.h"
 
 namespace LIBC_NAMESPACE_DECL {
 namespace {
@@ -151,7 +152,15 @@ extern "C" int __cxa_thread_atexit_impl(AtExitCallback *callback, void *obj,
 namespace internal {
 
 void call_atexit_callbacks() {
+  // Cancellation cleanup handlers (pthread_cleanup_push).
+  __pthread_cleanup_frame *frame;
+  while ((frame = current_thread().attrib->cleanup_stack.pop()) != nullptr)
+    frame->__routine(frame->__arg);
+
+  // thread exit callbacks (__cxa_thread_atexit).
   atexit_callback_mgr.call();
+
+  // Thread-specific keys (pthread_key_create).
   for (size_t i = 0; i < TSS_KEY_COUNT; ++i) {
     TSSValueUnit &unit = tss_values[i];
     // Both dtor and value need to nonnull to call dtor
diff --git a/libc/src/__support/threads/thread_attributes.h b/libc/src/__support/threads/thread_attributes.h
index 123b0d2a6c8ce..bffdbc616ccb3 100644
--- a/libc/src/__support/threads/thread_attributes.h
+++ b/libc/src/__support/threads/thread_attributes.h
@@ -20,6 +20,7 @@
 #include "src/__support/macros/attributes.h"
 #include "src/__support/macros/config.h"
 #include "src/__support/macros/properties/architectures.h"
+#include "src/__support/threads/cleanup_stack.h"
 
 namespace LIBC_NAMESPACE_DECL {
 
@@ -105,6 +106,7 @@ struct alignas(STACK_ALIGNMENT) ThreadAttributes {
   ThreadReturnValue retval;
   void *platform_data;
   cpp::Atomic<ThreadAttributes *> joiner;
+  CleanupStack cleanup_stack;
 
   LIBC_INLINE constexpr ThreadAttributes()
       : detach_state(uint32_t(DetachState::DETACHED)), stack(nullptr),
diff --git a/libc/src/pthread/CMakeLists.txt b/libc/src/pthread/CMakeLists.txt
index cfbcb152ecc48..3e034760d4838 100644
--- a/libc/src/pthread/CMakeLists.txt
+++ b/libc/src/pthread/CMakeLists.txt
@@ -645,6 +645,34 @@ add_entrypoint_object(
     libc.src.__support.threads.thread
 )
 
+add_entrypoint_object(
+  __pthread_cleanup_pop
+  SRCS
+    __pthread_cleanup_pop.cpp
+  HDRS
+    __pthread_cleanup_pop.h
+  DEPENDS
+    libc.hdr.types.struct___pthread_cleanup_frame
+    libc.src.__support.common
+    libc.src.__support.libc_assert
+    libc.src.__support.macros.config
+    libc.src.__support.threads.thread
+)
+
+add_entrypoint_object(
+  __pthread_cleanup_push
+  SRCS
+    __pthread_cleanup_push.cpp
+  HDRS
+    __pthread_cleanup_push.h
+  DEPENDS
+    libc.hdr.types.struct___pthread_cleanup_frame
+    libc.src.__support.common
+    libc.src.__support.macros.config
+    libc.src.__support.macros.null_check
+    libc.src.__support.threads.thread
+)
+
 add_entrypoint_object(
   pthread_exit
   SRCS
diff --git a/libc/src/pthread/__pthread_cleanup_pop.cpp b/libc/src/pthread/__pthread_cleanup_pop.cpp
new file mode 100644
index 0000000000000..67e3f43cca200
--- /dev/null
+++ b/libc/src/pthread/__pthread_cleanup_pop.cpp
@@ -0,0 +1,31 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of __pthread_cleanup_pop.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/__pthread_cleanup_pop.h"
+#include "hdr/types/struct___pthread_cleanup_frame.h"
+#include "src/__support/common.h"
+#include "src/__support/libc_assert.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/thread.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, __pthread_cleanup_pop, (int execute)) {
+  __pthread_cleanup_frame *frame = current_thread().attrib->cleanup_stack.pop();
+  LIBC_ASSERT(frame);
+
+  if (execute)
+    frame->__routine(frame->__arg);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pthread/__pthread_cleanup_pop.h b/libc/src/pthread/__pthread_cleanup_pop.h
new file mode 100644
index 0000000000000..bf18ea7959f4c
--- /dev/null
+++ b/libc/src/pthread/__pthread_cleanup_pop.h
@@ -0,0 +1,25 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for __pthread_cleanup_pop.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD___PTHREAD_CLEANUP_POP_H
+#define LLVM_LIBC_SRC_PTHREAD___PTHREAD_CLEANUP_POP_H
+
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void __pthread_cleanup_pop(int execute);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PTHREAD___PTHREAD_CLEANUP_POP_H
diff --git a/libc/src/pthread/__pthread_cleanup_push.cpp b/libc/src/pthread/__pthread_cleanup_push.cpp
new file mode 100644
index 0000000000000..1e658b7bcfd7b
--- /dev/null
+++ b/libc/src/pthread/__pthread_cleanup_push.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation of __pthread_cleanup_push.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/__pthread_cleanup_push.h"
+#include "hdr/types/struct___pthread_cleanup_frame.h"
+#include "src/__support/common.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/macros/null_check.h"
+#include "src/__support/threads/thread.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+LLVM_LIBC_FUNCTION(void, __pthread_cleanup_push,
+                   (struct __pthread_cleanup_frame * frame,
+                    void (*routine)(void *), void *arg)) {
+  LIBC_CRASH_ON_NULLPTR(frame);
+  LIBC_CRASH_ON_NULLPTR(routine);
+  frame->__routine = routine;
+  frame->__arg = arg;
+  current_thread().attrib->cleanup_stack.push(frame);
+}
+
+} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/pthread/__pthread_cleanup_push.h b/libc/src/pthread/__pthread_cleanup_push.h
new file mode 100644
index 0000000000000..dffe69b75e039
--- /dev/null
+++ b/libc/src/pthread/__pthread_cleanup_push.h
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Implementation header for __pthread_cleanup_push.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_PTHREAD___PTHREAD_CLEANUP_PUSH_H
+#define LLVM_LIBC_SRC_PTHREAD___PTHREAD_CLEANUP_PUSH_H
+
+#include "hdr/types/struct___pthread_cleanup_frame.h"
+#include "src/__support/macros/config.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+void __pthread_cleanup_push(struct __pthread_cleanup_frame *frame,
+                            void (*routine)(void *), void *arg);
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_PTHREAD___PTHREAD_CLEANUP_PUSH_H
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt
index abeadc4be1c9a..66721f8a67358 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -419,3 +419,27 @@ add_integration_test(
     libc.src.signal.sigaddset
     libc.src.signal.sigemptyset
 )
+
+add_integration_test(
+  pthread_cleanup_test
+  SUITE
+    libc-pthread-integration-tests
+  SRCS
+    pthread_cleanup_test.cpp
+  DEPENDS
+    libc.hdr.pthread_macros
+    libc.src.pthread.pthread_create
+    libc.src.pthread.pthread_join
+    libc.src.pthread.pthread_exit
+    libc.src.pthread.__pthread_cleanup_push
+    libc.src.pthread.__pthread_cleanup_pop
+    libc.src.pthread.pthread_key_create
+    libc.src.pthread.pthread_key_delete
+    libc.src.pthread.pthread_setspecific
+    libc.src.pthread.pthread_getspecific
+    libc.src.pthread.pthread_mutex_destroy
+    libc.src.pthread.pthread_mutex_init
+    libc.src.pthread.pthread_mutex_lock
+    libc.src.pthread.pthread_mutex_trylock
+    libc.src.pthread.pthread_mutex_unlock
+)
diff --git a/libc/test/integration/src/pthread/pthread_cleanup_test.cpp b/libc/test/integration/src/pthread/pthread_cleanup_test.cpp
new file mode 100644
index 0000000000000..9fd43201d8cf2
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_cleanup_test.cpp
@@ -0,0 +1,261 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// Tests for pthread_cleanup_push and pthread_cleanup_pop.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/__pthread_cleanup_pop.h"
+#include "src/pthread/__pthread_cleanup_push.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_exit.h"
+#include "src/pthread/pthread_getspecific.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_key_create.h"
+#include "src/pthread/pthread_key_delete.h"
+#include "src/pthread/pthread_mutex_destroy.h"
+#include "src/pthread/pthread_mutex_init.h"
+#include "src/pthread/pthread_mutex_lock.h"
+#include "src/pthread/pthread_mutex_trylock.h"
+#include "src/pthread/pthread_mutex_unlock.h"
+#include "src/pthread/pthread_setspecific.h"
+#include "test/IntegrationTest/test.h"
+
+#include "hdr/pthread_macros.h"
+
+// To make pthread_cleanup_push/pop work outside of LIBC_NAMESPACE
+#define __pthread_cleanup_push LIBC_NAMESPACE::__pthread_cleanup_push
+#define __pthread_cleanup_pop LIBC_NAMESPACE::__pthread_cleanup_pop
+
+static int call_order = 0;
+static void record_routine(void *arg) {
+  *reinterpret_cast<int *>(arg) = ++call_order;
+}
+
+// 1. Test direct pop with execute = 0 and execute = 1 on the main thread.
+static void test_direct_pop() {
+  int val = 0;
+  call_order = 0;
+  pthread_cleanup_push(record_routine, &val);
+  pthread_cleanup_pop(0);
+  ASSERT_EQ(val, 0);
+
+  pthread_cleanup_push(record_routine, &val);
+  pthread_cleanup_pop(1);
+  ASSERT_EQ(val, 1);
+}
+
+// 2. Test nested push / pop blocks in LIFO order on the main thread.
+static void test_nested_pop() {
+  int order[2] = {0, 0};
+  call_order = 0;
+
+  pthread_cleanup_push(record_routine, &order[1]);
+  pthread_cleanup_push(record_routine, &order[0]);
+  pthread_cleanup_pop(1);
+  pthread_cleanup_pop(1);
+
+  ASSERT_EQ(order[0], 1);
+  ASSERT_EQ(order[1], 2);
+}
+
+// 3. Test sequential push / pop blocks in the same scope.
+static void test_sequential_pop() {
+  int first = 0;
+  int second = 0;
+  call_order = 0;
+
+  pthread_cleanup_push(record_routine, &first);
+  pthread_cleanup_pop(1);
+  pthread_cleanup_push(record_routine, &second);
+  pthread_cleanup_pop(1);
+
+  ASSERT_EQ(first, 1);
+  ASSERT_EQ(second, 2);
+}
+
+// 4. Test pthread_exit running handlers in LIFO order.
+static int exit_order[3] = {0, 0, 0};
+
+static void *thread_exit_lifo_func(void *) {
+  pthread_cleanup_push(record_routine, &exit_order[2]);
+  pthread_cleanup_push(record_routine, &exit_order[1]);
+  pthread_cleanup_push(record_routine, &exit_order[0]);
+
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  pthread_cleanup_pop(0);
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_exit_lifo() {
+  pthread_t th;
+  void *retval = nullptr;
+
+  call_order = 0;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_exit_lifo_func,
+                                           nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(exit_order[0], 1);
+  ASSERT_EQ(exit_order[1], 2);
+  ASSERT_EQ(exit_order[2], 3);
+}
+
+// 5. Test that handlers popped prior to pthread_exit are not executed.
+static int exit_called1 = 0;
+static int exit_called2 = 0;
+
+static void *thread_popped_func(void *) {
+  pthread_cleanup_push(record_routine, &exit_called1);
+  pthread_cleanup_push(record_routine, &exit_called2);
+  pthread_cleanup_pop(0); // Pop handler2 without executing
+
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_exit_popped_not_called() {
+  pthread_t th;
+  void *retval = nullptr;
+
+  call_order = 0;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_popped_func, nullptr),
+      0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(exit_called1, 1);
+  ASSERT_EQ(exit_called2, 0);
+}
+
+// 6. Test that cleanup handlers run before TSS destructors.
+static pthread_key_t tss_key;
+static int global_sequence = 0;
+static int cleanup_sequence = 0;
+static int tss_sequence = 0;
+static int dummy_tss_val = 99;
+
+static void tss_destructor(void *) { tss_sequence = ++global_sequence; }
+
+static void tss_cleanup_handler(void *) {
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_getspecific(tss_key), &dummy_tss_val);
+  cleanup_sequence = ++global_sequence;
+}
+
+static void *thread_tss_order_func(void *) {
+  LIBC_NAMESPACE::pthread_setspecific(tss_key, &dummy_tss_val);
+  pthread_cleanup_push(tss_cleanup_handler, nullptr);
+
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_exit_order_before_tss() {
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_create(&tss_key, tss_destructor), 0);
+
+  pthread_t th;
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_tss_order_func,
+                                           nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_EQ(cleanup_sequence, 1);
+  ASSERT_EQ(tss_sequence, 2);
+
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_key_delete(tss_key), 0);
+}
+
+// 7. Test re-entrancy: a cleanup handler pushes and pops an inner handler.
+static bool outer_called = false;
+static bool inner_called = false;
+
+static void inner_handler(void *) { inner_called = true; }
+
+static void outer_handler(void *) {
+  outer_called = true;
+  pthread_cleanup_push(inner_handler, nullptr);
+  pthread_cleanup_pop(1);
+}
+
+static void *thread_reentrancy_func(void *) {
+  pthread_cleanup_push(outer_handler, nullptr);
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_reentrancy() {
+  pthread_t th;
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr, thread_reentrancy_func,
+                                           nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  ASSERT_TRUE(outer_called);
+  ASSERT_TRUE(inner_called);
+}
+
+// 8. Test releasing a mutex in a cleanup function.
+static pthread_mutex_t test_mutex;
+
+static void mutex_cleanup_routine(void *arg) {
+  auto *mutex = reinterpret_cast<pthread_mutex_t *>(arg);
+  LIBC_NAMESPACE::pthread_mutex_unlock(mutex);
+}
+
+static void *thread_mutex_cleanup_func(void *) {
+  LIBC_NAMESPACE::pthread_mutex_lock(&test_mutex);
+  pthread_cleanup_push(mutex_cleanup_routine, &test_mutex);
+
+  // Thread exits while holding the mutex. The cleanup handler should unlock it.
+  LIBC_NAMESPACE::pthread_exit(nullptr);
+
+  pthread_cleanup_pop(0);
+  return nullptr;
+}
+
+static void test_mutex_cleanup() {
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_init(&test_mutex, nullptr), 0);
+
+  pthread_t th;
+  void *retval = nullptr;
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&th, nullptr,
+                                           thread_mutex_cleanup_func, nullptr),
+            0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th, &retval), 0);
+
+  // Since the cleanup handler released the mutex, we should now be able to
+  // lock it without blocking.
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_trylock(&test_mutex), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_unlock(&test_mutex), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_mutex_destroy(&test_mutex), 0);
+}
+
+TEST_MAIN() {
+  test_direct_pop();
+  test_nested_pop();
+  test_sequential_pop();
+  test_exit_lifo();
+  test_exit_popped_not_called();
+  test_exit_order_before_tss();
+  test_reentrancy();
+  test_mutex_cleanup();
+  return 0;
+}
diff --git a/libc/utils/docgen/pthread.yaml b/libc/utils/docgen/pthread.yaml
index 0f8af6baacc03..469f5407972c5 100644
--- a/libc/utils/docgen/pthread.yaml
+++ b/libc/utils/docgen/pthread.yaml
@@ -53,10 +53,6 @@ functions:
     in-latest-posix: ''
   pthread_cancel:
     in-latest-posix: ''
-  pthread_cleanup_pop:
-    in-latest-posix: ''
-  pthread_cleanup_push:
-    in-latest-posix: ''
   pthread_cond_broadcast:
     in-latest-posix: ''
   pthread_cond_clockwait:
@@ -260,3 +256,7 @@ macros:
     in-latest-posix: ''
   PTHREAD_SCOPE_SYSTEM:
     in-latest-posix: ''
+  pthread_cleanup_pop:
+    in-latest-posix: ''
+  pthread_cleanup_push:
+    in-latest-posix: ''
diff --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index b3f854ad06a7d..4b8ddfe1ec428 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -572,6 +572,11 @@ libc_support_library(
     ],
 )
 
+libc_support_library(
+    name = "llvm_libc_types_struct___pthread_cleanup_frame",
+    hdrs = ["include/llvm-libc-types/struct___pthread_cleanup_frame.h"],
+)
+
 libc_support_library(
     name = "llvm_libc_macros_fcntl_macros",
     hdrs = ["include/llvm-libc-macros/linux/fcntl-macros.h"],
@@ -1441,6 +1446,16 @@ libc_support_library(
     deps = [":llvm_libc_types_struct_cmsghdr"],
 )
 
+libc_support_library(
+    name = "types_struct___pthread_cleanup_frame",
+    hdrs = ["hdr/types/struct___pthread_cleanup_frame.h"],
+    target_compatible_with = select({
+        ":full_build_linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    deps = [":llvm_libc_types_struct___pthread_cleanup_frame"],
+)
+
 libc_support_library(
     name = "types_wchar_t",
     hdrs = ["hdr/types/wchar_t.h"],
@@ -4221,6 +4236,10 @@ libc_support_library(
 libc_support_library(
     name = "__support_threads_thread_attributes",
     hdrs = ["src/__support/threads/thread_attributes.h"],
+    target_compatible_with = select({
+        ":full_build_linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     deps = [
         ":__support_common",
         ":__support_cpp_atomic",
@@ -4229,6 +4248,7 @@ libc_support_library(
         ":__support_macros_properties_architectures",
         ":hdr_stdint_proxy",
         ":types_size_t",
+        ":types_struct___pthread_cleanup_frame",
     ],
 )
 
@@ -4372,6 +4392,10 @@ libc_support_library(
 libc_support_library(
     name = "__support_threads_tcb",
     hdrs = ["src/__support/threads/tcb.h"],
+    target_compatible_with = select({
+        ":full_build_linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     textual_hdrs = select({
         "@platforms//os:linux": [
             "src/__support/threads/linux/tcb.h",
@@ -4395,6 +4419,10 @@ libc_support_library(
 libc_support_library(
     name = "__support_threads_thread_headers",
     hdrs = ["src/__support/threads/thread.h"],
+    target_compatible_with = select({
+        ":full_build_linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     deps = [
         ":__support_cpp_atomic",
         ":__support_cpp_optional",
@@ -4414,6 +4442,10 @@ libc_support_library(
 libc_support_library(
     name = "__support_threads_thread",
     srcs = ["src/__support/threads/thread.cpp"],
+    target_compatible_with = select({
+        ":full_build_linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     deps = [
         ":__support_cpp_array",
         ":__support_cpp_mutex",
@@ -4434,7 +4466,7 @@ libc_support_library(
     srcs = ["src/__support/threads/linux/thread.cpp"],
     copt_sets = ["threading"],
     target_compatible_with = select({
-        "@platforms//os:linux": [],
+        ":full_build_linux": [],
         "//conditions:default": ["@platforms//:incompatible"],
     }),
     deps = [
@@ -16679,6 +16711,10 @@ libc_function(
     name = "fork",
     srcs = ["src/unistd/linux/fork.cpp"],
     hdrs = ["src/unistd/fork.h"],
+    target_compatible_with = select({
+        ":full_build_linux": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
     deps = [
         ":__support_common",
         ":__support_libc_errno",
diff --git a/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl b/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl
index 3b01e668b4aa2..4412ccc81dfed 100644
--- a/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl
+++ b/utils/bazel/llvm-project-overlay/libc/libc_build_rules.bzl
@@ -138,6 +138,10 @@ def libc_startup_library(name, **kwargs):
     _libc_library(
         name = name,
         copt_sets = ["startup_object"],
+        target_compatible_with = select({
+            Label("//libc:full_build_linux"): [],
+            "//conditions:default": ["@platforms//:incompatible"],
+        }),
         **kwargs
     )
 
diff --git a/utils/bazel/llvm-project-overlay/libc/startup/linux/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/startup/linux/BUILD.bazel
index df21694c5a0ef..e8f76f330f49b 100644
--- a/utils/bazel/llvm-project-overlay/libc/startup/linux/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/startup/linux/BUILD.bazel
@@ -48,10 +48,6 @@ libc_startup_library(
     hdrs = [
         "do_start.h",
     ],
-    target_compatible_with = select({
-        "@platforms//os:linux": [],
-        "//conditions:default": ["@platforms//:incompatible"],
-    }),
     deps = [
         ":gnu_property_section",
         ":irelative_hdr",



More information about the libc-commits mailing list