mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
358 lines
9.5 KiB
C
358 lines
9.5 KiB
C
/* A custom executor to test that we can build an external executor
|
|
and use it with libswift_Concurrency.a. */
|
|
|
|
#include <swift/ExecutorImpl.h>
|
|
|
|
#include <assert.h>
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#ifndef DEBUG_EXECUTOR
|
|
#define DEBUG_EXECUTOR 1
|
|
#endif
|
|
|
|
static void debug(const char *fmt, ...) {
|
|
#if DEBUG_EXECUTOR
|
|
va_list val;
|
|
|
|
va_start(val, fmt);
|
|
vprintf(fmt, val);
|
|
va_end(val);
|
|
#else
|
|
(void)fmt;
|
|
#endif
|
|
}
|
|
|
|
// .. A max-heap to hold jobs in priority order ................................
|
|
|
|
/* This is an explicit binary heap threaded through the schedulerPrivate[]
|
|
fields in the job. */
|
|
|
|
static SwiftJob *jobHeap = NULL;
|
|
|
|
static SwiftJob *job_left(SwiftJob *job) {
|
|
return (SwiftJob *)(job->schedulerPrivate[0]);
|
|
}
|
|
static void job_setLeft(SwiftJob *job, SwiftJob *left) {
|
|
job->schedulerPrivate[0] = left;
|
|
}
|
|
static SwiftJob *job_right(SwiftJob *job) {
|
|
return (SwiftJob *)(job->schedulerPrivate[1]);
|
|
}
|
|
static void job_setRight(SwiftJob *job, SwiftJob *right) {
|
|
job->schedulerPrivate[1] = right;
|
|
}
|
|
|
|
static SwiftJob *job_heap_fixup(SwiftJob *job) {
|
|
SwiftJob *left = job_left(job);
|
|
SwiftJob *right = job_right(job);
|
|
|
|
SwiftJobPriority priority = swift_job_getPriority(job);
|
|
SwiftJobPriority leftPriority = left ? swift_job_getPriority(left) : 0;
|
|
SwiftJobPriority rightPriority = right ? swift_job_getPriority(right) : 0;
|
|
|
|
SwiftJob *temp;
|
|
|
|
if (left && leftPriority >= rightPriority && priority <= leftPriority) {
|
|
job_setLeft(job, job_left(left));
|
|
job_setRight(job, job_right(left));
|
|
job_setRight(left, right);
|
|
job_setLeft(left, job_heap_fixup(job));
|
|
return left;
|
|
}
|
|
if (right && rightPriority > leftPriority && priority <= rightPriority) {
|
|
job_setLeft(job, job_left(right));
|
|
job_setRight(job, job_right(right));
|
|
job_setLeft(right, left);
|
|
job_setRight(right, job_heap_fixup(job));
|
|
return right;
|
|
}
|
|
|
|
return job;
|
|
}
|
|
|
|
static void job_heap_push(SwiftJob *job) {
|
|
job_setLeft(job, NULL);
|
|
job_setRight(job, NULL);
|
|
|
|
// If the heap is empty, this job is the top
|
|
if (!jobHeap) {
|
|
jobHeap = job;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, make the existing top node a child of this one, then fix the
|
|
// heap condition.
|
|
SwiftJobPriority jobPriority = swift_job_getPriority(job);
|
|
|
|
job_setLeft(job, jobHeap);
|
|
jobHeap = job_heap_fixup(job);
|
|
}
|
|
|
|
static SwiftJob *job_heap_pop(void) {
|
|
if (!jobHeap)
|
|
return NULL;
|
|
|
|
SwiftJob *job = jobHeap;
|
|
|
|
// The easy case: job has at most one child
|
|
if (!job_left(job)) {
|
|
jobHeap = job_right(job);
|
|
return job;
|
|
}
|
|
if (!job_right(job)) {
|
|
jobHeap = job_left(job);
|
|
return job;
|
|
}
|
|
|
|
// Otherwise, find a job with no children
|
|
SwiftJob *parent = NULL;
|
|
SwiftJob *ptr = job;
|
|
while (job_left(ptr) || job_right(ptr)) {
|
|
parent = ptr;
|
|
if (job_right(ptr))
|
|
ptr = job_right(ptr);
|
|
else
|
|
ptr = job_left(ptr);
|
|
}
|
|
|
|
// Move it to the head of the queue
|
|
if (job_right(parent) == ptr)
|
|
job_setRight(parent, NULL);
|
|
else
|
|
job_setLeft(parent, NULL);
|
|
|
|
job_setLeft(ptr, job_left(job));
|
|
job_setRight(ptr, job_right(job));
|
|
|
|
// And fix the heap condition
|
|
jobHeap = job_heap_fixup(ptr);
|
|
|
|
return job;
|
|
}
|
|
|
|
// .. A list of delayed jobs ...................................................
|
|
|
|
// One for each clock
|
|
static SwiftJob *delayQueues[2] = { NULL, NULL };
|
|
|
|
static SwiftJob *job_next(SwiftJob *job) {
|
|
return (SwiftJob *)(job->schedulerPrivate[0]);
|
|
}
|
|
|
|
static SwiftJob **job_ptrToNext(SwiftJob *job) {
|
|
return (SwiftJob **)&job->schedulerPrivate[0];
|
|
}
|
|
|
|
static void job_setNext(SwiftJob *job, SwiftJob *next) {
|
|
job->schedulerPrivate[0] = next;
|
|
}
|
|
|
|
static uint64_t job_deadline(SwiftJob *job) {
|
|
return (uint64_t)(job->schedulerPrivate[1]);
|
|
}
|
|
|
|
static void job_setDeadline(SwiftJob *job, uint64_t deadline) {
|
|
job->schedulerPrivate[1] = (void *)(uintptr_t)deadline;
|
|
}
|
|
|
|
static void job_schedule(SwiftJob *job, SwiftClockId clock, uint64_t deadline) {
|
|
assert(clock >= 1 && clock <= 2 && "clock out of range");
|
|
|
|
job_setDeadline(job, deadline);
|
|
|
|
SwiftJob **pos = &delayQueues[clock - 1];
|
|
SwiftJob *ptr;
|
|
while ((ptr = *pos) && deadline >= job_deadline(ptr)) {
|
|
pos = job_ptrToNext(ptr);
|
|
}
|
|
|
|
job_setNext(job, ptr);
|
|
*pos = job;
|
|
}
|
|
|
|
static uint64_t job_getTime(SwiftClockId clock) {
|
|
SwiftTime now = swift_time_now(clock);
|
|
return now.seconds * 1000000000ull + now.nanoseconds;
|
|
}
|
|
|
|
static void job_runTimers(void) {
|
|
for (int clock = 0; clock < 2; ++clock) {
|
|
uint64_t flatNow = job_getTime((SwiftClockId)(clock + 1));
|
|
|
|
SwiftJob *job = delayQueues[clock];
|
|
while (job && flatNow >= job_deadline(job)) {
|
|
SwiftJob *next = job_next(job);
|
|
debug("executor: job %d is ready\n", job);
|
|
job_heap_push(job);
|
|
job = next;
|
|
}
|
|
delayQueues[clock] = job;
|
|
}
|
|
}
|
|
|
|
static bool job_haveDelayedJobs(void) {
|
|
for (int clock = 0; clock < 2; ++clock) {
|
|
if (delayQueues[clock])
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Sleep for a specified number of nanoseconds
|
|
static void job_sleep(uint64_t ns) {
|
|
if (ns == 0)
|
|
return;
|
|
|
|
debug("executor: sleeping for %"PRIu64" ns\n", ns);
|
|
|
|
// We don't know how to do this "properly", so spin instead
|
|
uint64_t flatNow = job_getTime(SwiftContinuousClock);
|
|
uint64_t deadline = flatNow + ns;
|
|
|
|
while (flatNow < deadline)
|
|
flatNow = job_getTime(SwiftContinuousClock);
|
|
}
|
|
|
|
// Wait until the next timer is about to fire
|
|
static void job_wait(void) {
|
|
uint64_t toSleep = ~(uint64_t)0;
|
|
|
|
for (int clock = 0; clock < 2; ++clock) {
|
|
uint64_t flatNow = job_getTime((SwiftClockId)(clock + 1));
|
|
|
|
if (delayQueues[clock]) {
|
|
uint64_t deadline = job_deadline(delayQueues[clock]);
|
|
uint64_t delay = deadline - flatNow;
|
|
if (delay < toSleep)
|
|
toSleep = delay;
|
|
}
|
|
}
|
|
|
|
job_sleep(toSleep);
|
|
}
|
|
|
|
// .. Main loop ................................................................
|
|
|
|
static SwiftJob *job_getNextJob() {
|
|
while (true) {
|
|
job_runTimers();
|
|
|
|
SwiftJob *job = job_heap_pop();
|
|
if (job) {
|
|
return job;
|
|
}
|
|
|
|
if (!job_haveDelayedJobs())
|
|
return NULL;
|
|
|
|
job_wait();
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// .. Interface functions ......................................................
|
|
|
|
/// Enqueue a job on the global executor.
|
|
SWIFT_CC(swift) void
|
|
swift_task_enqueueGlobalImpl(SwiftJob *job) {
|
|
debug("executor: job %p enqueued\n", job);
|
|
|
|
job_heap_push(job);
|
|
}
|
|
|
|
/// Enqueue a job on the global executor, with a specific delay before it
|
|
/// should execute.
|
|
SWIFT_CC(swift) void
|
|
swift_task_enqueueGlobalWithDelayImpl(SwiftJobDelay delay,
|
|
SwiftJob *job) {
|
|
SwiftTime now = swift_time_now(SwiftContinuousClock);
|
|
uint64_t deadline = now.seconds * 1000000000ull + now.nanoseconds + delay;
|
|
|
|
debug("executor: job %p scheduled with delay %llu ns\n", job, delay);
|
|
|
|
job_schedule(job, SwiftContinuousClock, deadline);
|
|
}
|
|
|
|
/// Enqueue a job on the global executor, with a specific deadline before
|
|
/// which it must execute.
|
|
SWIFT_CC(swift)
|
|
void swift_task_enqueueGlobalWithDeadlineImpl(long long sec,
|
|
long long nsec,
|
|
long long tsec,
|
|
long long tnsec,
|
|
int clock,
|
|
SwiftJob *job) {
|
|
uint64_t deadline = sec * 1000000000ull + nsec;
|
|
|
|
debug("executor: job %p scheduled with deadline %"PRIu64" on clock %d\n",
|
|
job, deadline, clock);
|
|
|
|
job_schedule(job, clock, deadline);
|
|
}
|
|
|
|
/// Enqueue a job on the main executor (which may or may not be the same as
|
|
/// the global executor).
|
|
SWIFT_CC(swift)
|
|
void swift_task_enqueueMainExecutorImpl(SwiftJob *job) {
|
|
swift_task_enqueueGlobalImpl(job);
|
|
}
|
|
|
|
/// Assert that the specified executor is the current executor.
|
|
SWIFT_CC(swift)
|
|
void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
|
|
swift_executor_invokeSwiftCheckIsolated(executor);
|
|
}
|
|
|
|
/// Check if the specified executor is the current executor.
|
|
SWIFT_CC(swift)
|
|
int8_t swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
|
|
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
|
|
}
|
|
|
|
/// Get a reference to the main executor.
|
|
SWIFT_CC(swift)
|
|
SwiftExecutorRef swift_task_getMainExecutorImpl() {
|
|
return swift_executor_generic();
|
|
}
|
|
|
|
/// Check if the specified executor is the main executor.
|
|
SWIFT_CC(swift)
|
|
bool swift_task_isMainExecutorImpl(SwiftExecutorRef executor) {
|
|
return swift_executor_isGeneric(executor);
|
|
}
|
|
|
|
/// Drain the main executor's queue, processing jobs enqueued on it; this
|
|
/// should never return.
|
|
SWIFT_RUNTIME_ATTRIBUTE_NORETURN SWIFT_CC(swift) void
|
|
swift_task_asyncMainDrainQueueImpl() {
|
|
debug("executor: running\n");
|
|
while (true) {
|
|
SwiftJob *job = job_getNextJob();
|
|
assert(job && "We shouldn't run out of jobs here.");
|
|
debug("executor: job %p running\n", job);
|
|
swift_job_run(job, swift_executor_generic());
|
|
}
|
|
}
|
|
|
|
/// Hand control of the current thread to the global executor until the
|
|
/// condition function returns `true`. Support for this function is optional,
|
|
/// but you should assert or provide a dummy implementation if your executor
|
|
/// does not support it.
|
|
SWIFT_CC(swift) void
|
|
swift_task_donateThreadToGlobalExecutorUntilImpl(bool (*condition)(void *),
|
|
void *conditionContext) {
|
|
debug("executor: running until condition\n");
|
|
while (!condition(conditionContext)) {
|
|
SwiftJob *job = job_getNextJob();
|
|
if (!job)
|
|
return;
|
|
debug("executor: job %p running\n", job);
|
|
swift_job_run(job, swift_executor_generic());
|
|
}
|
|
debug("executor: condition satisfied or no more jobs\n");
|
|
}
|
|
|