Make _Thread_local work across platforms

We now rewrite the binary image at runtime on Windows and XNU to change
mov %fs:0,%reg instructions to use %gs instead. There's also simpler
threading API introduced by this change and it's called _spawn() and
_join(), which has replaced most clone() usage.
This commit is contained in:
Justine Tunney 2022-07-10 04:01:17 -07:00
parent e4d6e263d4
commit 5f4f6b0e69
51 changed files with 808 additions and 1043 deletions

View file

@ -40,6 +40,7 @@
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/hyperion.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/time/time.h"
STATIC_YOINK("zip_uri_support");
@ -51,9 +52,6 @@ void PullSomeZipFilesIntoLinkage(void) {
gmtime(0);
}
char *stack[THREADS];
char tls[THREADS][64];
TEST(reservefd, testGrowthOfFdsDataStructure) {
int i, n;
struct rlimit rlim;
@ -87,7 +85,7 @@ void OnSigAlrm(int sig, siginfo_t *si, ucontext_t *ctx) {
close(fd); // can eintr which doesn't matter
}
int Worker(void *p) {
int Worker(void *p, int tid) {
char buf[64];
int i, rc, fd;
for (i = 0; i < 64; ++i) {
@ -111,6 +109,7 @@ int Worker(void *p) {
TEST(reservefd, tortureTest) {
int i;
struct spawn th[THREADS];
struct sigaction oldsa;
struct itimerval oldit;
struct itimerval it = {{0, 10000}, {0, 100}};
@ -119,17 +118,10 @@ TEST(reservefd, tortureTest) {
// ASSERT_SYS(0, 0, sigaction(SIGALRM, &sa, &oldsa));
// ASSERT_SYS(0, 0, setitimer(ITIMER_REAL, &it, &oldit));
for (i = 0; i < THREADS; ++i) {
clone(Worker,
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)),
GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | CLONE_SETTLS,
0, 0, __initialize_tls(tls[i]), sizeof(tls[i]),
(int *)(tls[i] + 0x38));
_spawn(Worker, 0, th + i);
}
for (i = 0; i < THREADS; ++i) {
_wait0((int *)(tls[i] + 0x38));
_join(th + i);
}
// EXPECT_SYS(0, 0, sigaction(SIGALRM, &oldsa, 0));
// EXPECT_SYS(0, 0, setitimer(ITIMER_REAL, &oldit, 0));

View file

@ -42,6 +42,7 @@ TEST_LIBC_CALLS_DIRECTDEPS = \
LIBC_STR \
LIBC_STUBS \
LIBC_SYSV \
LIBC_THREAD \
LIBC_TIME \
LIBC_TESTLIB \
LIBC_UNICODE \

View file

@ -43,7 +43,8 @@
*/
static uint64_t Rando(void) {
uint64_t x;
do x = lemur64();
do
x = lemur64();
while (((x ^ READ64LE("!!!!!!!!")) - 0x0101010101010101) &
~(x ^ READ64LE("!!!!!!!!")) & 0x8080808080808080);
return x;
@ -279,7 +280,7 @@ TEST(ksnprintf, testMisalignedPointer_wontFormat) {
TEST(ksnprintf, testUnterminatedOverrun_truncatesAtPageBoundary) {
char *m;
char b[32];
m = memset(mapanon(FRAMESIZE * 2), 1, FRAMESIZE);
m = memset(_mapanon(FRAMESIZE * 2), 1, FRAMESIZE);
EXPECT_SYS(0, 0, munmap(m + FRAMESIZE, FRAMESIZE));
EXPECT_EQ(12, ksnprintf(b, 32, "%'s", m + FRAMESIZE - 3));
EXPECT_STREQ("\\001\\001\\001", b);

View file

@ -41,32 +41,18 @@
#include "libc/sysv/consts/rlimit.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
#define THREADS 8
#define ITERATIONS 512
#define TLS_SIZE PAGESIZE
char *tls[THREADS];
char *stack[THREADS];
_Alignas(PAGESIZE) char tlsdata[THREADS * 3][TLS_SIZE];
int count;
_Atomic(int) started;
_Atomic(int) finished;
_Alignas(64) char slock;
pthread_mutex_t mylock;
__attribute__((__constructor__)) void init(void) {
int i;
__enable_tls();
__enable_threads();
for (i = 0; i < THREADS; ++i) {
CHECK_NE(-1, mprotect(tlsdata[i * 3 + 0], TLS_SIZE, PROT_NONE));
tls[i] = tlsdata[i * 3 + 1];
CHECK_NE(-1, mprotect(tlsdata[i * 3 + 2], TLS_SIZE, PROT_NONE));
}
}
struct spawn th[THREADS];
TEST(pthread_mutex_lock, normal) {
pthread_mutex_t lock;
@ -116,7 +102,7 @@ TEST(pthread_mutex_lock, errorcheck) {
__assert_disable = false;
}
int MutexWorker(void *p) {
int MutexWorker(void *p, int tid) {
int i;
++started;
for (i = 0; i < ITERATIONS; ++i) {
@ -124,7 +110,6 @@ int MutexWorker(void *p) {
++count;
pthread_mutex_unlock(&mylock);
}
ASSERT_NE(0, (int *)(tls[(intptr_t)p] + 0x38));
++finished;
return 0;
}
@ -140,29 +125,14 @@ TEST(pthread_mutex_lock, contention) {
started = 0;
finished = 0;
for (i = 0; i < THREADS; ++i) {
ASSERT_NE(MAP_FAILED,
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)));
ASSERT_NE(-1, clone(MutexWorker, stack[i], GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_CHILD_SETTID |
CLONE_CHILD_CLEARTID | CLONE_SETTLS,
(void *)(intptr_t)i, 0, __initialize_tls(tls[i]),
TLS_SIZE, (int *)(tls[i] + 0x38)));
ASSERT_SYS(0, 0, _spawn(MutexWorker, (void *)(intptr_t)i, th + i));
}
for (i = 0; i < THREADS; ++i) {
_wait0((int *)(tls[i] + 0x38));
ASSERT_EQ(0, *(int *)(tls[i] + 0x38));
}
for (i = 0; i < THREADS; ++i) {
ASSERT_EQ(0, *(int *)(tls[i] + 0x38));
ASSERT_SYS(0, 0, _join(th + i));
}
EXPECT_EQ(THREADS, started);
EXPECT_EQ(THREADS, finished);
EXPECT_EQ(THREADS * ITERATIONS, count);
for (i = 0; i < THREADS; ++i) {
ASSERT_SYS(0, 0, munmap(stack[i], GetStackSize()));
}
EXPECT_EQ(0, pthread_mutex_destroy(&mylock));
}
@ -177,29 +147,14 @@ TEST(pthread_mutex_lock, rcontention) {
started = 0;
finished = 0;
for (i = 0; i < THREADS; ++i) {
ASSERT_NE(MAP_FAILED,
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)));
ASSERT_NE(-1, clone(MutexWorker, stack[i], GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_CHILD_SETTID |
CLONE_CHILD_CLEARTID | CLONE_SETTLS,
(void *)(intptr_t)i, 0, __initialize_tls(tls[i]),
TLS_SIZE, (int *)(tls[i] + 0x38)));
ASSERT_NE(-1, _spawn(MutexWorker, (void *)(intptr_t)i, th + i));
}
for (i = 0; i < THREADS; ++i) {
_wait0((int *)(tls[i] + 0x38));
ASSERT_EQ(0, *(int *)(tls[i] + 0x38));
}
for (i = 0; i < THREADS; ++i) {
ASSERT_EQ(0, *(int *)(tls[i] + 0x38));
_join(th + i);
}
EXPECT_EQ(THREADS, started);
EXPECT_EQ(THREADS, finished);
EXPECT_EQ(THREADS * ITERATIONS, count);
for (i = 0; i < THREADS; ++i) {
ASSERT_SYS(0, 0, munmap(stack[i], GetStackSize()));
}
EXPECT_EQ(0, pthread_mutex_destroy(&mylock));
}
@ -214,33 +169,18 @@ TEST(pthread_mutex_lock, econtention) {
started = 0;
finished = 0;
for (i = 0; i < THREADS; ++i) {
ASSERT_NE(MAP_FAILED,
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)));
ASSERT_NE(-1, clone(MutexWorker, stack[i], GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_CHILD_SETTID |
CLONE_CHILD_CLEARTID | CLONE_SETTLS,
(void *)(intptr_t)i, 0, __initialize_tls(tls[i]),
TLS_SIZE, (int *)(tls[i] + 0x38)));
ASSERT_NE(-1, _spawn(MutexWorker, (void *)(intptr_t)i, th + i));
}
for (i = 0; i < THREADS; ++i) {
_wait0((int *)(tls[i] + 0x38));
ASSERT_EQ(0, *(int *)(tls[i] + 0x38));
}
for (i = 0; i < THREADS; ++i) {
ASSERT_EQ(0, *(int *)(tls[i] + 0x38));
_join(th + i);
}
EXPECT_EQ(THREADS, started);
EXPECT_EQ(THREADS, finished);
EXPECT_EQ(THREADS * ITERATIONS, count);
for (i = 0; i < THREADS; ++i) {
ASSERT_SYS(0, 0, munmap(stack[i], GetStackSize()));
}
EXPECT_EQ(0, pthread_mutex_destroy(&mylock));
}
int SpinlockWorker(void *p) {
int SpinlockWorker(void *p, int tid) {
int i;
++started;
for (i = 0; i < ITERATIONS; ++i) {
@ -248,7 +188,6 @@ int SpinlockWorker(void *p) {
++count;
_spunlock(&slock);
}
ASSERT_NE(0, (int *)(tls[(intptr_t)p] + 0x38));
++finished;
return 0;
}
@ -259,25 +198,14 @@ TEST(_spinlock, contention) {
started = 0;
finished = 0;
for (i = 0; i < THREADS; ++i) {
ASSERT_NE(MAP_FAILED,
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)));
ASSERT_NE(-1, clone(SpinlockWorker, stack[i], GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_CHILD_SETTID |
CLONE_CHILD_CLEARTID | CLONE_SETTLS,
(void *)(intptr_t)i, 0, __initialize_tls(tls[i]),
TLS_SIZE, (int *)(tls[i] + 0x38)));
ASSERT_NE(-1, _spawn(SpinlockWorker, (void *)(intptr_t)i, th + i));
}
for (i = 0; i < THREADS; ++i) {
_wait0((int *)(tls[i] + 0x38));
_join(th + i);
}
EXPECT_EQ(THREADS, started);
EXPECT_EQ(THREADS, finished);
EXPECT_EQ(THREADS * ITERATIONS, count);
for (i = 0; i < THREADS; ++i) {
ASSERT_SYS(0, 0, munmap(stack[i], GetStackSize()));
}
}
BENCH(pthread_mutex_lock, bench) {

View file

@ -35,6 +35,7 @@ TEST_LIBC_INTRIN_DIRECTDEPS = \
LIBC_STR \
LIBC_STUBS \
LIBC_SYSV \
LIBC_THREAD \
LIBC_TESTLIB \
LIBC_TINYMATH \
LIBC_UNICODE \

View file

@ -78,7 +78,7 @@ TEST(lz4, zoneFileGmt) {
size_t mapsize, gmtsize;
char *mapping, *gmtdata;
lz4decode((gmtdata = lz4decode(
(mapping = mapanon(
(mapping = _mapanon(
(mapsize = roundup(
LZ4_FRAME_BLOCKCONTENTSIZE(lz4check(dict.addr)) +
(gmtsize = LZ4_FRAME_BLOCKCONTENTSIZE(

View file

@ -37,6 +37,7 @@
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
#include "libc/time/time.h"
@ -54,7 +55,7 @@ dontinline void Generate(int i) {
A[i] = rand64();
}
int Thrasher(void *arg) {
int Thrasher(void *arg, int tid) {
int i, id = (intptr_t)arg;
while (!atomic_load(&ready)) {
cthread_memory_wait32(&ready, 0, 0);
@ -83,9 +84,8 @@ TEST(rand64, testLcg_doesntProduceIdenticalValues) {
TEST(rand64, testThreadSafety_doesntProduceIdenticalValues) {
int i, j, rc, ws;
sigset_t ss, oldss;
char *tls[THREADS];
void *stacks[THREADS];
struct sigaction oldsa;
struct spawn th[THREADS];
struct sigaction sa = {.sa_handler = OnChld, .sa_flags = SA_RESTART};
EXPECT_NE(-1, sigaction(SIGCHLD, &sa, &oldsa));
bzero(A, sizeof(A));
@ -94,25 +94,12 @@ TEST(rand64, testThreadSafety_doesntProduceIdenticalValues) {
EXPECT_EQ(0, sigprocmask(SIG_BLOCK, &ss, &oldss));
ready = false;
for (i = 0; i < THREADS; ++i) {
tls[i] = __initialize_tls(calloc(1, 64));
stacks[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(
-1,
clone(Thrasher, stacks[i], GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_SETTLS | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
(void *)(intptr_t)i, 0, tls[i], 64, (int *)(tls[i] + 0x38)));
ASSERT_SYS(0, 0, _spawn(Thrasher, (void *)(intptr_t)i, th + i));
}
atomic_store(&ready, 1);
cthread_memory_wake32(&ready, INT_MAX);
for (i = 0; i < THREADS; ++i) {
while ((j = atomic_load((uint32_t *)(tls[i] + 0x38)))) {
// FUTEX_WAIT_PRIVATE makes it hang
cthread_memory_wait32((int *)(tls[i] + 0x38), j, 0);
}
EXPECT_SYS(0, 0, munmap(stacks[i], GetStackSize()));
free(tls[i]);
ASSERT_SYS(0, 0, _join(th + i));
}
sigaction(SIGCHLD, &oldsa, 0);
sigprocmask(SIG_BLOCK, &oldss, 0);

View file

@ -23,9 +23,11 @@
#include "libc/intrin/spinlock.h"
#include "libc/intrin/wait0.internal.h"
#include "libc/log/backtrace.internal.h"
#include "libc/macros.internal.h"
#include "libc/mem/mem.h"
#include "libc/nexgen32e/gettls.h"
#include "libc/nexgen32e/nexgen32e.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/stack.h"
#include "libc/runtime/symbols.internal.h"
@ -36,10 +38,10 @@
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/time/time.h"
char *stack, *tls;
int x, me, tid, *childetid;
int x, me, tid;
_Atomic(int) thechilde;
__attribute__((__constructor__)) static void init(void) {
@ -47,47 +49,38 @@ __attribute__((__constructor__)) static void init(void) {
errno = 0;
}
void *__initialize_tls(char tib[64]) {
if (tib) {
*(intptr_t *)(tib + 0x00) = (intptr_t)tib;
*(intptr_t *)(tib + 0x30) = (intptr_t)tib;
*(int *)(tib + 0x38) = -1; // tid
*(int *)(tib + 0x3c) = 0;
}
return tib;
}
void SetUp(void) {
x = 0;
me = gettid();
tls = calloc(1, 64);
__initialize_tls(tls);
*(int *)(tls + 0x3c) = 31337;
childetid = (int *)(tls + 0x38);
ASSERT_NE(MAP_FAILED, (stack = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)));
}
void TearDown(void) {
EXPECT_SYS(0, 0, munmap(stack, GetStackSize()));
free(tls);
}
int DoNothing(void *arg) {
return 0;
}
////////////////////////////////////////////////////////////////////////////////
// TEST ERROR NUMBERS
TEST(clone, testNullFunc_raisesEinval) {
EXPECT_SYS(EINVAL, -1,
clone(0, stack, GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_SETTLS,
0, 0, tls, 64, 0));
}
////////////////////////////////////////////////////////////////////////////////
// TEST THREADS WORK
int CloneTest1(void *arg) {
int CloneTest1(void *arg, int tid) {
intptr_t rsp, top, bot;
CheckStackIsAligned();
// PrintBacktraceUsingSymbols(2, __builtin_frame_address(0),
// GetSymbolTable());
rsp = (intptr_t)__builtin_frame_address(0);
bot = (intptr_t)stack;
bot = ROUNDDOWN((intptr_t)rsp, GetStackSize());
top = bot + GetStackSize();
ASSERT_GT(rsp, bot); // check we're on stack
ASSERT_LT(rsp, top); // check we're on stack
@ -95,28 +88,16 @@ int CloneTest1(void *arg) {
ASSERT_TRUE(IS2POW(GetStackSize()));
ASSERT_EQ(0, bot & (GetStackSize() - 1));
x = 42;
if (!IsWindows()) {
ASSERT_EQ(31337, errno);
} else {
errno = 31337;
ASSERT_EQ(31337, errno);
}
ASSERT_EQ(23, (intptr_t)arg);
ASSERT_NE(gettid(), getpid());
ASSERT_EQ(gettid(), *childetid); // CLONE_CHILD_SETTID
return 0;
}
TEST(clone, test1) {
int ptid = 0;
*childetid = -1;
ASSERT_NE(-1, (tid = clone(CloneTest1, stack, GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES |
CLONE_SIGHAND | CLONE_PARENT_SETTID |
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID |
CLONE_SETTLS,
(void *)23, &ptid, tls, 64, childetid)));
_wait0(childetid); // CLONE_CHILD_CLEARTID
struct spawn th;
ASSERT_SYS(0, 0, _spawn(CloneTest1, (void *)23, &th));
ASSERT_SYS(0, 0, _join(&th));
ASSERT_NE(gettid(), tid);
ASSERT_EQ(tid, ptid);
ASSERT_EQ(42, x);
@ -132,7 +113,7 @@ TEST(clone, test1) {
_Atomic(int) sysbarrier;
int CloneTestSys(void *arg) {
int CloneTestSys(void *arg, int tid) {
int i, id = (intptr_t)arg;
CheckStackIsAligned();
while (!sysbarrier) asm("pause");
@ -165,25 +146,14 @@ int CloneTestSys(void *arg) {
TEST(clone, tlsSystemCallsErrno_wontClobberMainThreadBecauseTls) {
int i;
char *tls[8], *stack[8];
struct spawn th[8];
ASSERT_EQ(0, errno);
for (i = 0; i < 8; ++i) {
tls[i] = __initialize_tls(malloc(64));
stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0);
ASSERT_NE(
-1,
(tid = clone(
CloneTestSys, stack[i], GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | CLONE_SETTLS,
(void *)(intptr_t)i, 0, tls[i], 64, (int *)(tls[i] + 0x38))));
ASSERT_SYS(0, 0, _spawn(CloneTestSys, (void *)(intptr_t)i, th + i));
}
sysbarrier = 1;
for (i = 0; i < 8; ++i) {
_wait0((int *)(tls[i] + 0x38));
free(tls[i]);
munmap(stack[i], GetStackSize());
ASSERT_SYS(0, 0, _join(th + i));
}
ASSERT_EQ(0, errno);
}

View file

@ -119,7 +119,7 @@ TEST(mprotect, testSegfault_writeToReadOnlyAnonymous) {
}
TEST(mprotect, testExecOnly_canExecute) {
char *p = mapanon(FRAMESIZE);
char *p = _mapanon(FRAMESIZE);
void (*f)(void) = (void *)p;
p[0] = 0xC3; // RET
ASSERT_SYS(0, 0, mprotect(p, FRAMESIZE, PROT_EXEC | PROT_READ));

View file

@ -37,6 +37,7 @@ TEST_LIBC_RUNTIME_DIRECTDEPS = \
LIBC_STR \
LIBC_STUBS \
LIBC_SYSV \
LIBC_THREAD \
LIBC_TESTLIB \
LIBC_TINYMATH \
LIBC_UNICODE \

View file

@ -28,6 +28,7 @@
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/prot.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/x/x.h"
#define THREADS 32
@ -46,10 +47,9 @@ union Dub {
double x;
};
char *stack[THREADS];
char tls[THREADS][64];
struct spawn th[THREADS];
int Worker(void *p) {
int Worker(void *p, int tid) {
int i;
char str[64];
for (i = 0; i < 256; ++i) {
@ -63,17 +63,10 @@ int Worker(void *p) {
TEST(dtoa, test) {
int i;
for (i = 0; i < THREADS; ++i) {
clone(Worker,
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)),
GetStackSize(),
CLONE_THREAD | CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | CLONE_SETTLS,
0, 0, __initialize_tls(tls[i]), sizeof(tls[i]),
(int *)(tls[i] + 0x38));
_spawn(Worker, 0, th + i);
}
for (i = 0; i < THREADS; ++i) {
_wait0((int *)(tls[i] + 0x38));
_join(th + i);
}
}

View file

@ -37,6 +37,7 @@ TEST_LIBC_STDIO_DIRECTDEPS = \
LIBC_SYSV \
LIBC_TINYMATH \
LIBC_TESTLIB \
LIBC_THREAD \
LIBC_TIME \
LIBC_LOG \
LIBC_UNICODE \

View file

@ -1,100 +0,0 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
#include "libc/time/time.h"
static _Thread_local int tdata = 31337;
static _Thread_local int tbss;
static void *ReturnArg(void *arg) {
return arg;
}
TEST(cthread_create, testJoinDeadlock) {
ASSERT_SYS(0, EDEADLK, cthread_join(cthread_self(), 0));
}
TEST(cthread_create, testCreateReturnJoin) {
if (IsOpenbsd()) return; // TODO(jart): flakes
void *exitcode;
cthread_t thread;
ASSERT_EQ(0, cthread_create(&thread, 0, ReturnArg, ReturnArg));
ASSERT_EQ(0, cthread_join(thread, &exitcode));
ASSERT_EQ(ReturnArg, exitcode);
}
static void *ExitArg(void *arg) {
cthread_exit(arg);
}
TEST(cthread_create, testCreateExitJoin) {
if (IsOpenbsd()) return; // TODO(jart): flakes
void *exitcode;
cthread_t thread;
ASSERT_EQ(0, cthread_create(&thread, 0, ExitArg, (void *)-31337));
ASSERT_EQ(0, cthread_join(thread, &exitcode));
ASSERT_EQ((void *)-31337, exitcode);
}
TEST(gcctls, size) {
if (IsXnu()) return; // TODO(jart): codemorph
if (IsWindows()) return; // TODO(jart): codemorph
if (IsOpenbsd()) return; // TODO(jart): flakes
// schlep in .zip section too
// make sure executable isn't too huge
size_t size;
int64_t x = 0;
gmtime(&x);
ASSERT_LT((uintptr_t)_tls_size, 8192);
size = GetFileSize(GetProgramExecutableName());
if (IsTiny()) {
ASSERT_LT(size, 200 * 1024);
} else if (IsModeDbg() || IsAsan()) {
ASSERT_LT(size, 4 * 1024 * 1024);
} else {
ASSERT_LT(size, 500 * 1024);
}
}
static void *TlsWorker(void *arg) {
ASSERT_EQ(31337, tdata);
ASSERT_EQ(0, tbss);
return 0;
}
TEST(gcctls, worksAndIsNonInheritable) {
if (IsXnu()) return; // TODO(jart): codemorph
if (IsWindows()) return; // TODO(jart): codemorph
if (IsOpenbsd()) return; // TODO(jart): flakes
void *exitcode;
cthread_t thread;
ASSERT_EQ(tdata, 31337);
ASSERT_EQ(tbss, 0);
tdata = 1337;
tbss = 1337;
ASSERT_EQ(0, cthread_create(&thread, 0, TlsWorker, (void *)-31337));
ASSERT_EQ(0, cthread_join(thread, &exitcode));
ASSERT_EQ(NULL, exitcode);
}

View file

@ -16,22 +16,15 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/testlib.h"
char tib[64];
_Thread_local char x;
static _Thread_local char y;
TEST(gettid, test) {
if (IsLinux()) EXPECT_EQ(getpid(), gettid());
if (IsNetbsd()) EXPECT_EQ(1, gettid());
char ha(void) {
++y;
return x;
}
BENCH(gettid, bench) {
int gettid_(void) asm("gettid");
EZBENCH2("gettid (single threaded)", donothing, gettid());
__install_tls(__initialize_tls(tib));
EZBENCH2("gettid (tls enabled)", donothing, gettid());
char ya(void) {
return y;
}

View file

@ -16,25 +16,42 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/bits/atomic.h"
#include "libc/calls/calls.h"
#include "libc/errno.h"
#include "libc/nexgen32e/gettls.h"
#include "libc/nexgen32e/threaded.h"
#include "libc/runtime/runtime.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/macros.internal.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/spawn.h"
#include "libc/thread/thread.h"
static char tib[64];
#define N 128
TEST(tls, test) {
errno = 31337;
EXPECT_EQ(31337, errno);
EXPECT_EQ(&__errno, __errno_location());
__initialize_tls(tib);
*(int *)((char *)tib + 0x38) = gettid();
*(int *)((char *)tib + 0x3c) = __errno;
__install_tls(tib);
EXPECT_EQ(31337, errno);
EXPECT_EQ(tib, __get_tls());
EXPECT_EQ(tib, __get_tls_inline());
EXPECT_EQ(tib + 0x3c, (char *)__errno_location());
struct spawn t[N];
_Atomic(int) itworked;
_Thread_local int var;
int Worker(void *arg, int tid) {
int i = (long)arg;
ASSERT_EQ(0, var++);
ASSERT_EQ(gettid(), tid);
ASSERT_EQ(1, var++);
ASSERT_EQ(sys_gettid(), tid);
ASSERT_EQ(2, var++);
itworked++;
return 0;
}
TEST(_spawn, test) {
long i;
for (i = 0; i < N; ++i) EXPECT_SYS(0, 0, _spawn(Worker, (void *)i, t + i));
for (i = 0; i < N; ++i) EXPECT_SYS(0, 0, _join(t + i));
for (i = 0; i < N; ++i) EXPECT_SYS(0, 0, _join(t + i));
EXPECT_EQ(N, itworked);
}
__attribute__((__constructor__)) static void init(void) {
pledge("stdio rpath thread", 0);
errno = 0;
}