[libc-commits] [libc] Self (PR #220921)
Pavel Labath via libc-commits
libc-commits at lists.llvm.org
Thu Sep 3 23:58:37 PDT 2026
https://github.com/labath updated https://github.com/llvm/llvm-project/pull/220921
>From 3b1fc6008400f05c76db8724be48616f8d0153e9 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Wed, 12 Aug 2026 22:04:55 +0000
Subject: [PATCH] [libc][threads] Store ThreadAttributes in the TCB
This patch replaces the `thread_local self` object with the
ThreadAttributes pointer stored in the TCB. current_thread() now
fetches this pointer directly via get_current_thread_attrib().
Since every thread now needs a TCB, init_tls can no longer bail out
when app.tls.size == 0 (this was actually dead code before this patch
since every binary contained at least one thread-local variable
(`self)`). I've removed that check and updated do_start to always set up
a TCB (defaulting to alignment 1 if there is no TLS segment).
The motivation for this change is to make the pthread_self function
async-signal-safe (as required by POSIX), including in the cases where
the function is invoked from a signal hander on the newly created thread
-- before pthread_create returns. This requires making sure that the
thread attribute pointer is initialized from the very first instruction
of the new thread -- something which is hard to do when the attributes
are stored in a TLS variable. This also aligns us with how other C
libraries operate.
While in there, I also remove atexit_callback_mgr from ThreadAttributes.
It simplifies thread startup, and the field was always initialized to
point to the same object anyway. If needed, this can always be changed
later.
Since aligning the various parts of the TLS block is the trickiest part
of this patch, I also add a test that verifies that we've done that
correctly.
Assisted-by: Gemini.
---
libc/src/__support/threads/CMakeLists.txt | 1 +
.../src/__support/threads/linux/aarch64/tcb.h | 4 +
libc/src/__support/threads/linux/riscv/tcb.h | 4 +
libc/src/__support/threads/linux/thread.cpp | 6 +-
libc/src/__support/threads/linux/x86_64/tcb.h | 4 +
libc/src/__support/threads/thread.cpp | 8 +-
libc/src/__support/threads/thread.h | 15 +--
.../src/__support/threads/thread_attributes.h | 7 +-
libc/src/stdlib/exit.cpp | 2 +-
libc/startup/linux/CMakeLists.txt | 1 +
libc/startup/linux/aarch64/tls.cpp | 6 -
libc/startup/linux/do_start.cpp | 19 +--
libc/startup/linux/riscv/tls.cpp | 6 -
libc/startup/linux/x86_64/tls.cpp | 14 +--
.../integration/src/pthread/CMakeLists.txt | 13 +++
.../src/pthread/pthread_tls_align_test.cpp | 109 ++++++++++++++++++
16 files changed, 165 insertions(+), 54 deletions(-)
create mode 100644 libc/test/integration/src/pthread/pthread_tls_align_test.cpp
diff --git a/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt
index 1f14e3aab4768..dde3ee34f3a3a 100644
--- a/libc/src/__support/threads/CMakeLists.txt
+++ b/libc/src/__support/threads/CMakeLists.txt
@@ -135,6 +135,7 @@ add_header_library(
HDRS
thread.h
DEPENDS
+ .tcb
.thread_attributes
libc.hdr.stdint_proxy
libc.hdr.types.struct_sched_param
diff --git a/libc/src/__support/threads/linux/aarch64/tcb.h b/libc/src/__support/threads/linux/aarch64/tcb.h
index 74bdf7ffc9bfe..8a2d6a408b9b5 100644
--- a/libc/src/__support/threads/linux/aarch64/tcb.h
+++ b/libc/src/__support/threads/linux/aarch64/tcb.h
@@ -32,6 +32,10 @@ LIBC_INLINE ThreadControlBlock *get_tcb() {
return tp;
}
+LIBC_INLINE ThreadControlBlock *get_tcb(uintptr_t tp) {
+ return reinterpret_cast<ThreadControlBlock *>(tp);
+}
+
LIBC_INLINE ThreadAttributes *get_current_thread_attrib() {
return get_tcb()->attrib;
}
diff --git a/libc/src/__support/threads/linux/riscv/tcb.h b/libc/src/__support/threads/linux/riscv/tcb.h
index a3d09e894c930..f4e0a1314ef50 100644
--- a/libc/src/__support/threads/linux/riscv/tcb.h
+++ b/libc/src/__support/threads/linux/riscv/tcb.h
@@ -32,6 +32,10 @@ LIBC_INLINE ThreadControlBlock *get_tcb() {
return tp - 1;
}
+LIBC_INLINE ThreadControlBlock *get_tcb(uintptr_t tp) {
+ return reinterpret_cast<ThreadControlBlock *>(tp) - 1;
+}
+
LIBC_INLINE ThreadAttributes *get_current_thread_attrib() {
return get_tcb()->attrib;
}
diff --git a/libc/src/__support/threads/linux/thread.cpp b/libc/src/__support/threads/linux/thread.cpp
index 231b7cf2903ca..cd9f3eeb801f0 100644
--- a/libc/src/__support/threads/linux/thread.cpp
+++ b/libc/src/__support/threads/linux/thread.cpp
@@ -180,8 +180,6 @@ cleanup_thread_resources(ThreadAttributes *attrib) {
[[gnu::noinline]] void start_thread() {
auto *start_args = reinterpret_cast<StartArgs *>(get_start_args_addr());
auto *attrib = start_args->thread_attrib;
- internal::self.attrib = attrib;
- attrib->atexit_callback_mgr = internal::get_thread_atexit_callback_mgr();
if (attrib->style == ThreadStyle::POSIX) {
attrib->retval.posix_retval =
@@ -294,6 +292,8 @@ int Thread::run(ThreadStyle style, ThreadRunner runner, void *arg, void *stack,
clear_tid->set(CLEAR_TID_VALUE);
attrib->platform_data = clear_tid;
+ get_tcb(tls.tp)->attrib = attrib;
+
// The clone syscall takes arguments in an architecture specific order.
// Also, we want the result of the syscall to be in a register as the child
// thread gets a completely different stack after it is created. The stack
@@ -526,7 +526,7 @@ void thread_exit(ThreadReturnValue retval, ThreadStyle style) {
// cleanup_thread_resources function as that function can be called from a
// different thread. The destructors of thread local and TSS objects should
// be called by the thread which owns them.
- internal::call_atexit_callbacks(attrib);
+ internal::call_atexit_callbacks();
uint32_t joinable_state = uint32_t(DetachState::JOINABLE);
if (!attrib->detach_state.compare_exchange_strong(
diff --git a/libc/src/__support/threads/linux/x86_64/tcb.h b/libc/src/__support/threads/linux/x86_64/tcb.h
index c4ff19afb5fb5..7b9242690f8c4 100644
--- a/libc/src/__support/threads/linux/x86_64/tcb.h
+++ b/libc/src/__support/threads/linux/x86_64/tcb.h
@@ -38,6 +38,10 @@ LIBC_INLINE ThreadControlBlock *get_tcb() {
return tcb;
}
+LIBC_INLINE ThreadControlBlock *get_tcb(uintptr_t tp) {
+ return reinterpret_cast<ThreadControlBlock *>(tp);
+}
+
LIBC_INLINE ThreadAttributes *get_current_thread_attrib() {
ThreadAttributes *attrib;
asm("mov %%fs:%c1, %0"
diff --git a/libc/src/__support/threads/thread.cpp b/libc/src/__support/threads/thread.cpp
index 31c230a45b573..abcf7e399c16e 100644
--- a/libc/src/__support/threads/thread.cpp
+++ b/libc/src/__support/threads/thread.cpp
@@ -150,12 +150,8 @@ extern "C" int __cxa_thread_atexit_impl(AtExitCallback *callback, void *obj,
namespace internal {
-ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr() {
- return &atexit_callback_mgr;
-}
-
-void call_atexit_callbacks(ThreadAttributes *attrib) {
- attrib->atexit_callback_mgr->call();
+void call_atexit_callbacks() {
+ atexit_callback_mgr.call();
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.h b/libc/src/__support/threads/thread.h
index c42f87a1a3c57..446e7f71f08a7 100644
--- a/libc/src/__support/threads/thread.h
+++ b/libc/src/__support/threads/thread.h
@@ -19,6 +19,7 @@
#include "src/__support/macros/attributes.h"
#include "src/__support/macros/config.h"
#include "src/__support/macros/properties/architectures.h"
+#include "src/__support/threads/tcb.h"
#include "src/__support/threads/thread_attributes.h"
// TODO: fix this unguarded linux dep
@@ -160,21 +161,15 @@ namespace internal {
// Internal namespace containing utilities which are to be used by platform
// implementations of threads.
-// Return the current thread's atexit callback manager. After thread startup
-// but before running the thread function, platform implementations should
-// set the "atexit_callback_mgr" field of the thread's attributes to the value
-// returned by this function.
-ThreadAtExitCallbackMgr *get_thread_atexit_callback_mgr();
-
// Call the currently registered thread specific atexit callbacks. Useful for
// implementing the thread_exit function.
-void call_atexit_callbacks(ThreadAttributes *attrib);
-
-LIBC_INLINE_VAR LIBC_THREAD_LOCAL Thread self;
+void call_atexit_callbacks();
} // namespace internal
-LIBC_INLINE Thread current_thread() { return internal::self; }
+LIBC_INLINE Thread current_thread() {
+ return Thread(get_current_thread_attrib());
+}
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/__support/threads/thread_attributes.h b/libc/src/__support/threads/thread_attributes.h
index 6225837ed7784..123b0d2a6c8ce 100644
--- a/libc/src/__support/threads/thread_attributes.h
+++ b/libc/src/__support/threads/thread_attributes.h
@@ -67,8 +67,6 @@ enum class DetachType : int {
CLEANUP = 2
};
-class ThreadAtExitCallbackMgr;
-
// A data type to hold common thread attributes which have to be stored as
// thread state. Note that this is different from public attribute types like
// pthread_attr_t which might contain information which need not be saved as
@@ -105,15 +103,14 @@ struct alignas(STACK_ALIGNMENT) ThreadAttributes {
int tid;
ThreadStyle style;
ThreadReturnValue retval;
- ThreadAtExitCallbackMgr *atexit_callback_mgr;
void *platform_data;
cpp::Atomic<ThreadAttributes *> joiner;
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(),
- atexit_callback_mgr(nullptr), platform_data(nullptr), joiner(nullptr) {}
+ tid(-1), style(ThreadStyle::POSIX), retval(), platform_data(nullptr),
+ joiner(nullptr) {}
};
} // namespace LIBC_NAMESPACE_DECL
diff --git a/libc/src/stdlib/exit.cpp b/libc/src/stdlib/exit.cpp
index 99763729cf5b4..ab43a52b04b9f 100644
--- a/libc/src/stdlib/exit.cpp
+++ b/libc/src/stdlib/exit.cpp
@@ -23,7 +23,7 @@ extern "C" void __cxa_finalize(void *);
[[noreturn]] LLVM_LIBC_FUNCTION(void, exit, (int status)) {
#ifdef LIBC_COPT_SUPPORT_THREADS
// Call TLS destructors, if supported by the target.
- internal::call_atexit_callbacks(current_thread().attrib);
+ internal::call_atexit_callbacks();
#endif
__cxa_finalize(nullptr);
internal::exit(status);
diff --git a/libc/startup/linux/CMakeLists.txt b/libc/startup/linux/CMakeLists.txt
index 11aa97ef97c40..6fc59020ab043 100644
--- a/libc/startup/linux/CMakeLists.txt
+++ b/libc/startup/linux/CMakeLists.txt
@@ -121,6 +121,7 @@ add_object_library(
libc.include.sys_syscall
libc.src.__support.OSUtil.linux.auxv
libc.src.__support.OSUtil.osutil
+ libc.src.__support.threads.tcb
libc.src.__support.threads.thread
libc.src.__support.macros.config
libc.startup.linux.${LIBC_TARGET_ARCHITECTURE}.irelative
diff --git a/libc/startup/linux/aarch64/tls.cpp b/libc/startup/linux/aarch64/tls.cpp
index f16e833014ccb..c7a4364ea2e2f 100644
--- a/libc/startup/linux/aarch64/tls.cpp
+++ b/libc/startup/linux/aarch64/tls.cpp
@@ -24,12 +24,6 @@
namespace LIBC_NAMESPACE_DECL {
void init_tls(TLSDescriptor &tls_descriptor) {
- if (app.tls.size == 0) {
- tls_descriptor.size = 0;
- tls_descriptor.tp = 0;
- return;
- }
-
// aarch64 follows the variant 1 TLS layout:
//
// 1. First entry is the dynamic thread vector pointer
diff --git a/libc/startup/linux/do_start.cpp b/libc/startup/linux/do_start.cpp
index 410cbe8e12a48..a9b7168528dd4 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/tcb.h"
#include "src/__support/threads/thread.h"
#include "src/errno/program_invocation_name.h"
#include "src/errno/program_invocation_short_name.h"
@@ -183,20 +184,22 @@ static TLSDescriptor tls;
reinterpret_cast<uintptr_t>(__rela_iplt_end))
apply_irelative_relocs(base, hwcap, hwcap2);
- app.tls.address = tls_phdr->p_vaddr + base;
- app.tls.size = tls_phdr->p_memsz;
- app.tls.init_size = tls_phdr->p_filesz;
- app.tls.align = tls_phdr->p_align;
+ if (tls_phdr) {
+ app.tls.address = tls_phdr->p_vaddr + base;
+ app.tls.size = tls_phdr->p_memsz;
+ app.tls.init_size = tls_phdr->p_filesz;
+ app.tls.align = tls_phdr->p_align ? tls_phdr->p_align : 1;
+ } else {
+ app.tls.align = 1;
+ }
// This descriptor has to be static since its cleanup function cannot
// capture the context.
init_tls(tls);
- if (tls.size != 0 && !set_thread_ptr(tls.tp))
+ if (!set_thread_ptr(tls.tp))
syscall_impl<long>(SYS_exit, 1);
- internal::self.attrib = &main_thread_attrib;
- main_thread_attrib.atexit_callback_mgr =
- internal::get_thread_atexit_callback_mgr();
+ get_tcb(tls.tp)->attrib = &main_thread_attrib;
// We want the fini array callbacks to be run after other atexit
// callbacks are run. So, we register them before running the init
diff --git a/libc/startup/linux/riscv/tls.cpp b/libc/startup/linux/riscv/tls.cpp
index 9e0721195499b..f6d60ee3e6d27 100644
--- a/libc/startup/linux/riscv/tls.cpp
+++ b/libc/startup/linux/riscv/tls.cpp
@@ -19,12 +19,6 @@
namespace LIBC_NAMESPACE_DECL {
void init_tls(TLSDescriptor &tls_descriptor) {
- if (app.tls.size == 0) {
- tls_descriptor.size = 0;
- tls_descriptor.tp = 0;
- return;
- }
-
// riscv follows the variant 1 TLS layout:
const uintptr_t TCB_SIZE = sizeof(ThreadControlBlock);
uintptr_t padding = 0;
diff --git a/libc/startup/linux/x86_64/tls.cpp b/libc/startup/linux/x86_64/tls.cpp
index 5810955c5d24d..f7436f60e065e 100644
--- a/libc/startup/linux/x86_64/tls.cpp
+++ b/libc/startup/linux/x86_64/tls.cpp
@@ -22,12 +22,6 @@ namespace LIBC_NAMESPACE_DECL {
// TODO: Also generalize this routine and handle dynamic loading properly.
void init_tls(TLSDescriptor &tls_descriptor) {
- if (app.tls.size == 0) {
- tls_descriptor.size = 0;
- tls_descriptor.tp = 0;
- return;
- }
-
// We will assume the alignment is always a power of two.
uintptr_t tls_size = app.tls.size & -app.tls.align;
if (tls_size != app.tls.size)
@@ -48,9 +42,11 @@ void init_tls(TLSDescriptor &tls_descriptor) {
auto *tcb = reinterpret_cast<ThreadControlBlock *>(end_ptr);
tcb->self = end_ptr;
- inline_memcpy(reinterpret_cast<char *>(tls_addr),
- reinterpret_cast<const char *>(app.tls.address),
- app.tls.init_size);
+ if (app.tls.init_size) {
+ inline_memcpy(reinterpret_cast<char *>(tls_addr),
+ reinterpret_cast<const char *>(app.tls.address),
+ app.tls.init_size);
+ }
// Setting the stack guard to a random value.
// We cannot call the get_random function here as the function sets errno on
// failure. Since errno is implemented via a thread local variable, we cannot
diff --git a/libc/test/integration/src/pthread/CMakeLists.txt b/libc/test/integration/src/pthread/CMakeLists.txt
index 19d02bbc3f0c4..8c9df27d04802 100644
--- a/libc/test/integration/src/pthread/CMakeLists.txt
+++ b/libc/test/integration/src/pthread/CMakeLists.txt
@@ -150,6 +150,19 @@ add_integration_test(
libc.src.pthread.pthread_join
)
+add_integration_test(
+ pthread_tls_align_test
+ SUITE
+ libc-pthread-integration-tests
+ SRCS
+ pthread_tls_align_test.cpp
+ DEPENDS
+ libc.hdr.stdint_proxy
+ libc.include.pthread
+ libc.src.pthread.pthread_create
+ libc.src.pthread.pthread_join
+)
+
add_integration_test(
pthread_equal_test
SUITE
diff --git a/libc/test/integration/src/pthread/pthread_tls_align_test.cpp b/libc/test/integration/src/pthread/pthread_tls_align_test.cpp
new file mode 100644
index 0000000000000..8a9398c96b20a
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_tls_align_test.cpp
@@ -0,0 +1,109 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 handling of overly aligned thread local data.
+///
+//===----------------------------------------------------------------------===//
+
+#include "hdr/stdint_proxy.h"
+#include "src/pthread/pthread_create.h"
+#include "src/pthread/pthread_join.h"
+#include "test/IntegrationTest/test.h"
+
+#include <pthread.h>
+
+constexpr size_t ALIGN1 = 64;
+constexpr size_t ALIGN2 = 128;
+constexpr size_t ALIGN3 = 256;
+
+alignas(ALIGN1) static thread_local int aligned_var1 = 123;
+alignas(ALIGN2) static thread_local int aligned_var2 = 456;
+alignas(ALIGN3) static thread_local int aligned_bss_var;
+
+struct ThreadTlsPointers {
+ void *ptr1 = nullptr;
+ void *ptr2 = nullptr;
+ void *ptr3 = nullptr;
+};
+
+static void *thread_func(void *arg) {
+ auto *ptrs = static_cast<ThreadTlsPointers *>(arg);
+
+ ASSERT_EQ(reinterpret_cast<uintptr_t>(&aligned_var1) % ALIGN1,
+ static_cast<uintptr_t>(0));
+ ASSERT_EQ(reinterpret_cast<uintptr_t>(&aligned_var2) % ALIGN2,
+ static_cast<uintptr_t>(0));
+ ASSERT_EQ(reinterpret_cast<uintptr_t>(&aligned_bss_var) % ALIGN3,
+ static_cast<uintptr_t>(0));
+ ASSERT_EQ(aligned_var1, 123);
+ ASSERT_EQ(aligned_var2, 456);
+ ASSERT_EQ(aligned_bss_var, 0);
+
+ ptrs->ptr1 = &aligned_var1;
+ ptrs->ptr2 = &aligned_var2;
+ ptrs->ptr3 = &aligned_bss_var;
+
+ aligned_var1 = 789;
+ aligned_var2 = 101112;
+ aligned_bss_var = 131415;
+
+ ASSERT_EQ(aligned_var1, 789);
+ ASSERT_EQ(aligned_var2, 101112);
+ ASSERT_EQ(aligned_bss_var, 131415);
+
+ return nullptr;
+}
+
+TEST_MAIN() {
+ ASSERT_EQ(reinterpret_cast<uintptr_t>(&aligned_var1) % ALIGN1,
+ static_cast<uintptr_t>(0));
+ ASSERT_EQ(reinterpret_cast<uintptr_t>(&aligned_var2) % ALIGN2,
+ static_cast<uintptr_t>(0));
+ ASSERT_EQ(reinterpret_cast<uintptr_t>(&aligned_bss_var) % ALIGN3,
+ static_cast<uintptr_t>(0));
+ ASSERT_EQ(aligned_var1, 123);
+ ASSERT_EQ(aligned_var2, 456);
+ ASSERT_EQ(aligned_bss_var, 0);
+
+ pthread_t th1;
+ pthread_t th2;
+ ThreadTlsPointers th1_ptrs;
+ ThreadTlsPointers th2_ptrs;
+ void *retval = nullptr;
+
+ ASSERT_EQ(
+ LIBC_NAMESPACE::pthread_create(&th1, nullptr, thread_func, &th1_ptrs), 0);
+ ASSERT_EQ(
+ LIBC_NAMESPACE::pthread_create(&th2, nullptr, thread_func, &th2_ptrs), 0);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th1, &retval), 0);
+ ASSERT_EQ(retval, nullptr);
+ ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th2, &retval), 0);
+ ASSERT_EQ(retval, nullptr);
+
+ // Child thread pointers must not match main thread.
+ ASSERT_NE(th1_ptrs.ptr1, static_cast<void *>(&aligned_var1));
+ ASSERT_NE(th1_ptrs.ptr2, static_cast<void *>(&aligned_var2));
+ ASSERT_NE(th1_ptrs.ptr3, static_cast<void *>(&aligned_bss_var));
+
+ ASSERT_NE(th2_ptrs.ptr1, static_cast<void *>(&aligned_var1));
+ ASSERT_NE(th2_ptrs.ptr2, static_cast<void *>(&aligned_var2));
+ ASSERT_NE(th2_ptrs.ptr3, static_cast<void *>(&aligned_bss_var));
+
+ // Child thread pointers must not match each other.
+ ASSERT_NE(th1_ptrs.ptr1, th2_ptrs.ptr1);
+ ASSERT_NE(th1_ptrs.ptr2, th2_ptrs.ptr2);
+ ASSERT_NE(th1_ptrs.ptr3, th2_ptrs.ptr3);
+
+ // Child thread modifications must not affect main thread.
+ ASSERT_EQ(aligned_var1, 123);
+ ASSERT_EQ(aligned_var2, 456);
+ ASSERT_EQ(aligned_bss_var, 0);
+
+ return 0;
+}
More information about the libc-commits
mailing list