gencgc: More precise conservatism for pointers to boxed pages.
[sbcl.git] / src / runtime / darwin-os.c
index 2d79d06..415745e 100644 (file)
 #include <signal.h>
 #include <limits.h>
 #include <mach-o/dyld.h>
+#include <stdio.h>
 #include <errno.h>
 #include <dlfcn.h>
 
 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
 #include <mach/mach.h>
+#include <libkern/OSAtomic.h>
+#include <stdlib.h>
+#endif
+
+#if defined(LISP_FEATURE_SB_WTIMER)
+# include <sys/types.h>
+# include <sys/event.h>
+# include <sys/time.h>
 #endif
 
 char *
@@ -36,8 +45,6 @@ os_get_runtime_executable_path(int external)
 
     if (_NSGetExecutablePath(path, &size) == -1)
         return NULL;
-    else
-        path[size] = '\0';
 
     return copied_string(path);
 }
@@ -97,23 +104,80 @@ setup_mach_exception_handling_thread()
     return mach_exception_handling_thread;
 }
 
+struct exception_port_record
+{
+    struct thread * thread;
+    struct exception_port_record * next;
+};
+
+static OSQueueHead free_records = OS_ATOMIC_QUEUE_INIT;
+
+/* We can't depend on arbitrary addresses to be accepted as mach port
+ * names, particularly not on 64-bit platforms.  Instead, we allocate
+ * records that point to the thread struct, and loop until one is accepted
+ * as a port name.
+ *
+ * Threads are mapped to exception ports with a slot in the thread struct,
+ * and exception ports are casted to records that point to the corresponding
+ * thread.
+ *
+ * The lock-free free-list above is used as a cheap fast path.
+ */
+static mach_port_t
+find_receive_port(struct thread * thread)
+{
+    mach_port_t ret;
+    struct exception_port_record * curr, * to_free = NULL;
+    unsigned long i;
+    for (i = 1;; i++) {
+        curr = OSAtomicDequeue(&free_records, offsetof(struct exception_port_record, next));
+        if (curr == NULL) {
+            curr = calloc(1, sizeof(struct exception_port_record));
+            if (curr == NULL)
+                lose("unable to allocate exception_port_record\n");
+        }
+#ifdef LISP_FEATURE_X86_64
+        if ((mach_port_t)curr != (unsigned long)curr)
+            goto skip;
+#endif
+
+        if (mach_port_allocate_name(current_mach_task,
+                                    MACH_PORT_RIGHT_RECEIVE,
+                                    (mach_port_t)curr))
+            goto skip;
+        curr->thread = thread;
+        ret = (mach_port_t)curr;
+        break;
+        skip:
+        curr->next = to_free;
+        to_free = curr;
+        if ((i % 1024) == 0)
+            FSHOW((stderr, "Looped %lu times trying to allocate an exception port\n"));
+    }
+    while (to_free != NULL) {
+        struct exception_port_record * current = to_free;
+        to_free = to_free->next;
+        free(current);
+    }
+
+    FSHOW((stderr, "Allocated exception port %x for thread %p\n", ret, thread));
+
+    return ret;
+}
+
 /* tell the kernel that we want EXC_BAD_ACCESS exceptions sent to the
    exception port (which is being listened to do by the mach
    exception handling thread). */
 kern_return_t
-mach_thread_init(mach_port_t thread_exception_port)
+mach_lisp_thread_init(struct thread * thread)
 {
     kern_return_t ret;
-    mach_port_t current_mach_thread;
+    mach_port_t current_mach_thread, thread_exception_port;
 
     /* allocate a named port for the thread */
-    FSHOW((stderr, "Allocating mach port %x\n", thread_exception_port));
-    ret = mach_port_allocate_name(current_mach_task,
-                                  MACH_PORT_RIGHT_RECEIVE,
-                                  thread_exception_port);
-    if (ret) {
-        lose("mach_port_allocate_name failed with return_code %d\n", ret);
-    }
+    thread_exception_port
+        = thread->mach_port_name
+        = find_receive_port(thread);
 
     /* establish the right for the thread_exception_port to send messages */
     ret = mach_port_insert_right(current_mach_task,
@@ -149,10 +213,25 @@ mach_thread_init(mach_port_t thread_exception_port)
     return ret;
 }
 
+kern_return_t
+mach_lisp_thread_destroy(struct thread *thread) {
+    kern_return_t ret;
+    mach_port_t port = thread->mach_port_name;
+    FSHOW((stderr, "Deallocating mach port %x\n", port));
+    mach_port_move_member(current_mach_task, port, MACH_PORT_NULL);
+    mach_port_deallocate(current_mach_task, port);
+
+    ret = mach_port_destroy(current_mach_task, port);
+    ((struct exception_port_record*)port)->thread = NULL;
+    OSAtomicEnqueue(&free_records, (void*)port, offsetof(struct exception_port_record, next));
+
+    return ret;
+}
+
 void
 setup_mach_exceptions() {
     setup_mach_exception_handling_thread();
-    mach_thread_init(THREAD_STRUCT_TO_EXCEPTION_PORT(all_threads));
+    mach_lisp_thread_init(all_threads);
 }
 
 pid_t
@@ -165,6 +244,134 @@ mach_fork() {
         return pid;
     }
 }
+#endif
 
+void darwin_init(void)
+{
+#ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
+    setup_mach_exception_handling_thread();
 #endif
+}
+
+
+#ifdef LISP_FEATURE_SB_THREAD
+
+inline void
+os_sem_init(os_sem_t *sem, unsigned int value)
+{
+    if (KERN_SUCCESS!=semaphore_create(current_mach_task, sem, SYNC_POLICY_FIFO, (int)value))
+        lose("os_sem_init(%p): %s", sem, strerror(errno));
+}
 
+inline void
+os_sem_wait(os_sem_t *sem, char *what)
+{
+    kern_return_t ret;
+  restart:
+    FSHOW((stderr, "%s: os_sem_wait(%p)\n", what, sem));
+    ret = semaphore_wait(*sem);
+    FSHOW((stderr, "%s: os_sem_wait(%p) => %s\n", what, sem,
+           KERN_SUCCESS==ret ? "ok" : strerror(errno)));
+    switch (ret) {
+    case KERN_SUCCESS:
+        return;
+        /* It is unclear just when we can get this, but a sufficiently
+         * long wait seems to do that, at least sometimes.
+         *
+         * However, a wait that long is definitely abnormal for the
+         * GC, so we complain before retrying.
+         */
+    case KERN_OPERATION_TIMED_OUT:
+        fprintf(stderr, "%s: os_sem_wait(%p): %s", what, sem, strerror(errno));
+        /* This is analogous to POSIX EINTR. */
+    case KERN_ABORTED:
+        goto restart;
+    default:
+        lose("%s: os_sem_wait(%p): %lu, %s", what, sem, ret, strerror(errno));
+    }
+}
+
+void
+os_sem_post(os_sem_t *sem, char *what)
+{
+    if (KERN_SUCCESS!=semaphore_signal(*sem))
+        lose("%s: os_sem_post(%p): %s", what, sem, strerror(errno));
+    FSHOW((stderr, "%s: os_sem_post(%p) ok\n", what, sem));
+}
+
+void
+os_sem_destroy(os_sem_t *sem)
+{
+    if (-1==semaphore_destroy(current_mach_task, *sem))
+        lose("os_sem_destroy(%p): %s", sem, strerror(errno));
+}
+
+#endif
+
+#if defined(LISP_FEATURE_SB_WTIMER)
+
+# error Completely untested. Go ahead! Remove this line, try your luck!
+
+/*
+ * Waitable timer implementation for the safepoint-based (SIGALRM-free)
+ * timer facility using kqueue.
+ *
+ * Unlike FreeBSD with its ms (!) timer resolution, Darwin supports ns
+ * timer resolution -- or at least it pretends to do so on the API
+ * level (?).  To use it, we need the *64 versions of the functions and
+ * structures.
+ *
+ * Unfortunately, I don't run Darwin, and can't test this code, so it's
+ * just a hopeful translation from FreeBSD.
+ */
+
+int
+os_create_wtimer()
+{
+    int kq = kqueue();
+    if (kq == -1)
+        lose("os_create_wtimer: kqueue");
+    return kq;
+}
+
+int
+os_wait_for_wtimer(int kq)
+{
+    struct kevent64_s ev;
+    int n;
+    if ( (n = kevent64(kq, 0, 0, &ev, 1, 0, 0)) == -1) {
+        if (errno != EINTR)
+            lose("os_wtimer_listen failed");
+        n = 0;
+    }
+    return n != 1;
+}
+
+void
+os_close_wtimer(int kq)
+{
+    if (close(kq) == -1)
+        lose("os_close_wtimer failed");
+}
+
+void
+os_set_wtimer(int kq, int sec, int nsec)
+{
+    int64_t nsec = ((int64_t) sec) * 1000000000 + (int64_t) nsec;
+
+    struct kevent64_s ev;
+    EV_SET64(&ev, 1, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT, NOTE_NSECONDS,
+             nsec, 0, 0, 0);
+    if (kevent64(kq, &ev, 1, 0, 0, 0, 0) == -1)
+        perror("os_set_wtimer: kevent");
+}
+
+void
+os_cancel_wtimer(int kq)
+{
+    struct kevent64_s ev;
+    EV_SET64(&ev, 1, EVFILT_TIMER, EV_DISABLE, 0, 0, 0, 0, 0);
+    if (kevent64(kq, &ev, 1, 0, 0, 0, 0) == -1 && errno != ENOENT)
+        perror("os_cancel_wtimer: kevent");
+}
+#endif