Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 29 additions & 1 deletion platforms/unix/vm-display-X11/sqUnixX11.c
Original file line number Diff line number Diff line change
Expand Up @@ -4858,10 +4858,38 @@ display_ioBeep(void)
}


#define MAX_IDLE_USECS 500000 /* 500ms cap when no Delays pending */

static sqInt
display_ioRelinquishProcessorForMicroseconds(sqInt microSeconds)
{
aioSleepForUsecs(handleEvents() ? 0 : microSeconds);
extern usqLong getNextWakeupUsecs(void);
extern volatile int mainThreadIsIdle;

if (handleEvents())
return 0;

usqLong nextWakeupUsecs = getNextWakeupUsecs();
usqLong utcNow = ioUTCMicroseconds();
long realTimeToWait;

if (nextWakeupUsecs != 0 && nextWakeupUsecs <= utcNow)
return 0; /* Delay already overdue, don't sleep */

if (nextWakeupUsecs != 0) {
realTimeToWait = nextWakeupUsecs - utcNow;
if (realTimeToWait > MAX_IDLE_USECS)
realTimeToWait = MAX_IDLE_USECS;
} else {
realTimeToWait = MAX_IDLE_USECS;
}

mainThreadIsIdle = 1;
__sync_synchronize();
aioSleepForUsecs(realTimeToWait);
__sync_synchronize();
mainThreadIsIdle = 0;

return 0;
}

Expand Down
27 changes: 26 additions & 1 deletion platforms/unix/vm-display-null/sqUnixDisplayNull.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,34 @@ static sqInt display_ioFormPrint(sqInt b, sqInt w, sqInt h, sqInt d, double hS,

static sqInt display_ioBeep(void) { return 0; }

#define MAX_IDLE_USECS 500000 /* 500ms cap when no Delays pending */

static sqInt display_ioRelinquishProcessorForMicroseconds(sqInt microSeconds)
{
aioSleepForUsecs(microSeconds);
extern usqLong getNextWakeupUsecs(void);
extern volatile int mainThreadIsIdle;

usqLong nextWakeupUsecs = getNextWakeupUsecs();
usqLong utcNow = ioUTCMicroseconds();
long realTimeToWait;

if (nextWakeupUsecs != 0 && nextWakeupUsecs <= utcNow)
return 0; /* Delay already overdue, don't sleep */

if (nextWakeupUsecs != 0) {
realTimeToWait = nextWakeupUsecs - utcNow;
if (realTimeToWait > MAX_IDLE_USECS)
realTimeToWait = MAX_IDLE_USECS;
} else {
realTimeToWait = MAX_IDLE_USECS;
}

mainThreadIsIdle = 1;
__sync_synchronize();
aioSleepForUsecs(realTimeToWait);
__sync_synchronize();
mainThreadIsIdle = 0;

return 0;
}

Expand Down
231 changes: 225 additions & 6 deletions platforms/unix/vm/aio.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@
# include <sys/event.h>
# elif HAVE_EPOLL
# include <sys/epoll.h>
# elif defined(__APPLE__)
/* macOS Cocoa build: HAVE_CONFIG_H is set but no config.h exists,
* so HAVE_KQUEUE is never defined. Detect kqueue via __APPLE__. */
# include <sys/event.h>
# define HAVE_KQUEUE 1
# elif HAVE_SELECT
# include <sys/select.h>
# endif
Expand Down Expand Up @@ -97,12 +102,22 @@
# include <unistd.h>
# include <sys/types.h>
# include <sys/time.h>
# include <sys/select.h>
# include <sys/ioctl.h>
# include <fcntl.h>
# if defined(__APPLE__)
# include <sys/event.h>
# define USE_KQUEUE 1
# else
# include <sys/select.h>
# endif

#endif /* !HAVE_CONFIG_H */

/* Unify kqueue detection: USE_KQUEUE defined either way */
#if !defined(USE_KQUEUE) && defined(HAVE_KQUEUE) && HAVE_KQUEUE
# define USE_KQUEUE 1
#endif

/* function to inform the VM about idle time */
extern void addIdleUsecs(long idleUsecs);

Expand Down Expand Up @@ -137,7 +152,32 @@ const static int epollFlagsForAIOFlags[] = {
EPOLLIN | EPOLLOUT | EPOLLPRI // AIO_RWX
};

#else // HAVE_CONFIG_H && HAVE_EPOLL
#elif defined(USE_KQUEUE)
/* kqueue-based I/O for macOS (JMM-619 Phase 4) */
int kqFd = -1; /* non-static: accessed by sqUnixHeartbeat.c */

struct kqEventData {
int fd;
int aioMask;
aioHandler readHandler;
aioHandler writeHandler;
aioHandler exceptionHandler;
void *clientData;
};

static struct kqEventData **kqEventsByFd = NULL;
static size_t kqEventsCount = 0;

/* Heartbeat timer folded into kqueue (JMM-619 Phase 4).
* When the main thread is idle, we register EVFILT_TIMER in kqueue
* so kevent() delivers heartbeat events directly, eliminating the
* need for the heartbeat thread to wake up.
*/
#define KQ_HEARTBEAT_IDENT 0xBEA7 /* magic ident for timer */
int kqHeartbeatActive = 0; /* non-static: accessed by sqUnixHeartbeat.c */
extern void heartbeat(void); /* from sqUnixHeartbeat.c */

#else // select() fallback

# define _DO_FLAG_TYPE() do { _DO(AIO_R, rd) _DO(AIO_W, wr) _DO(AIO_X, ex) } while (0)

Expand All @@ -153,7 +193,7 @@ static fd_set rdMask; /* handle read */
static fd_set wrMask; /* handle write */
static fd_set exMask; /* handle exception */
static fd_set xdMask; /* external descriptor */
#endif // HAVE_CONFIG_H && HAVE_EPOLL
#endif // epoll / kqueue / select

static void
undefinedHandler(int fd, void *clientData, int flags)
Expand Down Expand Up @@ -255,6 +295,18 @@ aioInit(void)
epollInit();
if (pthread_atfork(NULL, NULL, epollInit))
perror("pthread_atfork");
#elif defined(USE_KQUEUE)
if (kqFd >= 0)
close(kqFd);
kqFd = kqueue();
if (kqFd == -1) {
perror("kqueue failed");
exit(1);
}
/* set close-on-exec */
fcntl(kqFd, F_SETFD, FD_CLOEXEC);
kqEventsCount = 0;
kqEventsByFd = NULL;
#else
FD_ZERO(&fdMask);
FD_ZERO(&rdMask);
Expand Down Expand Up @@ -313,6 +365,14 @@ aioFini(void)
}
}
}
#elif defined(USE_KQUEUE)
for (size_t i = 0; i < kqEventsCount; ++i) {
struct kqEventData *data = kqEventsByFd[i];
if (data && !(data->aioMask & AIO_EXT)) {
aioDisable(i);
close(i);
}
}
#else
int fd;

Expand Down Expand Up @@ -440,7 +500,61 @@ aioPoll(long microSeconds)
} while(microSeconds > 0);
return 0;

#else // HAVE_CONFIG_H && HAVE_EPOLL
#elif defined(USE_KQUEUE)

DO_TICK(SHOULD_TICK());

if (kqEventsCount == 0 && microSeconds == 0)
return 0;

do {
const usqLong start = ioUTCMicroseconds();
struct kevent events[128];
struct timespec ts;
ts.tv_sec = microSeconds / 1000000;
ts.tv_nsec = (microSeconds % 1000000) * 1000;
const int nev = kevent(kqFd, NULL, 0, events, 128,
microSeconds >= 0 ? &ts : NULL);
if (nev == -1) {
if (errno != EINTR) {
perror("kevent");
return 0;
}
} else if (nev > 0) {
for (int i = 0; i < nev; ++i) {
aioHandler handler;
struct kqEventData *data = (struct kqEventData *)events[i].udata;
if (!data) continue;
/* Heartbeat timer event */
if (events[i].filter == EVFILT_TIMER
&& events[i].ident == KQ_HEARTBEAT_IDENT) {
heartbeat();
continue;
}
if (events[i].filter == EVFILT_READ) {
if ((handler = data->readHandler))
handler(data->fd, data->clientData, AIO_R);
}
if (events[i].filter == EVFILT_WRITE) {
if ((handler = data->writeHandler))
handler(data->fd, data->clientData, AIO_W);
}
/* EV_ERROR or EOF treated as exception */
if (events[i].flags & (EV_EOF | EV_ERROR)) {
if ((handler = data->exceptionHandler))
handler(data->fd, data->clientData, AIO_X);
}
}
return 1;
} else { /* nev == 0: timeout */
if (microSeconds > 0) addIdleUsecs(microSeconds);
return 0;
}
microSeconds -= max(ioUTCMicroseconds() - start, 1);
} while (microSeconds > 0);
return 0;

#else // select() fallback

int fd;
fd_set rd, wr, ex;
Expand Down Expand Up @@ -648,7 +762,37 @@ aioEnable(int fd, void *data, int flags)
*/
makeFileDescriptorNonBlockingAndSetupSigio(fd);
}
#else // HAVE_CONFIG_H && HAVE_EPOLL
#elif defined(USE_KQUEUE)
if (fd >= (int)kqEventsCount) {
size_t newCount = kqEventsCount ? kqEventsCount * 2 : 64;
if ((size_t)fd >= newCount) newCount = (size_t)fd + 1;
struct kqEventData **newArr = (struct kqEventData **)realloc(
kqEventsByFd, newCount * sizeof(*kqEventsByFd));
if (!newArr) { perror("aioEnable realloc"); return; }
memset(newArr + kqEventsCount, 0,
(newCount - kqEventsCount) * sizeof(*newArr));
kqEventsByFd = newArr;
kqEventsCount = newCount;
}
if (kqEventsByFd[fd]) {
FPRINTF((stderr, "aioEnable: descriptor %d already enabled\n", fd));
return;
}
struct kqEventData *kqd = (struct kqEventData *)calloc(1, sizeof(*kqd));
if (!kqd) { perror("aioEnable calloc"); return; }
kqd->fd = fd;
kqd->aioMask = flags & AIO_EXT;
kqd->readHandler = undefinedHandler;
kqd->writeHandler = undefinedHandler;
kqd->exceptionHandler = undefinedHandler;
kqd->clientData = data;
kqEventsByFd[fd] = kqd;
if (flags & AIO_EXT) {
FPRINTF((stderr, "aioEnable(%d): external\n", fd));
} else {
makeFileDescriptorNonBlockingAndSetupSigio(fd);
}
#else // select() fallback
if (fd >= FD_SETSIZE) {
FPRINTF((stderr, "aioEnable(%d): fd too large\n", fd));
return;
Expand Down Expand Up @@ -679,7 +823,7 @@ aioEnable(int fd, void *data, int flags)
FD_CLR(fd, &xdMask);
makeFileDescriptorNonBlockingAndSetupSigio(fd);
}
#endif // HAVE_CONFIG_H && HAVE_EPOLL
#endif // epoll / kqueue / select
}

#if defined(AIO_DEBUG)
Expand Down Expand Up @@ -751,6 +895,34 @@ aioHandle(int fd, aioHandler handlerFn, int mask)
else {
FPRINTF((stderr, "aioHandle(%d, %p, %d): epoll_ctl(%d, %d, %d, %p) succeeded\n", fd, handlerFn, mask, epollFd, epoll_operation, fd, event));
}
#elif defined(USE_KQUEUE)
if (fd >= (int)kqEventsCount || !kqEventsByFd[fd]) {
FPRINTF((stderr, "aioHandle(%d): NOT ENABLED\n", fd));
return;
}
{
struct kqEventData *data = kqEventsByFd[fd];
struct kevent changes[2];
int nchanges = 0;

if (mask & AIO_R) data->readHandler = handlerFn;
if (mask & AIO_W) data->writeHandler = handlerFn;
if (mask & AIO_X) data->exceptionHandler = handlerFn;

if (mask & AIO_R) {
EV_SET(&changes[nchanges], fd, EVFILT_READ,
EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, data);
nchanges++;
}
if (mask & AIO_W) {
EV_SET(&changes[nchanges], fd, EVFILT_WRITE,
EV_ADD | EV_ENABLE | EV_CLEAR, 0, 0, data);
nchanges++;
}
data->aioMask |= mask;
if (nchanges > 0 && kevent(kqFd, changes, nchanges, NULL, 0, NULL) == -1)
perror("aioHandle kevent");
}
#else
# undef _DO
# define _DO(FLAG, TYPE) \
Expand Down Expand Up @@ -790,6 +962,31 @@ aioSuspend(int fd, int mask)
else {
FPRINTF((stderr, "aioSuspend(%d, %d): NOTHING TO SUSPEND\n", fd, mask));
}
#elif defined(USE_KQUEUE)
if (fd >= (int)kqEventsCount || !kqEventsByFd[fd]) {
FPRINTF((stderr, "aioSuspend(%d): NOT ENABLED\n", fd));
return;
}
{
struct kqEventData *data = kqEventsByFd[fd];
struct kevent changes[2];
int nchanges = 0;

if ((mask & AIO_R) && (data->aioMask & AIO_R)) {
EV_SET(&changes[nchanges], fd, EVFILT_READ,
EV_DELETE, 0, 0, NULL);
nchanges++;
}
if ((mask & AIO_W) && (data->aioMask & AIO_W)) {
EV_SET(&changes[nchanges], fd, EVFILT_WRITE,
EV_DELETE, 0, 0, NULL);
nchanges++;
}
data->aioMask &= ~mask;
if (nchanges > 0 && kevent(kqFd, changes, nchanges, NULL, 0, NULL) == -1) {
if (errno != ENOENT) perror("aioSuspend kevent");
}
}
#else
#undef _DO
#define _DO(FLAG, TYPE) \
Expand Down Expand Up @@ -827,6 +1024,28 @@ aioDisable(int fd)
free(event->data.ptr);
free(event);
epollEventsByFileDescriptor[fd] = NULL;
#elif defined(USE_KQUEUE)
if (fd >= (int)kqEventsCount || !kqEventsByFd[fd]) {
FPRINTF((stderr, "aioDisable(%d): NOT ENABLED\n", fd));
return;
}
{
struct kqEventData *data = kqEventsByFd[fd];
struct kevent changes[2];
int nchanges = 0;
if (data->aioMask & AIO_R) {
EV_SET(&changes[nchanges], fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
nchanges++;
}
if (data->aioMask & AIO_W) {
EV_SET(&changes[nchanges], fd, EVFILT_WRITE, EV_DELETE, 0, 0, NULL);
nchanges++;
}
if (nchanges > 0)
kevent(kqFd, changes, nchanges, NULL, 0, NULL); /* ignore ENOENT */
free(data);
kqEventsByFd[fd] = NULL;
}
#else
aioSuspend(fd, AIO_RWX);
FD_CLR(fd, &xdMask);
Expand Down
Loading
Loading