[libc-commits] [libc] 15cff90 - [libc] Make it possible to join the main thread (#221177)
via libc-commits
libc-commits at lists.llvm.org
Thu Sep 17 01:14:19 PDT 2026
Author: Pavel Labath
Date: 2026-09-17T10:14:13+02:00
New Revision: 15cff9069ae805e4f5decb4965d33fb4ebd274b5
URL: https://github.com/llvm/llvm-project/commit/15cff9069ae805e4f5decb4965d33fb4ebd274b5
DIFF: https://github.com/llvm/llvm-project/commit/15cff9069ae805e4f5decb4965d33fb4ebd274b5.diff
LOG: [libc] Make it possible to join the main thread (#221177)
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
Added:
libc/test/integration/src/pthread/pthread_join_main_test.cpp
Modified:
libc/src/__support/threads/linux/thread.cpp
libc/startup/linux/CMakeLists.txt
libc/startup/linux/do_start.cpp
libc/test/integration/src/pthread/CMakeLists.txt
libc/test/integration/src/pthread/pthread_getattr_np_test.cpp
Removed:
################################################################################
diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index 3dcb6fd6da6ac..efbdc4a45d373 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -29,6 +29,7 @@
#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
@@ -48,7 +49,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.
@@ -186,15 +186,11 @@ cleanup_thread_resources(ThreadAttributes *attrib) {
auto *attrib = start_args->thread_attrib;
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);
}
}
@@ -297,7 +293,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;
get_tcb(tls.tp)->attrib = attrib;
@@ -407,8 +403,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 {
@@ -561,6 +558,7 @@ ErrorOr<void> Thread::kill(int sig) {
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 6fc59020ab043..e798b00e8819a 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -123,6 +123,7 @@ add_object_library(
libc.src.__support.OSUtil.osutil
libc.src.__support.threads.tcb
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 a9b7168528dd4..b38929b66996f 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/tcb.h"
#include "src/__support/threads/thread.h"
#include "src/errno/program_invocation_name.h"
@@ -77,6 +78,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() {
@@ -84,6 +86,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 33794387a4708..abeadc4be1c9a 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -272,6 +272,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_getattr_np_test.cpp b/libc/test/integration/src/pthread/pthread_getattr_np_test.cpp
index ccba2102acdac..5833856cba0c8 100644
--- a/libc/test/integration/src/pthread/pthread_getattr_np_test.cpp
+++ b/libc/test/integration/src/pthread/pthread_getattr_np_test.cpp
@@ -82,7 +82,7 @@ static void test_main_thread() {
PthreadAttrValues values;
values.populate_from(LIBC_NAMESPACE::pthread_self());
- ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_DETACHED));
+ ASSERT_EQ(values.detachstate, static_cast<int>(PTHREAD_CREATE_JOINABLE));
ASSERT_NE(values.stackaddr, static_cast<void *>(nullptr));
ASSERT_EQ(values.stacksize, static_cast<size_t>(PTHREAD_STACK_DYNAMIC_NP));
ASSERT_EQ(reinterpret_cast<uintptr_t>(values.stackaddr) % pagesize(),
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..73c460246836e
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_join_main_test.cpp
@@ -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
+/// Test for pthread_join on the main thread.
+///
+//===----------------------------------------------------------------------===//
+
+#include "include/llvm-libc-macros/pthread-macros.h"
+#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"
+
+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();
+}
More information about the libc-commits
mailing list