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

@ -43,6 +43,7 @@
#include "libc/sysv/consts/sock.h"
#include "libc/sysv/consts/sol.h"
#include "libc/sysv/consts/tcp.h"
#include "libc/thread/spawn.h"
#include "libc/time/struct/tm.h"
#include "libc/time/time.h"
#include "net/http/http.h"
@ -106,7 +107,7 @@ _Atomic(int) connections;
_Atomic(int) closingtime;
const char *volatile status;
int Worker(void *id) {
int Worker(void *id, int tid) {
int server, yes = 1;
// load balance incoming connections for port 8080 across all threads
@ -273,8 +274,7 @@ void PrintStatus(void) {
int main(int argc, char *argv[]) {
int i;
char **tls;
char **stack;
struct spawn *th;
uint32_t *hostips;
// ShowCrashReports();
@ -293,36 +293,23 @@ int main(int argc, char *argv[]) {
PORT);
}
// spawn over 9,000 worker threads
tls = 0;
stack = 0;
threads = argc > 1 ? atoi(argv[1]) : GetCpuCount();
if ((1 <= threads && threads <= INT_MAX) &&
(tls = malloc(threads * sizeof(*tls))) &&
(stack = malloc(threads * sizeof(*stack)))) {
for (i = 0; i < threads; ++i) {
if ((tls[i] = __initialize_tls(malloc(64))) &&
(stack[i] = mmap(0, GetStackSize(), PROT_READ | PROT_WRITE,
MAP_STACK | MAP_ANONYMOUS, -1, 0)) != MAP_FAILED) {
++workers;
if (clone(Worker, stack[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)) == -1) {
--workers;
kprintf("error: clone(%d) failed %m\n", i);
}
} else {
kprintf("error: mmap(%d) failed %m\n", i);
}
if (!(i % 500)) {
PrintStatus();
}
}
} else {
if ((1 <= threads && threads <= 100000)) {
kprintf("error: invalid number of threads\n");
exit(1);
}
// spawn over 9,000 worker threads
th = calloc(threads, sizeof(*th));
for (i = 0; i < threads; ++i) {
++workers;
if (_spawn(Worker, (void *)(intptr_t)i, th + i) == -1) {
--workers;
kprintf("error: _spawn(%d) failed %m\n", i);
}
if (!(i % 500)) {
PrintStatus();
}
}
// wait for workers to terminate
@ -335,17 +322,11 @@ int main(int argc, char *argv[]) {
kprintf("\r\e[K");
// join the workers
// this is how we guarantee stacks are safe to free
if (tls && stack) {
for (i = 0; i < threads; ++i) {
_wait0((int *)(tls[i] + 0x38));
munmap(stack[i], GetStackSize());
free(tls[i]);
}
for (i = 0; i < threads; ++i) {
_join(th + i);
}
// clean up memory
free(hostips);
free(stack);
free(tls);
free(th);
}

View file

@ -1,70 +0,0 @@
#if 0
/*─────────────────────────────────────────────────────────────────╗
To the extent possible under law, Justine Tunney has waived
all copyright and related or neighboring rights to this file,
as it is written in the following disclaimers:
http://unlicense.org/ │
http://creativecommons.org/publicdomain/zero/1.0/ │
*/
#endif
#include "libc/calls/calls.h"
#include "libc/dce.h"
#include "libc/intrin/kprintf.h"
#include "libc/log/log.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/thread/thread.h"
#include "libc/time/time.h"
cthread_sem_t semaphore;
_Thread_local int test_tls = 0x12345678;
static void *worker(void *arg) {
int tid;
cthread_t self;
cthread_sem_signal(&semaphore);
self = cthread_self();
tid = self->tid;
printf("[%p] %d -> %#x\n", self, tid, test_tls);
if (test_tls != 0x12345678) {
printf(".tdata test #2 failed\n");
}
return (void *)4;
}
int main() {
int rc, tid;
void *exitcode;
cthread_t self, thread;
if (IsWindows() || IsXnu()) {
fprintf(stderr,
"error: can't run example\n"
"_Thread_local only works on Linux/FreeBSD/NetBSD/OpenBSD\n");
return 1;
}
self = cthread_self();
tid = self->tid;
printf("[%p] %d -> %#x\n", self, tid, test_tls);
if (test_tls != 0x12345678) {
printf(".tdata test #1 failed\n");
}
cthread_sem_init(&semaphore, 0);
rc = cthread_create(&thread, NULL, &worker, NULL);
if (rc == 0) {
cthread_sem_wait(&semaphore, 0, NULL);
printf("thread created: %p\n", thread);
#if 1
cthread_join(thread, &exitcode);
#else
exitcode = cthread_detach(thread);
#endif
cthread_sem_signal(&semaphore);
cthread_sem_wait(&semaphore, 0, NULL);
printf("thread joined: %p -> %p\n", thread, exitcode);
} else {
fprintf(stderr, "ERROR: thread could not be started: %d\n", rc);
}
return 0;
}