Update workq_kernreturn for some API changes

First, libpthread now expects `workq_kernreturn` not to return when called with `WQOPS_THREAD_RETURN`. We have to resume the thread and give it a special value for `nkevents` that tells it to kill itself.

Next, Apple has reworked the priority system into a "Quality of Service" (QoS) system. Unfortunately, since we're building libdispatch with a few different settings than Apple's configuration, it seems that they've forgotten to correctly set QoS in some cases, so we have to it for them so that libdispatch doesn't blow up.

Finally, `WQOPS_SETUP_DISPATCH` has been stubbed so libdispatch thinks everything is fine. In the future, we might want to do something with the values it passes in, but for now we have nothing to do with them.
This commit is contained in:
Ariel Abreu 2020-09-27 21:49:27 -04:00
parent 4cc0178c42
commit 10d7373f86
No known key found for this signature in database
GPG Key ID: ECF8C2B9E8AD3E6B

View File

@ -13,6 +13,12 @@
#include <os/lock.h>
#include <elfcalls/threads.h>
#define __PTHREAD_EXPOSE_INTERNALS__ 1
#include <pthread/priority_private.h>
// much easier to include than libpthread's `internal.h`
#include "../../../../libelfloader/native/dthreads.h"
#define WQ_MAX_THREADS 64
#define WQOPS_QUEUE_ADD 1
@ -24,17 +30,27 @@
#define WQOPS_QUEUE_REQTHREADS 0x20
#define WQOPS_QUEUE_REQTHREADS2 0x30
#define WQOPS_SET_EVENT_MANAGER_PRIORITY 0x80
#define WQOPS_SETUP_DISPATCH 0x400
// Flags for the newly spawned thread:
// WQ_FLAG_THREAD_NEWSPI
// WQ_FLAG_THREAD_REUSE (0 if thread is newly spawned)
// item is unused in "new SPI"
#define WQ_FLAG_THREAD_PRIOMASK 0x0000ffff
#define WQ_FLAG_THREAD_OVERCOMMIT 0x00010000
#define WQ_FLAG_THREAD_REUSE 0x00020000
#define WQ_FLAG_THREAD_NEWSPI 0x00040000
#define WQ_FLAG_THREAD_KEVENT 0x00080000 /* thread is response to kevent req */
#define WQ_FLAG_THREAD_PRIO_SCHED 0x00008000
#define WQ_FLAG_THREAD_PRIO_QOS 0x00004000
#define WQ_FLAG_THREAD_PRIO_MASK 0x00000fff
#define WQ_FLAG_THREAD_OVERCOMMIT 0x00010000
#define WQ_FLAG_THREAD_REUSE 0x00020000
#define WQ_FLAG_THREAD_NEWSPI 0x00040000
#define WQ_FLAG_THREAD_KEVENT 0x00080000
#define WQ_FLAG_THREAD_EVENT_MANAGER 0x00100000
#define WQ_FLAG_THREAD_TSD_BASE_SET 0x00200000
#define WQ_FLAG_THREAD_WORKLOOP 0x00400000
#define WQ_FLAG_THREAD_OUTSIDEQOS 0x00800000
#define WORKQ_EXIT_THREAD_NKEVENT (-1)
static int workq_sem = WQ_MAX_THREADS; // max 64 threads in use
static os_unfair_lock workq_parked_lock = OS_UNFAIR_LOCK_INIT;
@ -51,8 +67,6 @@ struct parked_thread
TAILQ_ENTRY(parked_thread) entries;
};
extern void* pthread_get_stackaddr_np(void* pth);
static void list_add(struct parked_thread* head, struct parked_thread* item);
static void list_remove(struct parked_thread* head, struct parked_thread* item);
@ -111,7 +125,8 @@ long sys_workq_kernreturn(int options, void* item, int affinity, int prio)
int thread_self;
struct parked_thread me;
void* pth;
dthread_t dthread;
bool terminating = false;
os_unfair_lock_lock(&workq_parked_lock);
@ -144,44 +159,46 @@ long sys_workq_kernreturn(int options, void* item, int affinity, int prio)
os_unfair_lock_unlock(&workq_parked_lock);
// Let the thread terminate (libc will call pthread_exit)
return 0;
terminating = true;
// resume the thread; it'll call `_pthread_wqthread_exit` and kill itself
goto resume_thread;
}
wakeup:
// reset stack and call entry point again with WQ_FLAG_THREAD_REUSE
thread_self = thread_self_trap();
pth = _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
thread_self = _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_MACH_THREAD_SELF);
dthread = _pthread_getspecific_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
// __simple_printf("Thread %d woken up, prio=%d\n", thread_self, me.flags & WQ_FLAG_THREAD_PRIOMASK);
if (me.event)
wq_event_pending = me.event;
resume_thread:
#ifdef __x86_64__
// arguments are in rdi, rsi, rdx, rcx, r8, r9
__asm__ __volatile__ (
// "int3\n"
"movq %%rbx, %%r8\n" // 5th argument
"movl %5, %%r9d\n" // 6th argument
"movq %0, %%rsp\n"
"movq %%rsp, %%rdx\n" // 3rd argument
"xorq %%rcx, %%rcx\n" // 4th argument
"subq $32, %%rsp\n"
"jmpq *%2\n"
:: "D" (pth), "S" (thread_self), "a" (wqueue_entry_point),
"b" (me.flags | WQ_FLAG_THREAD_REUSE), "c" (me.event ? me.event->events : NULL),
"r" (me.event ? me.event->nevents : 0)
:: "D" (dthread), "S" (thread_self), "a" (wqueue_entry_point),
"b" (me.flags | WQ_FLAG_THREAD_REUSE), "c" ((!terminating && me.event) ? me.event->events : NULL),
"r" (terminating ? WORKQ_EXIT_THREAD_NKEVENT : (me.event ? me.event->nevents : 0)), "d" (dthread->stackbottom)
);
#elif defined(__i386__)
// Arguments are in eax, ebx, ecx, edx, edi, esi
__asm__ __volatile__ (
"movl %0, %%esp\n"
"movl %0, %%ecx\n"
"xorl %%edx, %%edx\n"
"subl $32, %%esp\n"
"jmpl *%2\n"
:: "a" (pth), "b" (thread_self), "S" (wqueue_entry_point),
:: "a" (dthread), "b" (thread_self), "S" (wqueue_entry_point),
"D" (me.flags | WQ_FLAG_THREAD_REUSE), "d" (me.event ? me.event->events : NULL),
"S" (me.event ? me.event->nevents : 0)
"S" (terminating ? WORKQ_EXIT_THREAD_NKEVENT : (me.event ? me.event->nevents : 0)), "c" (dthread->stackbottom)
);
#else
# error Missing assembly!
@ -203,10 +220,21 @@ wakeup:
{
// affinity contains thread count
int i, flags;
int i, flags, qos = _pthread_priority_thread_qos(prio);
flags = WQ_FLAG_THREAD_NEWSPI;
flags |= prio & (WQ_FLAG_THREAD_PRIOMASK | WQ_FLAG_THREAD_OVERCOMMIT); //priority_to_class(prio);
// Apple has created a mess of QoS in libdispatch and sometimes libdispatch doesn't set QoS properly
// when building with configurations Apple (apparently) hasn't tested
//
// we only do this is a hack/workaround until we improve our workqueue/kqueue support to use libdispatch's new SPIs
if (qos == 0) {
qos = 4; // default libdispatch QoS
}
flags = WQ_FLAG_THREAD_NEWSPI | qos | WQ_FLAG_THREAD_PRIO_QOS;
if (prio & _PTHREAD_PRIORITY_OVERCOMMIT_FLAG) {
flags |= WQ_FLAG_THREAD_OVERCOMMIT;
}
if (wq_event != NULL)
flags |= WQ_FLAG_THREAD_KEVENT;
@ -265,6 +293,19 @@ wakeup:
}
return 0;
}
case WQOPS_SETUP_DISPATCH: {
// TODO: actually do something with the info passed in
// XNU just tacks it onto the process its called for and uses it later when someone asks for it
/*
struct workq_dispatch_config cfg = {0};
// `affinity` contains the size of the config structure passed in
memcpy(&cfg, item, (affinity < sizeof(struct workq_dispatch_config)) ? affinity : sizeof(struct workq_dispatch_config));
*/
return 0;
} break;
default:
return -ENOTSUP;
}