[libc-commits] [libc] [libc] Make it possible to join the main thread (PR #221177)
via libc-commits
libc-commits at lists.llvm.org
Fri Sep 4 02:22:53 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libc
Author: Pavel Labath (labath)
<details>
<summary>Changes</summary>
Joining the main thread currently crashes because its ThreadAttributes has a null platform_data pointer, causing Thread::wait() to dereference a null futex.
While this is not a frequently used feature, it is supported by other implementations, and I believe it is required by POSIX (the exec page says that the main thread is created in a joinable state, and neither pthread_join nor pthread_exit mention them not working on the main thread).
This patch sets up the main thread attributes during startup:
- allocate a static futex for the main thread's clear_tid and point platform_data to it
- invoke set_tid_address to have the kernel clear the futex and wake waiters on thread termination
- mark the main thread as joinable (it was previously defaulting to detached)
The test for this functionality exposed the bug where the thread return value is not preserved (and therefore not passed to pthread_join) for threads exiting via pthread_exit, so I fix that as well.
This patch required accessing the CLEAR_TID_VALUE constant from multiple files, but I couldn't find a suitable header to place it in. Instead of creating a new one, I just delete the constant, as we don't really care about its value -- only that it is not zero.
Assisted-by: Gemini
---
Full diff: https://github.com/llvm/llvm-project/pull/221177.diff
5 Files Affected:
- (modified) libc/src/__support/threads/linux/thread.cpp (+11-12)
- (modified) libc/startup/linux/CMakeLists.txt (+1)
- (modified) libc/startup/linux/do_start.cpp (+6)
- (modified) libc/test/integration/src/pthread/CMakeLists.txt (+15)
- (added) libc/test/integration/src/pthread/pthread_join_main_test.cpp (+46)
``````````diff
diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index 231b7cf2903ca..7e91125823c24 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -27,6 +27,8 @@
#include "src/__support/libc_errno.h" // For error macros
#include "src/__support/macros/config.h"
#include "src/__support/threads/linux/futex_utils.h" // For FutexWordType
+#include "src/__support/threads/linux/futex_word.h"
+#include "src/__support/threads/thread_attributes.h"
#ifdef LIBC_TARGET_ARCH_IS_AARCH64
#include <arm_acle.h>
@@ -44,7 +46,6 @@
namespace LIBC_NAMESPACE_DECL {
static constexpr size_t NAME_SIZE_MAX = 16; // Includes the null terminator
-static constexpr uint32_t CLEAR_TID_VALUE = 0xABCD1234;
static constexpr unsigned CLONE_SYSCALL_FLAGS =
CLONE_VM // Share the memory space with the parent.
| CLONE_FS // Share the file system with the parent.
@@ -184,15 +185,11 @@ cleanup_thread_resources(ThreadAttributes *attrib) {
attrib->atexit_callback_mgr = internal::get_thread_atexit_callback_mgr();
if (attrib->style == ThreadStyle::POSIX) {
- attrib->retval.posix_retval =
- start_args->runner.posix_runner(start_args->arg);
- thread_exit(ThreadReturnValue(attrib->retval.posix_retval),
- ThreadStyle::POSIX);
+ ThreadReturnValue retval = start_args->runner.posix_runner(start_args->arg);
+ thread_exit(retval, ThreadStyle::POSIX);
} else {
- attrib->retval.stdc_retval =
- start_args->runner.stdc_runner(start_args->arg);
- thread_exit(ThreadReturnValue(attrib->retval.stdc_retval),
- ThreadStyle::STDC);
+ ThreadReturnValue retval = start_args->runner.stdc_runner(start_args->arg);
+ thread_exit(retval, ThreadStyle::STDC);
}
}
@@ -291,7 +288,7 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
auto clear_tid = reinterpret_cast<Futex *>(
adjusted_stack + sizeof(StartArgs) + sizeof(ThreadAttributes));
- clear_tid->set(CLEAR_TID_VALUE);
+ clear_tid->set(1);
attrib->platform_data = clear_tid;
// The clone syscall takes arguments in an architecture specific order.
@@ -399,8 +396,9 @@ void Thread::wait() {
auto *clear_tid = reinterpret_cast<Futex *>(attrib->platform_data);
// We cannot do a FUTEX_WAIT_PRIVATE here as the kernel does a
// FUTEX_WAKE and not a FUTEX_WAKE_PRIVATE.
- while (clear_tid->load() != 0)
- clear_tid->wait(CLEAR_TID_VALUE, cpp::nullopt, true);
+ FutexWordType clear_tid_value;
+ while ((clear_tid_value = clear_tid->load()) != 0)
+ clear_tid->wait(clear_tid_value, cpp::nullopt, true);
}
bool Thread::operator==(const Thread &thread) const {
@@ -517,6 +515,7 @@ ErrorOr<SchedParameters> Thread::getschedparam() const {
void thread_exit(ThreadReturnValue retval, ThreadStyle style) {
auto attrib = current_thread().attrib;
+ attrib->retval = retval;
// The very first thing we do is to call the thread's atexit callbacks.
// These callbacks could be the ones registered by the language runtimes,
diff --git a/libc/startup/linux/CMakeLists.txt b/libc/startup/linux/CMakeLists.txt
index 11aa97ef97c40..e6568324a6c78 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -122,6 +122,7 @@ add_object_library(
libc.src.__support.OSUtil.linux.auxv
libc.src.__support.OSUtil.osutil
libc.src.__support.threads.thread
+ libc.src.__support.threads.linux.futex_utils
libc.src.__support.macros.config
libc.startup.linux.${LIBC_TARGET_ARCHITECTURE}.irelative
libc.src.link._r_debug
diff --git a/libc/startup/linux/do_start.cpp b/libc/startup/linux/do_start.cpp
index 410cbe8e12a48..5ff5e73c4649a 100644
--- a/libc/startup/linux/do_start.cpp
+++ b/libc/startup/linux/do_start.cpp
@@ -18,6 +18,7 @@
#include "src/__support/OSUtil/linux/auxv.h"
#include "src/__support/OSUtil/syscall.h"
#include "src/__support/macros/config.h"
+#include "src/__support/threads/linux/futex_utils.h"
#include "src/__support/threads/thread.h"
#include "src/errno/program_invocation_name.h"
#include "src/errno/program_invocation_short_name.h"
@@ -76,6 +77,7 @@ static void call_fini_array_callbacks() {
}
static ThreadAttributes main_thread_attrib;
+static Futex main_thread_clear_tid(1);
static TLSDescriptor tls;
[[noreturn]] void do_start() {
@@ -83,6 +85,10 @@ static TLSDescriptor tls;
if (tid <= 0)
syscall_impl<long>(SYS_exit, 1);
main_thread_attrib.tid = static_cast<int>(tid);
+ main_thread_attrib.platform_data = &main_thread_clear_tid;
+ main_thread_attrib.detach_state =
+ static_cast<uint32_t>(DetachState::JOINABLE);
+ syscall_impl<long>(SYS_set_tid_address, &main_thread_clear_tid.val);
// After the argv array, is a 8-byte long NULL value before the array of env
// values. The end of the env values is marked by another 8-byte long NULL
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt
index 19d02bbc3f0c4..ced0782736ef8 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -259,6 +259,21 @@ add_integration_test(
libc.src.pthread.pthread_self
)
+add_integration_test(
+ pthread_join_main_test
+ SUITE
+ libc-pthread-integration-tests
+ SRCS
+ pthread_join_main_test.cpp
+ DEPENDS
+ libc.include.pthread
+ libc.src.pthread.pthread_create
+ libc.src.pthread.pthread_exit
+ libc.src.pthread.pthread_join
+ libc.src.pthread.pthread_self
+ libc.src.stdlib.exit
+)
+
add_integration_test(
pthread_create_test
SUITE
diff --git a/libc/test/integration/src/pthread/pthread_join_main_test.cpp b/libc/test/integration/src/pthread/pthread_join_main_test.cpp
new file mode 100644
index 0000000000000..2b4ee5446bc5c
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_join_main_test.cpp
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Test for pthread_join on the main thread.
+///
+//===----------------------------------------------------------------------===//
+
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_exit.h"
+#include "src/pthread/pthread_join.h"
+#include "src/pthread/pthread_self.h"
+#include "src/stdlib/exit.h"
+#include "test/IntegrationTest/test.h"
+
+#include <pthread.h>
+
+static void *const RETVAL = reinterpret_cast<void *>(0xdead);
+
+static pthread_t main_thread;
+
+static void *worker_func(void *) {
+ void *retval = nullptr;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_join(main_thread, &retval), 0);
+ ASSERT_EQ(retval, RETVAL);
+
+ LIBC_NAMESPACE::exit(0);
+ __builtin_unreachable();
+}
+
+TEST_MAIN() {
+ main_thread = LIBC_NAMESPACE::pthread_self();
+
+ pthread_t worker_thread;
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_create(&worker_thread, nullptr, worker_func,
+ nullptr),
+ 0);
+
+ LIBC_NAMESPACE::pthread_exit(RETVAL);
+ __builtin_unreachable();
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/221177
More information about the libc-commits
mailing list