Kludge to unbreak sb-introspect tests
[sbcl.git] / src / runtime / darwin-os.c
blob8c5263a664ae6733c2c4adb121362d7325bed7e4
1 /*
2 * This is the Darwin incarnation of OS-dependent routines. See also
3 * "bsd-os.c".
4 */
6 /*
7 * This software is part of the SBCL system. See the README file for
8 * more information.
10 * This software is derived from the CMU CL system, which was
11 * written at Carnegie Mellon University and released into the
12 * public domain. The software is in the public domain and is
13 * provided with absolutely no warranty. See the COPYING and CREDITS
14 * files for more information.
17 #include "thread.h"
18 #include "sbcl.h"
19 #include "globals.h"
20 #include "runtime.h"
21 #include <signal.h>
22 #include <limits.h>
23 #include <mach-o/dyld.h>
24 #include <stdio.h>
25 #include <errno.h>
26 #include <dlfcn.h>
27 #include <pthread.h>
28 #include <mach/mach.h>
29 #include <mach/clock.h>
30 #include <stdlib.h>
31 #include <time.h>
32 #include <sys/syscall.h>
34 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
35 #include <libkern/OSAtomic.h>
36 #endif
38 #if defined(LISP_FEATURE_SB_WTIMER)
39 # include <sys/types.h>
40 # include <sys/event.h>
41 # include <sys/time.h>
42 #endif
44 char *
45 os_get_runtime_executable_path(int external)
47 char path[PATH_MAX + 1];
48 uint32_t size = sizeof(path);
50 if (_NSGetExecutablePath(path, &size) == -1)
51 return NULL;
53 return copied_string(path);
57 semaphore_t clock_sem = MACH_PORT_NULL;
58 mach_port_t clock_port = MACH_PORT_NULL;
60 void init_mach_clock() {
61 if (host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clock_port)
62 != KERN_SUCCESS) {
63 lose("Error initializing clocks");
66 if (semaphore_create(mach_task_self_, &clock_sem, SYNC_POLICY_FIFO, 0)
67 != KERN_SUCCESS) {
68 lose("Error initializing clocks");
72 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
74 /* exc_server handles mach exception messages from the kernel and
75 * calls catch exception raise. We use the system-provided
76 * mach_msg_server, which, I assume, calls exc_server in a loop.
79 extern boolean_t exc_server();
81 void *
82 mach_exception_handler(void *port)
84 mach_msg_server(exc_server, 2048, (mach_port_t) port, 0);
85 /* mach_msg_server should never return, but it should dispatch mach
86 * exceptions to our catch_exception_raise function
88 lose("mach_msg_server returned");
91 /* Sets up the thread that will listen for mach exceptions. note that
92 the exception handlers will be run on this thread. This is
93 different from the BSD-style signal handling situation in which the
94 signal handlers run in the relevant thread directly. */
96 mach_port_t mach_exception_handler_port_set = MACH_PORT_NULL;
98 pthread_t
99 setup_mach_exception_handling_thread()
101 kern_return_t ret;
102 pthread_t mach_exception_handling_thread = NULL;
103 pthread_attr_t attr;
105 /* allocate a mach_port for this process */
106 ret = mach_port_allocate(mach_task_self(),
107 MACH_PORT_RIGHT_PORT_SET,
108 &mach_exception_handler_port_set);
110 /* create the thread that will receive the mach exceptions */
112 FSHOW((stderr, "Creating mach_exception_handler thread!\n"));
114 pthread_attr_init(&attr);
115 pthread_create(&mach_exception_handling_thread,
116 &attr,
117 mach_exception_handler,
118 (void*)(long)mach_exception_handler_port_set);
119 pthread_attr_destroy(&attr);
121 return mach_exception_handling_thread;
124 /* tell the kernel that we want EXC_BAD_ACCESS exceptions sent to the
125 exception port (which is being listened to do by the mach
126 exception handling thread). */
127 kern_return_t
128 mach_lisp_thread_init(struct thread * thread)
130 kern_return_t ret;
131 mach_port_t current_mach_thread, thread_exception_port;
133 if (mach_port_allocate(mach_task_self(),
134 MACH_PORT_RIGHT_RECEIVE,
135 &thread_exception_port) != KERN_SUCCESS) {
136 lose("Cannot allocate thread_exception_port");
139 if (mach_port_set_context(mach_task_self(), thread_exception_port,
140 (mach_vm_address_t)thread)
141 != KERN_SUCCESS) {
142 lose("Cannot set thread_exception_port context");
144 thread->mach_port_name = thread_exception_port;
146 /* establish the right for the thread_exception_port to send messages */
147 ret = mach_port_insert_right(mach_task_self(),
148 thread_exception_port,
149 thread_exception_port,
150 MACH_MSG_TYPE_MAKE_SEND);
151 if (ret) {
152 lose("mach_port_insert_right failed with return_code %d\n", ret);
155 current_mach_thread = mach_thread_self();
156 ret = thread_set_exception_ports(current_mach_thread,
157 EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION,
158 thread_exception_port,
159 EXCEPTION_DEFAULT,
160 THREAD_STATE_NONE);
161 if (ret) {
162 lose("thread_set_exception_ports failed with return_code %d\n", ret);
165 ret = mach_port_deallocate (mach_task_self(), current_mach_thread);
166 if (ret) {
167 lose("mach_port_deallocate failed with return_code %d\n", ret);
170 ret = mach_port_move_member(mach_task_self(),
171 thread_exception_port,
172 mach_exception_handler_port_set);
173 if (ret) {
174 lose("mach_port_move_member failed with return_code %d\n", ret);
177 return ret;
180 void
181 mach_lisp_thread_destroy(struct thread *thread) {
182 mach_port_t port = thread->mach_port_name;
183 FSHOW((stderr, "Deallocating mach port %x\n", port));
184 if (mach_port_move_member(mach_task_self(), port, MACH_PORT_NULL)
185 != KERN_SUCCESS) {
186 lose("Error destroying an exception port");
188 if (mach_port_deallocate(mach_task_self(), port) != KERN_SUCCESS) {
189 lose("Error destroying an exception port");
192 if (mach_port_destroy(mach_task_self(), port) != KERN_SUCCESS) {
193 lose("Error destroying an exception port");
197 void
198 setup_mach_exceptions() {
199 setup_mach_exception_handling_thread();
200 mach_lisp_thread_init(all_threads);
203 pid_t
204 mach_fork() {
205 pid_t pid = fork();
206 if (pid == 0) {
207 setup_mach_exceptions();
208 return pid;
209 } else {
210 return pid;
213 #endif
215 void darwin_init(void)
217 init_mach_clock();
218 #ifdef LISP_FEATURE_MACH_EXCEPTION_HANDLER
219 setup_mach_exception_handling_thread();
220 #endif
224 #ifdef LISP_FEATURE_SB_THREAD
226 inline void
227 os_sem_init(os_sem_t *sem, unsigned int value)
229 if (KERN_SUCCESS!=semaphore_create(mach_task_self(), sem, SYNC_POLICY_FIFO, (int)value))
230 lose("os_sem_init(%p): %s", sem, strerror(errno));
233 inline void
234 os_sem_wait(os_sem_t *sem, char *what)
236 kern_return_t ret;
237 restart:
238 FSHOW((stderr, "%s: os_sem_wait(%p)\n", what, sem));
239 ret = semaphore_wait(*sem);
240 FSHOW((stderr, "%s: os_sem_wait(%p) => %s\n", what, sem,
241 KERN_SUCCESS==ret ? "ok" : strerror(errno)));
242 switch (ret) {
243 case KERN_SUCCESS:
244 return;
245 /* It is unclear just when we can get this, but a sufficiently
246 * long wait seems to do that, at least sometimes.
248 * However, a wait that long is definitely abnormal for the
249 * GC, so we complain before retrying.
251 case KERN_OPERATION_TIMED_OUT:
252 fprintf(stderr, "%s: os_sem_wait(%p): %s", what, sem, strerror(errno));
253 /* This is analogous to POSIX EINTR. */
254 case KERN_ABORTED:
255 goto restart;
256 default:
257 lose("%s: os_sem_wait(%p): %lu, %s", what, sem, ret, strerror(errno));
261 void
262 os_sem_post(os_sem_t *sem, char *what)
264 if (KERN_SUCCESS!=semaphore_signal(*sem))
265 lose("%s: os_sem_post(%p): %s", what, sem, strerror(errno));
266 FSHOW((stderr, "%s: os_sem_post(%p) ok\n", what, sem));
269 void
270 os_sem_destroy(os_sem_t *sem)
272 if (-1==semaphore_destroy(mach_task_self(), *sem))
273 lose("os_sem_destroy(%p): %s", sem, strerror(errno));
276 #endif
278 #if defined(LISP_FEATURE_SB_WTIMER)
280 # error Completely untested. Go ahead! Remove this line, try your luck!
283 * Waitable timer implementation for the safepoint-based (SIGALRM-free)
284 * timer facility using kqueue.
286 * Unlike FreeBSD with its ms (!) timer resolution, Darwin supports ns
287 * timer resolution -- or at least it pretends to do so on the API
288 * level (?). To use it, we need the *64 versions of the functions and
289 * structures.
291 * Unfortunately, I don't run Darwin, and can't test this code, so it's
292 * just a hopeful translation from FreeBSD.
296 os_create_wtimer()
298 int kq = kqueue();
299 if (kq == -1)
300 lose("os_create_wtimer: kqueue");
301 return kq;
305 os_wait_for_wtimer(int kq)
307 struct kevent64_s ev;
308 int n;
309 if ( (n = kevent64(kq, 0, 0, &ev, 1, 0, 0)) == -1) {
310 if (errno != EINTR)
311 lose("os_wtimer_listen failed");
312 n = 0;
314 return n != 1;
317 void
318 os_close_wtimer(int kq)
320 if (close(kq) == -1)
321 lose("os_close_wtimer failed");
324 void
325 os_set_wtimer(int kq, int sec, int nsec)
327 int64_t nsec = ((int64_t) sec) * 1000000000 + (int64_t) nsec;
329 struct kevent64_s ev;
330 EV_SET64(&ev, 1, EVFILT_TIMER, EV_ADD|EV_ENABLE|EV_ONESHOT, NOTE_NSECONDS,
331 nsec, 0, 0, 0);
332 if (kevent64(kq, &ev, 1, 0, 0, 0, 0) == -1)
333 perror("os_set_wtimer: kevent");
336 void
337 os_cancel_wtimer(int kq)
339 struct kevent64_s ev;
340 EV_SET64(&ev, 1, EVFILT_TIMER, EV_DISABLE, 0, 0, 0, 0, 0);
341 if (kevent64(kq, &ev, 1, 0, 0, 0, 0) == -1 && errno != ENOENT)
342 perror("os_cancel_wtimer: kevent");
344 #endif
346 /* nanosleep() is not re-entrant on some versions of Darwin,
347 * reimplement it using the underlying syscalls. */
349 sb_nanosleep(time_t sec, int nsec) {
350 int ret;
351 mach_timespec_t current_time;
352 mach_timespec_t start_time;
354 if (sec < 0 || nsec >= NSEC_PER_SEC) {
355 errno = EINVAL;
356 return -1;
359 ret = clock_get_time(clock_port, &start_time);
360 if (ret != KERN_SUCCESS) {
361 lose(mach_error_string(ret));
364 for (;;) {
366 /* Older version do not have a wrapper. */
367 ret = syscall(SYS___semwait_signal, (int)clock_sem, (int)MACH_PORT_NULL, (int)1, (int)1,
368 (__int64_t)sec, (__int32_t)nsec);
369 if (ret < 0) {
370 if (errno == ETIMEDOUT) {
371 return 0;
373 if (errno == EINTR) {
374 ret = clock_get_time(clock_port, &current_time);
375 if (ret != KERN_SUCCESS) {
376 lose(mach_error_string(ret));
378 time_t elapsed_sec = current_time.tv_sec - start_time.tv_sec;
379 int elapsed_nsec = current_time.tv_nsec - start_time.tv_nsec;
380 if (elapsed_nsec < 0) {
381 elapsed_sec--;
382 elapsed_nsec += NSEC_PER_SEC;
384 sec -= elapsed_sec;
385 nsec -= elapsed_nsec;
386 if (nsec < 0) {
387 sec--;
388 nsec += NSEC_PER_SEC;
390 if (sec < 0 || (sec == 0 && nsec == 0)) {
391 return 0;
393 start_time = current_time;
394 } else {
395 errno = EINVAL;
396 return -1;
398 } else {
399 return -1;