--- /dev/null
+/* An approximation of Linux futexes implemented using pthread mutexes
+ * and pthread condition variables.
+ */
+
+/*
+ * This software is part of the SBCL system. See the README file for
+ * more information.
+ *
+ * The software is in the public domain and is provided with
+ * absolutely no warranty. See the COPYING and CREDITS files for more
+ * information.
+ */
+
+#include "sbcl.h"
+
+#if defined(LISP_FEATURE_SB_THREAD) && defined(LISP_FEATURE_SB_PTHREAD_FUTEX)
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include "runtime.h"
+#include "arch.h"
+#include "target-arch-os.h"
+#include "os.h"
+
+#define FUTEX_WAIT_NSEC (10000000) /* 10 msec */
+
+#if 1
+# define futex_assert(ex) \
+do { \
+ if (!(ex)) futex_abort(); \
+} while (0)
+# define futex_assert_verbose(ex, fmt, ...) \
+do { \
+ if (!(ex)) { \
+ fprintf(stderr, fmt, ## __VA_ARGS__); \
+ futex_abort(); \
+ } \
+} while (0)
+#else
+# define futex_assert(ex)
+# define futex_assert_verbose(ex, fmt, ...)
+#endif
+
+#define futex_abort() \
+ lose("Futex assertion failure, file \"%s\", line %d\n", __FILE__, __LINE__)
+
+struct futex {
+ struct futex *prev;
+ struct futex *next;
+ int *lock_word;
+ pthread_mutex_t mutex;
+ pthread_cond_t cond;
+ int count;
+};
+
+static pthread_mutex_t futex_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static struct futex *futex_head = NULL;
+static struct futex *futex_free_head = NULL;
+
+static struct futex *
+futex_add(struct futex *head, struct futex *futex)
+{
+ futex->prev = NULL;
+ futex->next = head;
+ if (head != NULL)
+ head->prev = futex;
+ head = futex;
+
+ return head;
+}
+
+static struct futex *
+futex_delete(struct futex *head, struct futex *futex)
+{
+ if (head == futex)
+ head = futex->next;
+ if (futex->prev != NULL)
+ futex->prev->next = futex->next;
+ if (futex->next != NULL)
+ futex->next->prev = futex->prev;
+
+ return head;
+}
+
+static struct futex *
+futex_find(struct futex *head, int *lock_word)
+{
+ struct futex *futex;
+
+ for (futex = head; futex != NULL; futex = futex->next) {
+ if (futex->lock_word == lock_word)
+ break;
+ }
+
+ return futex;
+}
+
+static struct futex *
+futex_get(int *lock_word)
+{
+ int ret;
+ struct futex *futex;
+
+ ret = pthread_mutex_lock(&futex_lock);
+ futex_assert(ret == 0);
+
+ futex = futex_find(futex_head, lock_word);
+
+ if (futex != NULL)
+ futex->count++;
+
+ ret = pthread_mutex_unlock(&futex_lock);
+ futex_assert(ret == 0);
+
+ if (futex != NULL) {
+ ret = pthread_mutex_lock(&futex->mutex);
+ futex_assert(ret == 0);
+ }
+
+ return futex;
+}
+
+static struct futex *
+futex_allocate(int *lock_word)
+{
+ int ret;
+ struct futex *futex;
+
+ ret = pthread_mutex_lock(&futex_lock);
+ futex_assert(ret == 0);
+
+ futex = futex_free_head;
+
+ if (futex != NULL)
+ futex_free_head = futex_delete(futex_free_head, futex);
+
+ ret = pthread_mutex_unlock(&futex_lock);
+ futex_assert(ret == 0);
+
+ if (futex == NULL) {
+ futex = malloc(sizeof(struct futex));
+ futex_assert(futex != NULL);
+
+ ret = pthread_mutex_init(&futex->mutex, NULL);
+ futex_assert(ret == 0);
+
+ ret = pthread_cond_init(&futex->cond, NULL);
+ futex_assert(ret == 0);
+ }
+
+ futex->lock_word = lock_word;
+ futex->count = 1;
+
+ /* Lock mutex before register to avoid race conditions. */
+ ret = pthread_mutex_lock(&futex->mutex);
+ futex_assert(ret == 0);
+
+ ret = pthread_mutex_lock(&futex_lock);
+ futex_assert(ret == 0);
+
+ futex_head = futex_add(futex_head, futex);
+
+ ret = pthread_mutex_unlock(&futex_lock);
+ futex_assert(ret == 0);
+
+ return futex;
+}
+
+static void
+futex_cleanup(void *p)
+{
+ struct futex *futex = (struct futex *)p;
+ int ret, count;
+
+ ret = pthread_mutex_lock(&futex_lock);
+ futex_assert(ret == 0);
+
+ count = --futex->count;
+ if (count <= 0) {
+ futex_head = futex_delete(futex_head, futex);
+ futex_free_head = futex_add(futex_free_head, futex);
+ }
+
+ ret = pthread_mutex_unlock(&futex_lock);
+ futex_assert(ret == 0);
+
+ ret = pthread_mutex_unlock(&futex->mutex);
+ futex_assert(ret == 0);
+}
+
+static int
+futex_relative_to_abs(struct timespec *tp, int relative)
+{
+ int ret;
+ struct timeval tv;
+
+ ret = gettimeofday(&tv, NULL);
+ if (ret != 0)
+ return ret;
+ tp->tv_sec = tv.tv_sec + (tv.tv_usec * 1000 + relative) / 1000000000;
+ tp->tv_nsec = (tv.tv_usec * 1000 + relative) % 1000000000;
+ return 0;
+}
+
+int
+futex_wait(int *lock_word, int oldval)
+{
+ int ret, result;
+ struct futex *futex;
+ sigset_t oldset, newset;
+
+ sigemptyset(&newset);
+ sigaddset_deferrable(&newset);
+
+again:
+ pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
+ futex = futex_get(lock_word);
+
+ if (futex == NULL)
+ futex = futex_allocate(lock_word);
+
+ pthread_cleanup_push(futex_cleanup, futex);
+
+ /* Compare lock_word after the lock is aquired to avoid race
+ * conditions. */
+ if (*(volatile int *)lock_word != oldval) {
+ result = EWOULDBLOCK;
+ goto done;
+ }
+
+ /* It's not possible to unwind frames across pthread_cond_wait(3). */
+ for (;;) {
+ int i;
+ sigset_t pendset;
+ struct timespec abstime;
+
+ ret = futex_relative_to_abs(&abstime, FUTEX_WAIT_NSEC);
+ futex_assert(ret == 0);
+
+ result = pthread_cond_timedwait(&futex->cond, &futex->mutex,
+ &abstime);
+ futex_assert(result == 0 || result == ETIMEDOUT);
+
+ if (result != ETIMEDOUT)
+ break;
+
+ /* futex system call of Linux returns with EINTR errno when
+ * it's interrupted by signals. Check pending signals here to
+ * emulate this behaviour. */
+ sigpending(&pendset);
+ for (i = 1; i < NSIG; i++) {
+ if (sigismember(&pendset, i) && sigismember(&newset, i)) {
+ result = EINTR;
+ goto done;
+ }
+ }
+ }
+done:
+ ; /* Null statement is required between label and pthread_cleanup_pop. */
+ pthread_cleanup_pop(1);
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
+ /* futex_wake() in linux-os.c loops when futex system call returns
+ * EINTR. */
+ if (result == EINTR) {
+ sched_yield();
+ goto again;
+ }
+
+ return result;
+}
+
+int
+futex_wake(int *lock_word, int n)
+{
+ int ret;
+ struct futex *futex;
+ sigset_t newset, oldset;
+
+ sigemptyset(&newset);
+ sigaddset_deferrable(&newset);
+
+ pthread_sigmask(SIG_BLOCK, &newset, &oldset);
+
+ futex = futex_get(lock_word);
+
+ if (futex != NULL) {
+ pthread_cleanup_push(futex_cleanup, futex);
+
+ /* The lisp-side code passes N=2**29-1 for a broadcast. */
+ if (n >= ((1 << 29) - 1)) {
+ /* CONDITION-BROADCAST */
+ ret = pthread_cond_broadcast(&futex->cond);
+ futex_assert(ret == 0);
+ } else {
+ while (n-- > 0) {
+ ret = pthread_cond_signal(&futex->cond);
+ futex_assert(ret == 0);
+ }
+ }
+
+ pthread_cleanup_pop(1);
+ }
+
+ pthread_sigmask(SIG_SETMASK, &oldset, NULL);
+
+ return 0;
+}
+#endif