[libc-commits] [libc] Self (PR #220921)

Pavel Labath via libc-commits libc-commits at lists.llvm.org
Thu Sep 3 05:57:56 PDT 2026


https://github.com/labath created https://github.com/llvm/llvm-project/pull/220921

None

>From f34db9276fb949029c880bdb3cac99cc539202b9 Mon Sep 17 00:00:00 2001
From: Pavel Labath <pavel at labath.sk>
Date: Wed, 12 Aug 2026 21:10:24 +0000
Subject: [PATCH 1/2] [libc][threads] Add architecture-specific Thread Control
 Block (TCB) definitions

Add TCB structures and low-level thread-pointer accessor primitives for
Linux (x86_64, aarch64, riscv), and update init_tls code to use the
struct fields instead of manually computing raw pointer offsets.

I've set aside space for the ThreadAttributes pointer inside the
ABI-reserved slot for the thread library, but I'm not actually using
that value yet. I'll do that in the next patch, which will replace the
`thread_local self` object with this pointer.
---
 libc/src/__support/threads/CMakeLists.txt     | 11 ++++
 .../__support/threads/linux/CMakeLists.txt    | 16 ++++++
 .../src/__support/threads/linux/aarch64/tcb.h | 41 +++++++++++++++
 libc/src/__support/threads/linux/riscv/tcb.h  | 41 +++++++++++++++
 libc/src/__support/threads/linux/tcb.h        | 29 +++++++++++
 libc/src/__support/threads/linux/x86_64/tcb.h | 51 +++++++++++++++++++
 libc/src/__support/threads/tcb.h              | 25 +++++++++
 libc/startup/linux/aarch64/CMakeLists.txt     |  1 +
 libc/startup/linux/aarch64/tls.cpp            | 12 ++---
 libc/startup/linux/riscv/CMakeLists.txt       |  1 +
 libc/startup/linux/riscv/tls.cpp              | 12 ++---
 libc/startup/linux/x86_64/CMakeLists.txt      |  1 +
 libc/startup/linux/x86_64/tls.cpp             | 18 +++----
 13 files changed, 236 insertions(+), 23 deletions(-)
 create mode 100644 libc/src/__support/threads/linux/aarch64/tcb.h
 create mode 100644 libc/src/__support/threads/linux/riscv/tcb.h
 create mode 100644 libc/src/__support/threads/linux/tcb.h
 create mode 100644 libc/src/__support/threads/linux/x86_64/tcb.h
 create mode 100644 libc/src/__support/threads/tcb.h

diff --git a/libc/src/__support/threads/CMakeLists.txt b/libc/src/__support/threads/CMakeLists.txt
index 67a176f87a9fd..1f14e3aab4768 100644
--- a/libc/src/__support/threads/CMakeLists.txt
+++ b/libc/src/__support/threads/CMakeLists.txt
@@ -36,6 +36,17 @@ if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_OS})
   add_subdirectory(${LIBC_TARGET_OS})
 endif()
 
+if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.tcb)
+  add_header_library(
+    tcb
+    HDRS
+      tcb.h
+    DEPENDS
+      .${LIBC_TARGET_OS}.tcb
+      libc.src.__support.macros.properties.os
+  )
+endif()
+
 if(TARGET libc.src.__support.threads.${LIBC_TARGET_OS}.futex_utils)
   add_header_library(
     futex_utils
diff --git a/libc/src/__support/threads/linux/CMakeLists.txt b/libc/src/__support/threads/linux/CMakeLists.txt
index 7d349f6d9bd00..51e9d4005ad0d 100644
--- a/libc/src/__support/threads/linux/CMakeLists.txt
+++ b/libc/src/__support/threads/linux/CMakeLists.txt
@@ -1,3 +1,19 @@
+if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${LIBC_TARGET_ARCHITECTURE}/tcb.h)
+  add_header_library(
+    tcb
+    HDRS
+      tcb.h
+      ${LIBC_TARGET_ARCHITECTURE}/tcb.h
+    DEPENDS
+      libc.hdr.offsetof_macros
+      libc.hdr.stdint_proxy
+      libc.src.__support.threads.thread_attributes
+      libc.src.__support.macros.attributes
+      libc.src.__support.macros.config
+      libc.src.__support.macros.properties.architectures
+  )
+endif()
+
 add_header_library(
   futex_word_type
   HDRS
diff --git a/libc/src/__support/threads/linux/aarch64/tcb.h b/libc/src/__support/threads/linux/aarch64/tcb.h
new file mode 100644
index 0000000000000..74bdf7ffc9bfe
--- /dev/null
+++ b/libc/src/__support/threads/linux/aarch64/tcb.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Defines the Thread Control Block (TCB) for Linux AArch64.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_AARCH64_TCB_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_AARCH64_TCB_H
+
+#include "hdr/stdint_proxy.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/thread_attributes.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct ThreadControlBlock {
+  uintptr_t dtv;
+  ThreadAttributes *attrib;
+};
+
+LIBC_INLINE ThreadControlBlock *get_tcb() {
+  ThreadControlBlock *tp;
+  asm("mrs %0, tpidr_el0" : "=r"(tp));
+  return tp;
+}
+
+LIBC_INLINE ThreadAttributes *get_current_thread_attrib() {
+  return get_tcb()->attrib;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_AARCH64_TCB_H
diff --git a/libc/src/__support/threads/linux/riscv/tcb.h b/libc/src/__support/threads/linux/riscv/tcb.h
new file mode 100644
index 0000000000000..a3d09e894c930
--- /dev/null
+++ b/libc/src/__support/threads/linux/riscv/tcb.h
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Defines the Thread Control Block (TCB) for Linux RISC-V.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_RISCV_TCB_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_RISCV_TCB_H
+
+#include "hdr/stdint_proxy.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/thread_attributes.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct ThreadControlBlock {
+  uintptr_t dtv;
+  ThreadAttributes *attrib;
+};
+
+LIBC_INLINE ThreadControlBlock *get_tcb() {
+  ThreadControlBlock *tp;
+  asm("mv %0, tp" : "=r"(tp));
+  return tp - 1;
+}
+
+LIBC_INLINE ThreadAttributes *get_current_thread_attrib() {
+  return get_tcb()->attrib;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_RISCV_TCB_H
diff --git a/libc/src/__support/threads/linux/tcb.h b/libc/src/__support/threads/linux/tcb.h
new file mode 100644
index 0000000000000..e9abd07423aec
--- /dev/null
+++ b/libc/src/__support/threads/linux/tcb.h
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Defines Linux architecture-level Thread Control Block dispatcher.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_TCB_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_TCB_H
+
+#include "src/__support/macros/properties/architectures.h"
+
+#if defined(LIBC_TARGET_ARCH_IS_X86_64)
+#include "src/__support/threads/linux/x86_64/tcb.h"
+#elif defined(LIBC_TARGET_ARCH_IS_AARCH64)
+#include "src/__support/threads/linux/aarch64/tcb.h"
+#elif defined(LIBC_TARGET_ARCH_IS_ANY_RISCV)
+#include "src/__support/threads/linux/riscv/tcb.h"
+#else
+#error "Unsupported architecture for Linux Thread Control Block"
+#endif
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_TCB_H
diff --git a/libc/src/__support/threads/linux/x86_64/tcb.h b/libc/src/__support/threads/linux/x86_64/tcb.h
new file mode 100644
index 0000000000000..c4ff19afb5fb5
--- /dev/null
+++ b/libc/src/__support/threads/linux/x86_64/tcb.h
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+/// Defines the Thread Control Block (TCB) for Linux x86_64.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_X86_64_TCB_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_X86_64_TCB_H
+
+#include "hdr/offsetof_macros.h"
+#include "hdr/stdint_proxy.h"
+#include "src/__support/macros/attributes.h"
+#include "src/__support/macros/config.h"
+#include "src/__support/threads/thread_attributes.h"
+
+namespace LIBC_NAMESPACE_DECL {
+
+struct ThreadControlBlock {
+  uintptr_t self;
+  uintptr_t dtv;
+  ThreadAttributes *attrib;
+  uintptr_t reserved[2];
+  uintptr_t stack_guard;
+};
+static_assert(offsetof(ThreadControlBlock, stack_guard) == 0x28,
+              "Offset defined by the ABI");
+
+LIBC_INLINE ThreadControlBlock *get_tcb() {
+  ThreadControlBlock *tcb;
+  asm("mov %%fs:0, %0" : "=r"(tcb));
+  return tcb;
+}
+
+LIBC_INLINE ThreadAttributes *get_current_thread_attrib() {
+  ThreadAttributes *attrib;
+  asm("mov %%fs:%c1, %0"
+      : "=r"(attrib)
+      : "i"(offsetof(ThreadControlBlock, attrib)));
+  return attrib;
+}
+
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_LINUX_X86_64_TCB_H
diff --git a/libc/src/__support/threads/tcb.h b/libc/src/__support/threads/tcb.h
new file mode 100644
index 0000000000000..04af422c35df4
--- /dev/null
+++ b/libc/src/__support/threads/tcb.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
+/// Defines OS-level Thread Control Block dispatcher.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC___SUPPORT_THREADS_TCB_H
+#define LLVM_LIBC_SRC___SUPPORT_THREADS_TCB_H
+
+#include "src/__support/macros/properties/os.h"
+
+#if defined(LIBC_TARGET_OS_IS_LINUX)
+#include "src/__support/threads/linux/tcb.h"
+#else
+#error "Unsupported OS for Thread Control Block"
+#endif
+
+#endif // LLVM_LIBC_SRC___SUPPORT_THREADS_TCB_H
diff --git a/libc/startup/linux/aarch64/CMakeLists.txt b/libc/startup/linux/aarch64/CMakeLists.txt
index e733010a0a509..512ce24ba1658 100644
--- a/libc/startup/linux/aarch64/CMakeLists.txt
+++ b/libc/startup/linux/aarch64/CMakeLists.txt
@@ -9,6 +9,7 @@ add_startup_object(
     libc.src.__support.OSUtil.linux.syscall_wrappers.mmap
     libc.src.__support.OSUtil.linux.syscall_wrappers.munmap
     libc.src.__support.OSUtil.osutil
+    libc.src.__support.threads.tcb
     libc.src.string.memory_utils.inline_memcpy
   COMPILE_OPTIONS
     -fno-omit-frame-pointer
diff --git a/libc/startup/linux/aarch64/tls.cpp b/libc/startup/linux/aarch64/tls.cpp
index bc35b85bdec46..f16e833014ccb 100644
--- a/libc/startup/linux/aarch64/tls.cpp
+++ b/libc/startup/linux/aarch64/tls.cpp
@@ -12,7 +12,7 @@
 #include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
 #include "src/__support/OSUtil/syscall.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/threads/thread.h"
+#include "src/__support/threads/tcb.h"
 #include "src/string/memory_utils/inline_memcpy.h"
 
 #include <arm_acle.h>
@@ -33,20 +33,20 @@ void init_tls(TLSDescriptor &tls_descriptor) {
   // aarch64 follows the variant 1 TLS layout:
   //
   // 1. First entry is the dynamic thread vector pointer
-  // 2. Second entry is a 8-byte reserved word.
+  // 2. Second entry is a 8-byte reserved word (used for attrib).
   // 3. Padding for alignment.
   // 4. The TLS data from the ELF image.
   //
   // The thread pointer points to the first entry.
 
-  const uintptr_t size_of_pointers = 2 * sizeof(uintptr_t);
+  const uintptr_t TCB_SIZE = sizeof(ThreadControlBlock);
   uintptr_t padding = 0;
   const uintptr_t ALIGNMENT_MASK = app.tls.align - 1;
-  uintptr_t diff = size_of_pointers & ALIGNMENT_MASK;
+  uintptr_t diff = TCB_SIZE & ALIGNMENT_MASK;
   if (diff != 0)
     padding += (ALIGNMENT_MASK - diff) + 1;
 
-  uintptr_t alloc_size = size_of_pointers + padding + app.tls.size;
+  uintptr_t alloc_size = TCB_SIZE + padding + app.tls.size;
 
   ErrorOr<void *> mmap_ret =
       linux_syscalls::mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE,
@@ -54,7 +54,7 @@ void init_tls(TLSDescriptor &tls_descriptor) {
   if (!mmap_ret.has_value())
     syscall_impl<long>(SYS_exit, 1);
   uintptr_t thread_ptr = uintptr_t(mmap_ret.value());
-  uintptr_t tls_addr = thread_ptr + size_of_pointers + padding;
+  uintptr_t tls_addr = thread_ptr + TCB_SIZE + padding;
   inline_memcpy(reinterpret_cast<char *>(tls_addr),
                 reinterpret_cast<const char *>(app.tls.address),
                 app.tls.init_size);
diff --git a/libc/startup/linux/riscv/CMakeLists.txt b/libc/startup/linux/riscv/CMakeLists.txt
index 28654aef44c7c..be09b7e620d04 100644
--- a/libc/startup/linux/riscv/CMakeLists.txt
+++ b/libc/startup/linux/riscv/CMakeLists.txt
@@ -9,6 +9,7 @@ add_startup_object(
     libc.src.__support.OSUtil.linux.syscall_wrappers.mmap
     libc.src.__support.OSUtil.linux.syscall_wrappers.munmap
     libc.src.__support.OSUtil.osutil
+    libc.src.__support.threads.tcb
     libc.src.string.memory_utils.inline_memcpy
   COMPILE_OPTIONS
     -fno-omit-frame-pointer
diff --git a/libc/startup/linux/riscv/tls.cpp b/libc/startup/linux/riscv/tls.cpp
index 87edca12c5b87..9e0721195499b 100644
--- a/libc/startup/linux/riscv/tls.cpp
+++ b/libc/startup/linux/riscv/tls.cpp
@@ -12,7 +12,7 @@
 #include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
 #include "src/__support/OSUtil/syscall.h"
 #include "src/__support/macros/config.h"
-#include "src/__support/threads/thread.h"
+#include "src/__support/threads/tcb.h"
 #include "src/string/memory_utils/inline_memcpy.h"
 #include <sys/syscall.h>
 
@@ -25,15 +25,15 @@ void init_tls(TLSDescriptor &tls_descriptor) {
     return;
   }
 
-  // riscv64 follows the variant 1 TLS layout:
-  const uintptr_t size_of_pointers = 2 * sizeof(uintptr_t);
+  // riscv follows the variant 1 TLS layout:
+  const uintptr_t TCB_SIZE = sizeof(ThreadControlBlock);
   uintptr_t padding = 0;
   const uintptr_t ALIGNMENT_MASK = app.tls.align - 1;
-  uintptr_t diff = size_of_pointers & ALIGNMENT_MASK;
+  uintptr_t diff = TCB_SIZE & ALIGNMENT_MASK;
   if (diff != 0)
     padding += (ALIGNMENT_MASK - diff) + 1;
 
-  uintptr_t alloc_size = size_of_pointers + padding + app.tls.size;
+  uintptr_t alloc_size = TCB_SIZE + padding + app.tls.size;
 
   ErrorOr<void *> mmap_ret =
       linux_syscalls::mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE,
@@ -41,7 +41,7 @@ void init_tls(TLSDescriptor &tls_descriptor) {
   if (!mmap_ret.has_value())
     syscall_impl<long>(SYS_exit, 1);
   uintptr_t thread_ptr = uintptr_t(mmap_ret.value());
-  uintptr_t tls_addr = thread_ptr + size_of_pointers + padding;
+  uintptr_t tls_addr = thread_ptr + TCB_SIZE + padding;
   inline_memcpy(reinterpret_cast<char *>(tls_addr),
                 reinterpret_cast<const char *>(app.tls.address),
                 app.tls.init_size);
diff --git a/libc/startup/linux/x86_64/CMakeLists.txt b/libc/startup/linux/x86_64/CMakeLists.txt
index d458d9ac89316..a0938d403411e 100644
--- a/libc/startup/linux/x86_64/CMakeLists.txt
+++ b/libc/startup/linux/x86_64/CMakeLists.txt
@@ -10,6 +10,7 @@ add_startup_object(
     libc.src.__support.OSUtil.linux.syscall_wrappers.getrandom
     libc.src.__support.OSUtil.linux.syscall_wrappers.mmap
     libc.src.__support.OSUtil.linux.syscall_wrappers.munmap
+    libc.src.__support.threads.tcb
     libc.src.string.memory_utils.inline_memcpy
   COMPILE_OPTIONS
     -fno-stack-protector
diff --git a/libc/startup/linux/x86_64/tls.cpp b/libc/startup/linux/x86_64/tls.cpp
index 13f6491295fe8..5810955c5d24d 100644
--- a/libc/startup/linux/x86_64/tls.cpp
+++ b/libc/startup/linux/x86_64/tls.cpp
@@ -12,6 +12,7 @@
 #include "src/__support/OSUtil/linux/syscall_wrappers/mmap.h"
 #include "src/__support/OSUtil/linux/syscall_wrappers/munmap.h"
 #include "src/__support/macros/config.h"
+#include "src/__support/threads/tcb.h"
 #include "src/string/memory_utils/inline_memcpy.h"
 
 #include <asm/prctl.h>
@@ -32,15 +33,10 @@ void init_tls(TLSDescriptor &tls_descriptor) {
   if (tls_size != app.tls.size)
     tls_size += app.tls.align;
 
-  // Per the x86_64 TLS ABI, the entry pointed to by the thread pointer is the
-  // address of the TLS block. So, we add more size to accomodate this address
-  // entry.
-  // We also need to include space for the stack canary. The canary is at
-  // offset 0x28 (40) and is of size uintptr_t.
-  uintptr_t tls_size_with_addr = tls_size + sizeof(uintptr_t) + 40;
+  uintptr_t tls_size_with_tcb = tls_size + sizeof(ThreadControlBlock);
 
   ErrorOr<void *> mmap_ret =
-      linux_syscalls::mmap(nullptr, tls_size_with_addr, PROT_READ | PROT_WRITE,
+      linux_syscalls::mmap(nullptr, tls_size_with_tcb, PROT_READ | PROT_WRITE,
                            MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
   if (!mmap_ret.has_value())
     syscall_impl<long>(SYS_exit, 1);
@@ -49,23 +45,23 @@ void init_tls(TLSDescriptor &tls_descriptor) {
   // x86_64 TLS faces down from the thread pointer with the first entry
   // pointing to the address of the first real TLS byte.
   uintptr_t end_ptr = reinterpret_cast<uintptr_t>(tls_addr) + tls_size;
-  *reinterpret_cast<uintptr_t *>(end_ptr) = end_ptr;
+  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);
-  uintptr_t *stack_guard_addr = reinterpret_cast<uintptr_t *>(end_ptr + 40);
   // 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
   // use errno before TLS is setup. The linux_syscalls wrapper is safe as it
   // reports errors via ErrorOr instead of errno.
   ErrorOr<ssize_t> stack_guard_retval =
-      linux_syscalls::getrandom(stack_guard_addr, sizeof(uint64_t), 0);
+      linux_syscalls::getrandom(&tcb->stack_guard, sizeof(tcb->stack_guard), 0);
   if (!stack_guard_retval.has_value())
     syscall_impl(SYS_exit, 1);
 
-  tls_descriptor = {tls_size_with_addr, reinterpret_cast<uintptr_t>(tls_addr),
+  tls_descriptor = {tls_size_with_tcb, reinterpret_cast<uintptr_t>(tls_addr),
                     end_ptr};
   return;
 }

>From 8a74eb66dd49e47cd0f824e63d6fde2e7cb2465b 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 2/2] [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             |  6 --
 .../integration/src/pthread/CMakeLists.txt    | 13 +++
 .../src/pthread/pthread_tls_align_test.cpp    | 79 +++++++++++++++++++
 16 files changed, 130 insertions(+), 51 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..bef7a67279542 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)
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..c99c8517717bb
--- /dev/null
+++ b/libc/test/integration/src/pthread/pthread_tls_align_test.cpp
@@ -0,0 +1,79 @@
+//===-- Test handling of overly aligned 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 "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;
+
+alignas(ALIGN1) static thread_local int aligned_var1 = 123;
+alignas(ALIGN2) static thread_local int aligned_var2 = 456;
+
+static void *child_ptr1 = nullptr;
+static void *child_ptr2 = nullptr;
+
+static void *thread_func(void *) {
+  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(aligned_var1, 123);
+  ASSERT_EQ(aligned_var2, 456);
+
+  child_ptr1 = &aligned_var1;
+  child_ptr2 = &aligned_var2;
+
+  aligned_var1 = 789;
+  aligned_var2 = 101112;
+
+  ASSERT_EQ(aligned_var1, 789);
+  ASSERT_EQ(aligned_var2, 101112);
+
+  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(aligned_var1, 123);
+  ASSERT_EQ(aligned_var2, 456);
+
+  pthread_t th1;
+  void *retval = nullptr;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th1, nullptr, thread_func, nullptr), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th1, &retval), 0);
+  ASSERT_EQ(retval, nullptr);
+
+  ASSERT_NE(child_ptr1, static_cast<void *>(&aligned_var1));
+  ASSERT_NE(child_ptr2, static_cast<void *>(&aligned_var2));
+
+  // Child thread modifications must not affect main thread.
+  ASSERT_EQ(aligned_var1, 123);
+  ASSERT_EQ(aligned_var2, 456);
+
+  pthread_t th2;
+  ASSERT_EQ(
+      LIBC_NAMESPACE::pthread_create(&th2, nullptr, thread_func, nullptr), 0);
+  ASSERT_EQ(LIBC_NAMESPACE::pthread_join(th2, &retval), 0);
+  ASSERT_EQ(retval, nullptr);
+
+  ASSERT_NE(child_ptr1, static_cast<void *>(&aligned_var1));
+  ASSERT_NE(child_ptr2, static_cast<void *>(&aligned_var2));
+  ASSERT_EQ(aligned_var1, 123);
+  ASSERT_EQ(aligned_var2, 456);
+
+  return 0;
+}



More information about the libc-commits mailing list