Repository navigation
libs/libc/tls: Add a per-thread __emutls_get_address(). - #20803
v-zhangxiaomeng5 wants to merge 1 commit into
Conversation
|
f22b0a6 to
31c8fe1
Compare
GCC toolchain configured with --disable-tls compiles a thread_local or __thread variable into a call to __emutls_get_address(), but libgcc only provides the implementation for single thread model. Uses cascaded task_tls + pthread_key for per-thread TLS implementation. Assisted-by: Claude Code:claude-opus-5-5 Signed-off-by: v-zhangxiaomeng5 <v-zhangxiaomeng5@xiaomi.com>
31c8fe1 to
7d9fdf9
Compare
|
Thank you for this PR! A per-thread I tried it on master fc91db1 with qemu-armv8a (QEMU 11) and the Arm GNU Toolchain 14.2.Rel1 for aarch64-none-elf, which is built with 1. A kernel build does not compile. 2. The sim link fails in CI. When the link goes through gcc or c++, as in the sim, the driver does not know 3. The task TLS slots can run out. Each variable takes one task TLS slot, and in a flat build the slots belong to the whole system and are not given back. With The same happens across programs. Five small programs with one ( 4. The pthread keys can run out too. Each variable also takes one pthread key in each task group, from the same This can come quickly in C++: libstdc++ built with thread support has One idea, in case it helps: one pthread key per task group that holds an array of the thread's copies, with an index per variable given out once from a global counter. That needs no task TLS slot and has no fixed limit, so it would also work in a kernel build. Happy to test a new version! Test programs (built-in apps, PROGNAME = tls5 tls1a tls1b tls1c tls1d tls1e)/* tls5.c */
#include <pthread.h>
#include <stdio.h>
static __thread int a = 1, b = 2, c = 3, d = 4, e = 5;
static void *worker(void *arg)
{
a = 10;
return (void *)(long)(a + b + c + d + e);
}
int main(int argc, char **argv)
{
pthread_t t;
void *r;
pthread_create(&t, NULL, worker, NULL);
pthread_join(t, &r);
printf("tls5: %s\n", (long)r == 24 && a == 1 ? "PASS" : "FAIL");
return 0;
}/* tls1a.c; tls1b.c to tls1e.c are the same with their own name */
#include <stdio.h>
static __thread int v = 1;
int main(int argc, char **argv)
{
v++;
printf("tls1a: %s\n", v == 2 ? "PASS" : "FAIL");
return 0;
} |
Summary
A GCC toolchain configured with --disable-tls compiles a thread_local or __thread variable into a call to __emutls_get_address(). The libgcc of such a toolchain is built with the single thread model, so it keeps one copy of each variable for the whole program and every thread reads and writes that same copy, which breaks the meaning of the keyword.
This adds libs/libc/tls/tls_emutls.c, which gives one copy per thread. A pthread key holds the copy and frees it when the thread exits. Because a pthread key belongs to one task group while the __emutls_object the compiler emits is a single global object shared by every task group of a flat build, the key is reached through two levels: obj->loc.offset holds a task local storage index allocated once for the variable, and the task local storage holds the pthread key of the task group, created on first use.
__emutls_register_common() is provided as well, with the same largest-wins merge as libgcc.
Impact
A thread_local variable now has one copy per thread on a toolchain without native TLS.
Known limitation: each variable costs one task local storage slot and one pthread key per task group, so the count is bounded by those two symbols, and a failed task_tls_alloc() is fatal because the caller the compiler emits cannot report an error.
Testing
Built on Ubuntu 22.04.5 (x86_64) with Arm GNU Toolchain 13.2.Rel1 (--disable-tls, the case this targets) and arm-none-eabi-gcc 10.3.1 (--enable-tls), NuttX master and nuttx-apps master.
lm3s6965-ek: libc.a contains tls_emutls.o exporting T __wrap___emutls_get_address and T __wrap___emutls_register_common, and both --wrap options appear in LDFLAGS and in build.ninja.