feat(thread): futex + TLS + pthread_self substrate

This commit is contained in:
2026-09-08 15:02:26 -04:00
parent 49968bdbfc
commit 0d07a914a1
10 changed files with 551 additions and 0 deletions
+6
View File
@@ -73,6 +73,10 @@ VLIBC_CORE_SRCS = \
src/start/exit.c \
src/start/environ.c \
src/start/tls.c \
src/thread/futex.c \
src/thread/pthread_self.c \
src/thread/tls.c \
src/thread/x86_64/__set_thread_area.s \
src/errno/strerror.c \
src/setjmp/x86_64/setjmp.s \
src/setjmp/x86_64/longjmp.s \
@@ -693,6 +697,7 @@ EXTRA_DIST = \
arch/x86_64/syscall_arch.h \
src/internal/atomic.h \
src/internal/errno.h \
src/internal/futex.h \
src/internal/libc.h \
src/internal/malloc.h \
src/internal/strtox.h \
@@ -700,6 +705,7 @@ EXTRA_DIST = \
src/internal/types.h \
src/start/start.h \
src/start/tcb.h \
src/thread/pthread_impl.h \
src/dirent/dirent_impl.h \
src/process/atfork_impl.h \
src/stat/stat_impl.h \
+68
View File
@@ -12,6 +12,14 @@
* All wrappers are sequentially consistent by default: a libc-internal
* operation is almost never hot enough to justify weakening, and a single
* memory order everywhere is the least error-prone choice.
*
* The fetch_or / fetch_and / fetch_sub family and the add_fetch / sub_fetch
* result-returning forms were added by todo 44 for the futex lock words of
* the pthread layer (#46-#48): futex wait words are built with or/and
* (musl's a_or/a_and) and waiter counts with add/sub that return the NEW
* value (musl's a_inc/a_dec pair). The fetch_* forms return the OLD value,
* matching the C11/GCC __atomic_fetch_* convention; the *_fetch forms
* return the NEW one.
*/
static inline int
@@ -32,6 +40,36 @@ atomic_fetch_add(volatile int *p, int v) // NOLINT(readability-non-const-paramet
return __atomic_fetch_add(p, v, __ATOMIC_SEQ_CST);
}
static inline int
atomic_add_fetch(volatile int *p, int v) // NOLINT(readability-non-const-parameter)
{
return __atomic_add_fetch(p, v, __ATOMIC_SEQ_CST);
}
static inline int
atomic_fetch_sub(volatile int *p, int v) // NOLINT(readability-non-const-parameter)
{
return __atomic_fetch_sub(p, v, __ATOMIC_SEQ_CST);
}
static inline int
atomic_sub_fetch(volatile int *p, int v) // NOLINT(readability-non-const-parameter)
{
return __atomic_sub_fetch(p, v, __ATOMIC_SEQ_CST);
}
static inline int
atomic_fetch_or(volatile int *p, int v) // NOLINT(readability-non-const-parameter)
{
return __atomic_fetch_or(p, v, __ATOMIC_SEQ_CST);
}
static inline int
atomic_fetch_and(volatile int *p, int v) // NOLINT(readability-non-const-parameter)
{
return __atomic_fetch_and(p, v, __ATOMIC_SEQ_CST);
}
static inline int
atomic_exchange(volatile int *p, int v) // NOLINT(readability-non-const-parameter)
{
@@ -71,6 +109,36 @@ atomic_fetch_add_l(volatile long *p, long v) // NOLINT(readability-non-const-par
return __atomic_fetch_add(p, v, __ATOMIC_SEQ_CST);
}
static inline long
atomic_add_fetch_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter)
{
return __atomic_add_fetch(p, v, __ATOMIC_SEQ_CST);
}
static inline long
atomic_fetch_sub_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter)
{
return __atomic_fetch_sub(p, v, __ATOMIC_SEQ_CST);
}
static inline long
atomic_sub_fetch_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter)
{
return __atomic_sub_fetch(p, v, __ATOMIC_SEQ_CST);
}
static inline long
atomic_fetch_or_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter)
{
return __atomic_fetch_or(p, v, __ATOMIC_SEQ_CST);
}
static inline long
atomic_fetch_and_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter)
{
return __atomic_fetch_and(p, v, __ATOMIC_SEQ_CST);
}
static inline long
atomic_exchange_l(volatile long *p, long v) // NOLINT(readability-non-const-parameter)
{
+101
View File
@@ -0,0 +1,101 @@
#ifndef VLIBC_INTERNAL_FUTEX_H
#define VLIBC_INTERNAL_FUTEX_H
/*
* vlibc — internal futex substrate (todo 44).
*
* Linux futex operations over SYS_futex, the kernel's process-shared sleep
* primitive that every lock, condvar, barrier, and rwlock in the library
* rests on. Two calling conventions are deliberately provided:
*
* - __futex() is the RAW seam: it returns exactly what the kernel put in
* rax (0 or a positive value on success, a NEGATIVE errno on failure)
* and never touches errno. Consumers that need custom error handling
* (retry-on-EINTR, -ENOSYS fallbacks, or errno-code returns) use this.
*
* - __futex_wait() / __futex_timedwait() / __futex_wake() are the mapped
* wrappers in the house syscall_ret() convention: 0 (or the number of
* waiters woken) on success, -1 with errno set on failure. These are
* what the pthread layer (#45-#48) calls.
*
* Op codes and flags are kernel UAPI constants (linux/futex.h); the values
* are pinned below so this header stays self-contained. FUTEX_PRIVATE adds
* no extra atomics: the wait word is shared only between threads of one
* process. The ancient-kernel ENOSYS fallback path musl carries (retry
* without FUTEX_PRIVATE) is deliberately NOT implemented — every kernel
* since 2.6.22 handles the private flag, and the plan targets modern
* x86_64 only.
*
* Timeout semantics: __futex_timedwait() takes an ABSOLUTE deadline and
* uses FUTEX_WAIT_BITSET (kernel-compared, no userspace clock read):
* CLOCK_MONOTONIC by default, CLOCK_REALTIME when the FUTEX_CLOCK_REALTIME
* flag is added. A deadline already in the past returns -1/ETIMEDOUT from
* the kernel immediately. musl's relative-timeout __timedwait shape is not
* copied: it exists there only to keep pre-WAIT_BITSET kernels working.
*/
#include <time.h>
#include "../internal/libc.h"
/* futex op codes (linux/futex.h). */
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_FD 2
#define FUTEX_REQUEUE 3
#define FUTEX_CMP_REQUEUE 4
#define FUTEX_WAKE_OP 5
#define FUTEX_LOCK_PI 6
#define FUTEX_UNLOCK_PI 7
#define FUTEX_TRYLOCK_PI 8
#define FUTEX_WAIT_BITSET 9
/* op flags (linux/futex.h). */
#define FUTEX_PRIVATE 128
#define FUTEX_CLOCK_REALTIME 256
/* bitset for "any bits match" (linux/futex.h). */
#define FUTEX_BITSET_MATCH_ANY 0xffffffffu
/*
* The __futex family lives in the implementation-reserved namespace, same
* as the __syscall<n> seam — intentional, waived here for the whole block.
*/
// NOLINTBEGIN(bugprone-reserved-identifier)
/*
* Raw futex syscall: return the kernel's raw result — 0 or a positive value
* on success, a negative errno on failure. errno is never touched. op may
* be any FUTEX_* op code; ts is the timeout pointer for FUTEX_WAIT /
* FUTEX_WAIT_BITSET, NULL for the others.
*/
hidden long
__futex(volatile int *addr, int op, int val, void *ts);
/*
* Mapped wait: block until *addr != val or a wakeup arrives. Returns 0 on
* wakeup, -1 with errno set on failure. priv != 0 selects FUTEX_PRIVATE.
*/
hidden int
__futex_wait(volatile int *addr, int val, int priv);
/*
* Mapped timed wait: like __futex_wait, but with an ABSOLUTE deadline on
* clock clk (CLOCK_MONOTONIC or CLOCK_REALTIME). Returns 0 on wakeup, -1
* with errno set (ETIMEDOUT when the deadline passes, EINTR when a signal
* interrupts the wait) on failure.
*/
hidden int
__futex_timedwait(volatile int *addr, int val, int priv, int clk, const struct timespec *at);
/*
* Mapped wake: wake up to cnt waiters on *addr (cnt < 0 = wake all, the
* INT_MAX kernel convention musl uses). Returns the number of waiters
* actually woken, or -1 with errno set on failure.
*/
hidden int
__futex_wake(volatile int *addr, int cnt, int priv);
// NOLINTEND(bugprone-reserved-identifier)
#endif /* VLIBC_INTERNAL_FUTEX_H */
+30
View File
@@ -74,6 +74,9 @@
#include "../internal/errno.h" /* VLIBC_TCB_ERRNO_OFF */
#include <stddef.h>
#include <stdint.h>
#define VLIBC_TCB_SELF_OFF 0
/* VLIBC_TCB_ERRNO_OFF (8) is consumed from src/internal/errno.h. */
@@ -84,4 +87,31 @@
#define VLIBC_TCB_SIZE 24
/*
* New-thread TLS geometry (todo 44). All four are set by init_main_tls()
* in src/start/tls.c and read by src/thread/tls.c (__vlibc_tls_setup):
*
* __vlibc_static_tls_size PT_TLS p_memsz of the main executable — the
* one DEFAULT-VISIBILITY EXPORTED symbol of
* the group: #62 grows it on dlopen, #45 reads
* it when sizing new-thread TLS blocks.
* __vlibc_static_tls_filesz PT_TLS p_filesz (.tdata part).
* __vlibc_static_tls_align PT_TLS p_align (16 when there is no PT_TLS).
* __vlibc_static_tls_template the PRIMAL TLS image address: the mapped
* .tdata at p_vaddr, never the main thread's
* LIVE TLS block (which mutates). In the
* file-fallback path it is the main block
* itself — the only copy that exists then.
*
* The three hidden ones keep their attribute on the definitions (as with
* environ's hidden alias in src/start/environ.c); __vlibc_static_tls_size
* deliberately carries none so it stays an exported ABI object.
*/
// NOLINTBEGIN(bugprone-reserved-identifier)
extern size_t __vlibc_static_tls_size;
extern uintptr_t __vlibc_static_tls_template;
extern size_t __vlibc_static_tls_filesz;
extern size_t __vlibc_static_tls_align;
// NOLINTEND(bugprone-reserved-identifier)
#endif /* VLIBC_INTERNAL_START_TCB_H */
+24
View File
@@ -76,6 +76,17 @@ struct elf64_phdr
uint64_t p_align;
};
/*
* New-thread TLS geometry, consumed by src/thread/tls.c (todo 44) — see
* tcb.h for the full contract. __vlibc_static_tls_size is the EXPORTED
* default-visibility object (no attribute!); the other three are internal
* and hidden.
*/
size_t __vlibc_static_tls_size = 0;
hidden uintptr_t __vlibc_static_tls_template = 0;
hidden size_t __vlibc_static_tls_filesz = 0;
hidden size_t __vlibc_static_tls_align = 16;
/*
* Round v up to a multiple of a. a must be a power of two, which ELF
* p_align of a PT_TLS segment always is.
@@ -253,6 +264,13 @@ init_main_tls(char **envp)
align = 16;
}
/* Record the geometry for new-thread TLS blocks (#44/#45) BEFORE the
* template address is known: size/filesz/align are PT_TLS facts, the
* template address depends on the mmap below. */
__vlibc_static_tls_size = memsz;
__vlibc_static_tls_filesz = filesz;
__vlibc_static_tls_align = align;
/*
* Single allocation: TLS image at the base, TCB at tp. tp uses exactly
* p_align (never bumped): the linker bakes tpoff against
@@ -273,6 +291,12 @@ init_main_tls(char **envp)
tp = base + block;
image = (char *)base;
/* The PRIMAL image is the new-thread template: the still-pristine
* .tdata at p_vaddr when mapped, else this block (the file-fallback
* path leaves no other copy). Never the main thread's live TLS — its
* mutations must not leak into threads created later. */
__vlibc_static_tls_template = mapped ? vaddr : base;
if (filesz > 0)
{
copy_tls_image(image, offset, vaddr, filesz, mapped);
+71
View File
@@ -0,0 +1,71 @@
#include <errno.h>
#include <stddef.h>
#include <time.h>
#include "../internal/futex.h"
#include "../internal/syscall.h"
/*
* vlibc — futex implementation (todo 44).
*
* The raw seam returns exactly what SYS_futex left in rax (0/positive on
* success, negative errno on failure); the mapped wrappers translate that
* through syscall_ret() into the house convention (0/value or -1 with
* errno set). __futex_wake maps a negative cnt to INT_MAX before the
* syscall: the kernel's futex_wake_mark stops scanning when the count it
* has already woken exceeds nr_wake, so -1 would stop after ONE waiter,
* while INT_MAX is the "wake all" sentinel musl uses (after musl
* src/thread/__futex.c + pthread_impl.h's __wake).
*/
long
__futex(volatile int *addr, int op, int val, void *ts)
{
return __syscall4(SYS_futex, (long)addr, op, val, (long)ts);
}
int
__futex_wait(volatile int *addr, int val, int priv)
{
long r = __futex(addr, FUTEX_WAIT | (priv ? FUTEX_PRIVATE : 0), val, 0);
/* The kernel reports a value mismatch as -EAGAIN: the wake condition
* already holds, nothing to wait for. That is success for every
* futex consumer, not an error (musl's __timedwait applies the same
* rule). */
if (r == -EAGAIN)
{
return 0;
}
return syscall_ret(r);
}
// NOLINTBEGIN(bugprone-easily-swappable-parameters)
int
__futex_timedwait(volatile int *addr, int val, int priv, int clk, const struct timespec *at)
{
int op = FUTEX_WAIT_BITSET | (priv ? FUTEX_PRIVATE : 0);
if (clk == CLOCK_REALTIME)
{
op |= FUTEX_CLOCK_REALTIME;
}
long r = __syscall6(SYS_futex, (long)addr, op, val, (long)at, 0, FUTEX_BITSET_MATCH_ANY);
if (r == -EAGAIN)
{
return 0; /* value already differs: condition holds, see above */
}
return syscall_ret(r);
}
// NOLINTEND(bugprone-easily-swappable-parameters)
int
__futex_wake(volatile int *addr, int cnt, int priv)
{
if (cnt < 0)
{
cnt = 0x7fffffff; /* wake-all sentinel, see the file comment */
}
return syscall_ret(__futex(addr, FUTEX_WAKE | (priv ? FUTEX_PRIVATE : 0), cnt, 0));
}
+109
View File
@@ -0,0 +1,109 @@
#ifndef VLIBC_THREAD_PTHREAD_IMPL_H
#define VLIBC_THREAD_PTHREAD_IMPL_H
/*
* vlibc — internal threading substrate (todo 44).
*
* Everything the pthread layer (#45 and later) needs that is NOT itself a
* public pthread_* API: the TCB accessor, the thread-pointer installer, the
* new-thread TLS block builder, the futex seam, and the shared static-TLS
* size symbol. The public pthread_* functions are built by #45 on top of
* exactly these primitives; nothing here is exported from the shared
* library (every function is hidden, and __vlibc_static_tls_size — the one
* symbol with default visibility — is a data object, not an API).
*
* Ownership notes for the follow-up todos:
*
* - #45 defines pthread_t and the public pthread_self(); its
* implementation simply returns __pthread_self(). musl's
* cancellation-aware __syscall_cp/syscall_cp.s seam is NOT provided
* here: cancellation points are #45's concept, and a syscall_cp here
* would have to reference a __cancel symbol that does not exist yet.
* - #46-#48 use __futex_wait/__futex_timedwait/__futex_wake and the
* atomic.h extension for their lock words.
* - #45's pthread_create maps a fresh stack + TLS block, calls
* __vlibc_tls_setup(base) to copy the template and get the TP, and
* installs it in the new thread via __set_thread_area().
*
* TP discipline (authoritative layout in src/start/tcb.h): the thread
* pointer points AT the TCB (ELF TLS variant II), slot 0 holds the TCB's
* own address, and static TLS data live at negative offsets. Every
* primitive here respects that contract and never re-derives it.
*/
#include <stddef.h>
#include "../internal/atomic.h"
#include "../internal/futex.h"
#include "../internal/libc.h"
#include "../start/tcb.h"
/*
* The names below live in the implementation-reserved namespace (the
* __pthread_self/__set_thread_area pair by musl parity); the waiver covers
* exactly those, nothing else.
*/
// NOLINTBEGIN(bugprone-reserved-identifier)
/*
* Internal TCB accessor: returns the calling thread's TCB address, which
* is the thread pointer itself (slot 0 of the TCB holds exactly that
* value; __builtin_thread_pointer() reads %fs:0). Distinct and non-null
* for every thread whose FS base was installed by init_main_tls() or
* __set_thread_area(). The PUBLIC pthread_self() is #45's symbol; this
* one stays hidden so the two can never collide at the ABI level.
*/
hidden void *
__pthread_self(void);
/*
* Install the calling thread's thread pointer via arch_prctl(ARCH_SET_FS).
* Must run IN the thread whose FS base is being set (the arch_prctl
* operates on the current task). Returns the raw syscall result: 0 on
* success, a negative errno on failure — the caller decides how to report
* it (there is no errno write here). Implemented in assembly
* (src/thread/x86_64/__set_thread_area.s), after musl's file of the same
* name.
*/
hidden long
__set_thread_area(void *tp);
/*
* Initialize a fresh TCB at tp exactly as the authoritative tcb.h layout
* dictates: self pointer at slot 0, errno slot zeroed, and the per-thread
* fenv reservation reset to the x86 defaults (x87 control word 0x037F,
* MXCSR 0x1F80) — the same values init_main_tls() writes for the main
* thread, so #42's fenv functions (which read/write the slot through the
* fixed offsets) start from a defined state in every thread.
*/
hidden void
__vlibc_tcb_init(void *tp);
/*
* Build a new thread's static TLS block at base: copy the main
* executable's PT_TLS image from the stored template, zero the .tbss
* tail, initialize the TCB, and return the thread pointer. base must be a
* page-aligned region of at least round_up(__vlibc_static_tls_size,
* align) + VLIBC_TCB_SIZE bytes; the caller (#45's pthread_create) owns
* the mapping (stack + guard + TLS in one block, munmap'd at thread
* exit). The placement formula is exactly the one the linker bakes into
* every %fs-relative TLS offset — see tcb.h, and never round the
* alignment differently.
*/
hidden void *
__vlibc_tls_setup(void *base);
/*
* Size in bytes of the main executable's PT_TLS (p_memsz, i.e. .tdata +
* .tbss) for the STATIC no-ld.so case. Default visibility ON PURPOSE:
* this is exported ABI. init_main_tls() (src/start/tls.c) initializes it
* from the auxv program headers at startup; #62's dynamic loader is the
* sole runtime updater (it grows it on each successful dlopen), and #45
* reads it when sizing new-thread TLS blocks. In a static build there is
* no dlopen, so the startup value is the final one.
*/
extern size_t __vlibc_static_tls_size;
// NOLINTEND(bugprone-reserved-identifier)
#endif /* VLIBC_THREAD_PTHREAD_IMPL_H */
+25
View File
@@ -0,0 +1,25 @@
#include <stddef.h>
#include "pthread_impl.h"
/*
* vlibc — internal thread self-access (todo 44).
*
* The TCB lives at the thread pointer itself (variant II; the authoritative
* layout in src/start/tcb.h), and slot 0 of the TCB holds exactly that
* address because __builtin_thread_pointer() reads %fs:0. __pthread_self()
* is therefore a single load: it returns the calling thread's TCB address,
* distinct and non-null for every thread whose FS base was installed by
* init_main_tls() (main thread) or __set_thread_area() (new threads).
*
* This symbol is INTERNAL and hidden. The public pthread_self() belongs to
* todo 45 and is its own exported symbol; its implementation delegates to
* this accessor once pthread_t is defined there. Keeping the two names
* separate means this substrate cannot collide with #45's public ABI.
*/
void *
__pthread_self(void)
{
return __builtin_thread_pointer();
}
+83
View File
@@ -0,0 +1,83 @@
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "pthread_impl.h"
/*
* vlibc — new-thread TLS block builder (todo 44).
*
* The counterpart of init_main_tls() (src/start/tls.c) for THREADS: the
* caller (#45's pthread_create) maps a fresh block, this file turns it into
* a working per-thread TLS area. Two halves:
*
* - __vlibc_tcb_init(tp): fill the TCB fields at a fresh thread pointer.
* The fenv reservation is reset to the x86 defaults (x87 control word
* 0x037F, MXCSR 0x1F80) so #42's fenv functions observe a defined
* state in every thread — the same values init_main_tls() writes for
* the main thread. All offsets are the authoritative constants from
* src/start/tcb.h, never re-derived.
*
* - __vlibc_tls_setup(base): place the TLS image + TCB in base per the
* linker's placement contract (tp = base + round_up(memsz, align),
* with align EXACT — see tcb.h) and return the TP.
*
* The image source is the stored PRIMAL template (the executable's
* mapped .tdata at p_vaddr, recorded by init_main_tls in
* __vlibc_static_tls_template), NOT the main thread's live TLS block:
* copying the live block would leak the main thread's mutations into
* every new thread. The .tbss tail is zeroed explicitly.
*
* Thread pointer installation is deliberately NOT done here:
* __set_thread_area() must run in the new thread itself, so #45 calls it
* from the thread's entry point after __vlibc_tls_setup() returns.
*/
/*
* Round v up to a multiple of a. a must be a power of two, which the ELF
* p_align of a PT_TLS segment (and the no-TLS fallback of 16) always is.
*/
static size_t
round_up(size_t v, size_t a)
{
return (v + a - 1) & ~(a - 1);
}
void
__vlibc_tcb_init(void *tp)
{
char *t = tp;
((uintptr_t *)t)[VLIBC_TCB_SELF_OFF / sizeof(uintptr_t)] = (uintptr_t)tp;
*(int *)(t + VLIBC_TCB_ERRNO_OFF) = 0;
*(uint16_t *)(t + VLIBC_TCB_FENV_X87CW_OFF) = 0x037F; /* x87 reset default */
*(uint32_t *)(t + VLIBC_TCB_FENV_MXCSR_OFF) = 0x1F80; /* MXCSR reset default */
}
/*
* memcpy/memset from a raw template address: the insecureAPI check and the
* integer-to-pointer pessimization warning do not apply to a libc copying
* its own TLS image (the template is a stored address by design; boundary
* checks are the placement contract in tcb.h, not memcpy_s). The whole
* function is waived.
*/
// NOLINTBEGIN(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,performance-no-int-to-ptr)
void *
__vlibc_tls_setup(void *base)
{
char *block = base;
char *tp = block + round_up(__vlibc_static_tls_size, __vlibc_static_tls_align);
if (__vlibc_static_tls_filesz > 0)
{
memcpy(block, (const void *)__vlibc_static_tls_template, __vlibc_static_tls_filesz);
}
if (__vlibc_static_tls_size > __vlibc_static_tls_filesz)
{
memset(block + __vlibc_static_tls_filesz, 0,
__vlibc_static_tls_size - __vlibc_static_tls_filesz);
}
__vlibc_tcb_init(tp);
return tp;
}
// NOLINTEND(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,performance-no-int-to-ptr)
+34
View File
@@ -0,0 +1,34 @@
/*
* vlibc — x86_64 thread-pointer installer (todo 44).
*
* __set_thread_area(tp): install tp as the calling thread's FS base via
* arch_prctl(ARCH_SET_FS). After this, %fs-relative TLS offsets resolve
* against tp exactly as the linker baked them (see src/start/tcb.h for the
* placement contract), and __builtin_thread_pointer() / %fs:0 returns tp.
*
* Must be executed IN the thread whose FS base is being set: arch_prctl
* operates on the current task, so pthread_create (#45) calls this from the
* new thread's entry point, never from the parent.
*
* Return convention: the raw syscall result is left in %eax — 0 on success,
* a negative errno on failure. No errno write happens here; the caller maps
* the result however its context needs (this mirrors musl's
* src/thread/x86_64/__set_thread_area.s, which also returns raw).
*
* arch_prctl codes (kernel UAPI asm/prctl.h): ARCH_SET_FS 0x1002,
* SYS_arch_prctl 158. Kept as literals because there is no internal header
* for them yet; src/start/tls.c defines the same values locally.
*/
.text
.global __set_thread_area
.hidden __set_thread_area
.type __set_thread_area,@function
__set_thread_area:
mov %rdi,%rsi
movl $0x1002,%edi
movl $158,%eax
syscall
ret
.size __set_thread_area,.-__set_thread_area
.section .note.GNU-stack,"",@progbits