From 0afdee46583459484004bd79764dd81cd8c333d8 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Mon, 27 Jul 2026 14:30:23 +0000 Subject: [PATCH 5/5] build: real macOS pipe2/sem_init replacements, no compat headers pipe2() doesn't exist on Darwin -- add a portable static inline polyfill to util.h (pipe() + per-fd fcntl(F_SETFL/F_SETFD)), included by all five call sites. sem_init/sem_destroy are deprecated on Darwin, and documented as outright unreliable there, not just discouraged. Use libdispatch's plain-C semaphore API instead (dispatch_semaphore_create/_wait/ _signal/dispatch_release, no Objective-C/Blocks, available since the first libdispatch release in macOS 10.6): terminal.h now conditions its include and the render worker start/done fields' type on __APPLE__, with render_sem_wait/render_sem_post wrapping whichever API is in effect so terminal.c/render.c's call sites don't need to branch individually. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01U6fuL1PtRJHhr97gAGyS1h --- render.c | 15 ++++++++++----- spawn.c | 1 + terminal.c | 31 ++++++++++++++++++++++++++----- terminal.h | 43 ++++++++++++++++++++++++++++++++++++++++++- util.h | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 11 deletions(-) diff --git a/render.c b/render.c index cf5f969a..96cfdf84 100644 --- a/render.c +++ b/render.c @@ -2197,12 +2197,17 @@ render_worker_thread(void *_ctx) if (pthread_setname_np(pthread_self(), proc_title) < 0) LOG_ERRNO("render worker %d: failed to set process title", my_id); +#if defined(__APPLE__) + dispatch_semaphore_t *start = &term->render.workers.start; + dispatch_semaphore_t *done = &term->render.workers.done; +#else sem_t *start = &term->render.workers.start; sem_t *done = &term->render.workers.done; +#endif mtx_t *lock = &term->render.workers.lock; while (true) { - sem_wait(start); + render_sem_wait(start); struct buffer *buf = term->render.workers.buf; @@ -2236,7 +2241,7 @@ render_worker_thread(void *_ctx) case -1: frame_done = true; - sem_post(done); + render_sem_post(done); break; case -2: @@ -3557,7 +3562,7 @@ grid_render(struct terminal *term) mtx_lock(&term->render.workers.lock); term->render.workers.buf = buf; for (size_t i = 0; i < term->render.workers.count; i++) - sem_post(&term->render.workers.start); + render_sem_post(&term->render.workers.start); xassert(tll_length(term->render.workers.queue) == 0); } @@ -3587,7 +3592,7 @@ grid_render(struct terminal *term) mtx_unlock(&term->render.workers.lock); for (size_t i = 0; i < term->render.workers.count; i++) - sem_wait(&term->render.workers.done); + render_sem_wait(&term->render.workers.done); term->render.workers.buf = NULL; } @@ -5460,7 +5465,7 @@ render_buffer_release_callback(struct buffer *buf, void *data) mtx_unlock(&term->render.workers.preapplied_damage.lock); mtx_lock(&term->render.workers.lock); - sem_post(&term->render.workers.start); + render_sem_post(&term->render.workers.start); xassert(tll_length(term->render.workers.queue) == 0); tll_push_back(term->render.workers.queue, -3); mtx_unlock(&term->render.workers.lock); diff --git a/spawn.c b/spawn.c index 17c821b5..c97f4d71 100644 --- a/spawn.c +++ b/spawn.c @@ -13,6 +13,7 @@ #define LOG_ENABLE_DBG 0 #include "log.h" #include "debug.h" +#include "util.h" #include "xmalloc.h" pid_t diff --git a/terminal.c b/terminal.c index 9ef5f143..292138cf 100644 --- a/terminal.c +++ b/terminal.c @@ -705,17 +705,40 @@ fdm_app_id_update_timeout(struct fdm *fdm, int fd, int events, void *data) return true; } +static void +render_worker_sems_destroy(struct terminal *term) +{ +#if defined(__APPLE__) + if (term->render.workers.start != NULL) + dispatch_release(term->render.workers.start); + if (term->render.workers.done != NULL) + dispatch_release(term->render.workers.done); +#else + sem_destroy(&term->render.workers.start); + sem_destroy(&term->render.workers.done); +#endif +} + static bool initialize_render_workers(struct terminal *term) { LOG_INFO("using %hu rendering threads", term->render.workers.count); +#if defined(__APPLE__) + term->render.workers.start = dispatch_semaphore_create(0); + term->render.workers.done = dispatch_semaphore_create(0); + if (term->render.workers.start == NULL || term->render.workers.done == NULL) { + LOG_ERR("failed to instantiate render worker semaphores"); + return false; + } +#else if (sem_init(&term->render.workers.start, 0, 0) < 0 || sem_init(&term->render.workers.done, 0, 0) < 0) { LOG_ERRNO("failed to instantiate render worker semaphores"); return false; } +#endif int err; if ((err = mtx_init(&term->render.workers.lock, mtx_plain)) != thrd_success) { @@ -751,8 +774,7 @@ initialize_render_workers(struct terminal *term) return true; err_sem_destroy: - sem_destroy(&term->render.workers.start); - sem_destroy(&term->render.workers.done); + render_worker_sems_destroy(term); return false; } @@ -1859,7 +1881,7 @@ term_destroy(struct terminal *term) } for (size_t i = 0; i < worker_count; i++) { - sem_post(&term->render.workers.start); + render_sem_post(&term->render.workers.start); tll_push_back(term->render.workers.queue, -2); } } @@ -1906,8 +1928,7 @@ term_destroy(struct terminal *term) mtx_destroy(&term->render.workers.preapplied_damage.lock); cnd_destroy(&term->render.workers.preapplied_damage.cond); mtx_destroy(&term->render.workers.lock); - sem_destroy(&term->render.workers.start); - sem_destroy(&term->render.workers.done); + render_worker_sems_destroy(term); xassert(tll_length(term->render.workers.queue) == 0); tll_free(term->render.workers.queue); diff --git a/terminal.h b/terminal.h index 446d5f23..e6a992e3 100644 --- a/terminal.h +++ b/terminal.h @@ -5,7 +5,43 @@ #include #include -#include + +#if defined(__APPLE__) + /* + * Darwin's unnamed POSIX semaphores (sem_init/sem_destroy) are + * deprecated, and documented as unreliable, not just discouraged. + * Use libdispatch's plain-C semaphore API instead (no Objective-C/ + * Blocks required; available since the first libdispatch release + * in macOS 10.6). + */ + #include + +static inline void +render_sem_wait(dispatch_semaphore_t *sem) +{ + dispatch_semaphore_wait(*sem, DISPATCH_TIME_FOREVER); +} + +static inline void +render_sem_post(dispatch_semaphore_t *sem) +{ + dispatch_semaphore_signal(*sem); +} +#else + #include + +static inline void +render_sem_wait(sem_t *sem) +{ + sem_wait(sem); +} + +static inline void +render_sem_post(sem_t *sem) +{ + sem_post(sem); +} +#endif #if defined(FOOT_GRAPHEME_CLUSTERING) #include @@ -691,8 +727,13 @@ struct terminal { /* Render threads + synchronization primitives */ struct { uint16_t count; +#if defined(__APPLE__) + dispatch_semaphore_t start; + dispatch_semaphore_t done; +#else sem_t start; sem_t done; +#endif mtx_t lock; tll(int) queue; thrd_t *threads; diff --git a/util.h b/util.h index 3746e269..45722f39 100644 --- a/util.h +++ b/util.h @@ -5,6 +5,12 @@ #include #include +#if defined(__APPLE__) + #include + #include + #include +#endif + #define ALEN(v) (sizeof(v) / sizeof((v)[0])) #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) @@ -15,6 +21,51 @@ streq(const char *a, const char *b) return strcmp(a, b) == 0; } +#if defined(__APPLE__) +/* + * pipe2() is a Linux/glibc extension (later adopted by *BSD), never + * implemented by Darwin on any macOS version. All current call sites + * only ever pass O_CLOEXEC and/or O_NONBLOCK. + */ +static inline int +pipe2(int fildes[2], int flags) +{ + if (flags & ~(O_CLOEXEC | O_NONBLOCK)) { + errno = EINVAL; + return -1; + } + + if (pipe(fildes) < 0) + return -1; + + if (flags & O_NONBLOCK) { + for (int i = 0; i < 2; i++) { + int f = fcntl(fildes[i], F_GETFL, 0); + if (f < 0 || fcntl(fildes[i], F_SETFL, f | O_NONBLOCK) < 0) + goto fail; + } + } + + if (flags & O_CLOEXEC) { + for (int i = 0; i < 2; i++) { + int f = fcntl(fildes[i], F_GETFD, 0); + if (f < 0 || fcntl(fildes[i], F_SETFD, f | FD_CLOEXEC) < 0) + goto fail; + } + } + + return 0; + +fail: { + int saved_errno = errno; + close(fildes[0]); + close(fildes[1]); + errno = saved_errno; + return -1; +} +} +#endif + static inline const char * thrd_err_as_string(int thrd_err) { -- 2.43.0