* Makefile.in (dbxout.o): Depend on ggc.h.
[official-gcc.git] / boehm-gc / irix_threads.c
blob5efca2110343942b2adf44a736b25cb0af4067ae
1 /*
2 * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
3 * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
5 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
6 * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
8 * Permission is hereby granted to use or copy this program
9 * for any purpose, provided the above notices are retained on all copies.
10 * Permission to modify the code and to distribute modified code is granted,
11 * provided the above notices are retained, and a notice that the code was
12 * modified is included with the above copyright notice.
15 * Support code for Irix (>=6.2) Pthreads. This relies on properties
16 * not guaranteed by the Pthread standard. It may or may not be portable
17 * to other implementations.
19 * Note that there is a lot of code duplication between linux_threads.c
20 * and irix_threads.c; any changes made here may need to be reflected
21 * there too.
24 # if defined(IRIX_THREADS)
26 # include "gc_priv.h"
27 # include <pthread.h>
28 # include <semaphore.h>
29 # include <time.h>
30 # include <errno.h>
31 # include <unistd.h>
32 # include <sys/mman.h>
33 # include <sys/time.h>
35 #undef pthread_create
36 #undef pthread_sigmask
37 #undef pthread_join
39 void GC_thr_init();
41 #if 0
42 void GC_print_sig_mask()
44 sigset_t blocked;
45 int i;
47 if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
48 ABORT("pthread_sigmask");
49 GC_printf0("Blocked: ");
50 for (i = 1; i <= MAXSIG; i++) {
51 if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
53 GC_printf0("\n");
55 #endif
57 /* We use the allocation lock to protect thread-related data structures. */
59 /* The set of all known threads. We intercept thread creation and */
60 /* joins. We never actually create detached threads. We allocate all */
61 /* new thread stacks ourselves. These allow us to maintain this */
62 /* data structure. */
63 /* Protected by GC_thr_lock. */
64 /* Some of this should be declared volatile, but that's incosnsistent */
65 /* with some library routine declarations. */
66 typedef struct GC_Thread_Rep {
67 struct GC_Thread_Rep * next; /* More recently allocated threads */
68 /* with a given pthread id come */
69 /* first. (All but the first are */
70 /* guaranteed to be dead, but we may */
71 /* not yet have registered the join.) */
72 pthread_t id;
73 word stop;
74 # define NOT_STOPPED 0
75 # define PLEASE_STOP 1
76 # define STOPPED 2
77 word flags;
78 # define FINISHED 1 /* Thread has exited. */
79 # define DETACHED 2 /* Thread is intended to be detached. */
80 # define CLIENT_OWNS_STACK 4
81 /* Stack was supplied by client. */
82 ptr_t stack;
83 ptr_t stack_ptr; /* Valid only when stopped. */
84 /* But must be within stack region at */
85 /* all times. */
86 size_t stack_size; /* 0 for original thread. */
87 void * status; /* Used only to avoid premature */
88 /* reclamation of any data it might */
89 /* reference. */
90 } * GC_thread;
92 GC_thread GC_lookup_thread(pthread_t id);
95 * The only way to suspend threads given the pthread interface is to send
96 * signals. Unfortunately, this means we have to reserve
97 * a signal, and intercept client calls to change the signal mask.
99 # define SIG_SUSPEND (SIGRTMIN + 6)
101 pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER;
102 /* Number of threads stopped so far */
103 pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER;
104 pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER;
106 void GC_suspend_handler(int sig)
108 int dummy;
109 GC_thread me;
110 sigset_t all_sigs;
111 sigset_t old_sigs;
112 int i;
114 if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
115 me = GC_lookup_thread(pthread_self());
116 /* The lookup here is safe, since I'm doing this on behalf */
117 /* of a thread which holds the allocation lock in order */
118 /* to stop the world. Thus concurrent modification of the */
119 /* data structure is impossible. */
120 if (PLEASE_STOP != me -> stop) {
121 /* Misdirected signal. */
122 pthread_mutex_unlock(&GC_suspend_lock);
123 return;
125 pthread_mutex_lock(&GC_suspend_lock);
126 me -> stack_ptr = (ptr_t)(&dummy);
127 me -> stop = STOPPED;
128 pthread_cond_signal(&GC_suspend_ack_cv);
129 pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock);
130 pthread_mutex_unlock(&GC_suspend_lock);
131 /* GC_printf1("Continuing 0x%x\n", pthread_self()); */
135 GC_bool GC_thr_initialized = FALSE;
137 size_t GC_min_stack_sz;
139 size_t GC_page_sz;
141 # define N_FREE_LISTS 25
142 ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 };
143 /* GC_stack_free_lists[i] is free list for stacks of */
144 /* size GC_min_stack_sz*2**i. */
145 /* Free lists are linked through first word. */
147 /* Return a stack of size at least *stack_size. *stack_size is */
148 /* replaced by the actual stack size. */
149 /* Caller holds allocation lock. */
150 ptr_t GC_stack_alloc(size_t * stack_size)
152 register size_t requested_sz = *stack_size;
153 register size_t search_sz = GC_min_stack_sz;
154 register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */
155 register ptr_t result;
157 while (search_sz < requested_sz) {
158 search_sz *= 2;
159 index++;
161 if ((result = GC_stack_free_lists[index]) == 0
162 && (result = GC_stack_free_lists[index+1]) != 0) {
163 /* Try next size up. */
164 search_sz *= 2; index++;
166 if (result != 0) {
167 GC_stack_free_lists[index] = *(ptr_t *)result;
168 } else {
169 result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_sz);
170 result = (ptr_t)(((word)result + GC_page_sz) & ~(GC_page_sz - 1));
171 /* Protect hottest page to detect overflow. */
172 /* mprotect(result, GC_page_sz, PROT_NONE); */
173 result += GC_page_sz;
175 *stack_size = search_sz;
176 return(result);
179 /* Caller holds allocation lock. */
180 void GC_stack_free(ptr_t stack, size_t size)
182 register int index = 0;
183 register size_t search_sz = GC_min_stack_sz;
185 while (search_sz < size) {
186 search_sz *= 2;
187 index++;
189 if (search_sz != size) ABORT("Bad stack size");
190 *(ptr_t *)stack = GC_stack_free_lists[index];
191 GC_stack_free_lists[index] = stack;
196 # define THREAD_TABLE_SZ 128 /* Must be power of 2 */
197 volatile GC_thread GC_threads[THREAD_TABLE_SZ];
199 /* Add a thread to GC_threads. We assume it wasn't already there. */
200 /* Caller holds allocation lock. */
201 GC_thread GC_new_thread(pthread_t id)
203 int hv = ((word)id) % THREAD_TABLE_SZ;
204 GC_thread result;
205 static struct GC_Thread_Rep first_thread;
206 static GC_bool first_thread_used = FALSE;
208 if (!first_thread_used) {
209 result = &first_thread;
210 first_thread_used = TRUE;
211 /* Dont acquire allocation lock, since we may already hold it. */
212 } else {
213 result = (struct GC_Thread_Rep *)
214 GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
216 if (result == 0) return(0);
217 result -> id = id;
218 result -> next = GC_threads[hv];
219 GC_threads[hv] = result;
220 /* result -> flags = 0; */
221 /* result -> stop = 0; */
222 return(result);
225 /* Delete a thread from GC_threads. We assume it is there. */
226 /* (The code intentionally traps if it wasn't.) */
227 /* Caller holds allocation lock. */
228 void GC_delete_thread(pthread_t id)
230 int hv = ((word)id) % THREAD_TABLE_SZ;
231 register GC_thread p = GC_threads[hv];
232 register GC_thread prev = 0;
234 while (!pthread_equal(p -> id, id)) {
235 prev = p;
236 p = p -> next;
238 if (prev == 0) {
239 GC_threads[hv] = p -> next;
240 } else {
241 prev -> next = p -> next;
245 /* If a thread has been joined, but we have not yet */
246 /* been notified, then there may be more than one thread */
247 /* in the table with the same pthread id. */
248 /* This is OK, but we need a way to delete a specific one. */
249 void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
251 int hv = ((word)id) % THREAD_TABLE_SZ;
252 register GC_thread p = GC_threads[hv];
253 register GC_thread prev = 0;
255 while (p != gc_id) {
256 prev = p;
257 p = p -> next;
259 if (prev == 0) {
260 GC_threads[hv] = p -> next;
261 } else {
262 prev -> next = p -> next;
266 /* Return a GC_thread corresponding to a given thread_t. */
267 /* Returns 0 if it's not there. */
268 /* Caller holds allocation lock or otherwise inhibits */
269 /* updates. */
270 /* If there is more than one thread with the given id we */
271 /* return the most recent one. */
272 GC_thread GC_lookup_thread(pthread_t id)
274 int hv = ((word)id) % THREAD_TABLE_SZ;
275 register GC_thread p = GC_threads[hv];
277 while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
278 return(p);
282 /* Caller holds allocation lock. */
283 void GC_stop_world()
285 pthread_t my_thread = pthread_self();
286 register int i;
287 register GC_thread p;
288 register int result;
289 struct timespec timeout;
291 for (i = 0; i < THREAD_TABLE_SZ; i++) {
292 for (p = GC_threads[i]; p != 0; p = p -> next) {
293 if (p -> id != my_thread) {
294 if (p -> flags & FINISHED) {
295 p -> stop = STOPPED;
296 continue;
298 p -> stop = PLEASE_STOP;
299 result = pthread_kill(p -> id, SIG_SUSPEND);
300 /* GC_printf1("Sent signal to 0x%x\n", p -> id); */
301 switch(result) {
302 case ESRCH:
303 /* Not really there anymore. Possible? */
304 p -> stop = STOPPED;
305 break;
306 case 0:
307 break;
308 default:
309 ABORT("pthread_kill failed");
314 pthread_mutex_lock(&GC_suspend_lock);
315 for (i = 0; i < THREAD_TABLE_SZ; i++) {
316 for (p = GC_threads[i]; p != 0; p = p -> next) {
317 while (p -> id != my_thread && p -> stop != STOPPED) {
318 clock_gettime(CLOCK_REALTIME, &timeout);
319 timeout.tv_nsec += 50000000; /* 50 msecs */
320 if (timeout.tv_nsec >= 1000000000) {
321 timeout.tv_nsec -= 1000000000;
322 ++timeout.tv_sec;
324 result = pthread_cond_timedwait(&GC_suspend_ack_cv,
325 &GC_suspend_lock,
326 &timeout);
327 if (result == ETIMEDOUT) {
328 /* Signal was lost or misdirected. Try again. */
329 /* Duplicate signals should be benign. */
330 result = pthread_kill(p -> id, SIG_SUSPEND);
335 pthread_mutex_unlock(&GC_suspend_lock);
336 /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
339 /* Caller holds allocation lock. */
340 void GC_start_world()
342 GC_thread p;
343 unsigned i;
345 /* GC_printf0("World starting\n"); */
346 for (i = 0; i < THREAD_TABLE_SZ; i++) {
347 for (p = GC_threads[i]; p != 0; p = p -> next) {
348 p -> stop = NOT_STOPPED;
351 pthread_mutex_lock(&GC_suspend_lock);
352 /* All other threads are at pthread_cond_wait in signal handler. */
353 /* Otherwise we couldn't have acquired the lock. */
354 pthread_mutex_unlock(&GC_suspend_lock);
355 pthread_cond_broadcast(&GC_continue_cv);
358 # ifdef MMAP_STACKS
359 --> not really supported yet.
360 int GC_is_thread_stack(ptr_t addr)
362 register int i;
363 register GC_thread p;
365 for (i = 0; i < THREAD_TABLE_SZ; i++) {
366 for (p = GC_threads[i]; p != 0; p = p -> next) {
367 if (p -> stack_size != 0) {
368 if (p -> stack <= addr &&
369 addr < p -> stack + p -> stack_size)
370 return 1;
374 return 0;
376 # endif
378 /* We hold allocation lock. We assume the world is stopped. */
379 void GC_push_all_stacks()
381 register int i;
382 register GC_thread p;
383 register ptr_t sp = GC_approx_sp();
384 register ptr_t lo, hi;
385 pthread_t me = pthread_self();
387 if (!GC_thr_initialized) GC_thr_init();
388 /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
389 for (i = 0; i < THREAD_TABLE_SZ; i++) {
390 for (p = GC_threads[i]; p != 0; p = p -> next) {
391 if (p -> flags & FINISHED) continue;
392 if (pthread_equal(p -> id, me)) {
393 lo = GC_approx_sp();
394 } else {
395 lo = p -> stack_ptr;
397 if (p -> stack_size != 0) {
398 hi = p -> stack + p -> stack_size;
399 } else {
400 /* The original stack. */
401 hi = GC_stackbottom;
403 GC_push_all_stack(lo, hi);
409 /* We hold the allocation lock. */
410 void GC_thr_init()
412 GC_thread t;
413 struct sigaction act;
415 if (GC_thr_initialized) return;
416 GC_thr_initialized = TRUE;
417 GC_min_stack_sz = HBLKSIZE;
418 GC_page_sz = sysconf(_SC_PAGESIZE);
419 (void) sigaction(SIG_SUSPEND, 0, &act);
420 if (act.sa_handler != SIG_DFL)
421 ABORT("Previously installed SIG_SUSPEND handler");
422 /* Install handler. */
423 act.sa_handler = GC_suspend_handler;
424 act.sa_flags = SA_RESTART;
425 (void) sigemptyset(&act.sa_mask);
426 if (0 != sigaction(SIG_SUSPEND, &act, 0))
427 ABORT("Failed to install SIG_SUSPEND handler");
428 /* Add the initial thread, so we can stop it. */
429 t = GC_new_thread(pthread_self());
430 t -> stack_size = 0;
431 t -> stack_ptr = (ptr_t)(&t);
432 t -> flags = DETACHED;
435 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
437 sigset_t fudged_set;
439 if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
440 fudged_set = *set;
441 sigdelset(&fudged_set, SIG_SUSPEND);
442 set = &fudged_set;
444 return(pthread_sigmask(how, set, oset));
447 struct start_info {
448 void *(*start_routine)(void *);
449 void *arg;
450 word flags;
451 ptr_t stack;
452 size_t stack_size;
453 sem_t registered; /* 1 ==> in our thread table, but */
454 /* parent hasn't yet noticed. */
457 void GC_thread_exit_proc(void *arg)
459 GC_thread me;
461 LOCK();
462 me = GC_lookup_thread(pthread_self());
463 if (me -> flags & DETACHED) {
464 GC_delete_thread(pthread_self());
465 } else {
466 me -> flags |= FINISHED;
468 UNLOCK();
471 int GC_pthread_join(pthread_t thread, void **retval)
473 int result;
474 GC_thread thread_gc_id;
476 LOCK();
477 thread_gc_id = GC_lookup_thread(thread);
478 /* This is guaranteed to be the intended one, since the thread id */
479 /* cant have been recycled by pthreads. */
480 UNLOCK();
481 result = pthread_join(thread, retval);
482 /* Some versions of the Irix pthreads library can erroneously */
483 /* return EINTR when the call succeeds. */
484 if (EINTR == result) result = 0;
485 LOCK();
486 /* Here the pthread thread id may have been recycled. */
487 GC_delete_gc_thread(thread, thread_gc_id);
488 UNLOCK();
489 return result;
492 void * GC_start_routine(void * arg)
494 struct start_info * si = arg;
495 void * result;
496 GC_thread me;
497 pthread_t my_pthread;
498 void *(*start)(void *);
499 void *start_arg;
501 my_pthread = pthread_self();
502 /* If a GC occurs before the thread is registered, that GC will */
503 /* ignore this thread. That's fine, since it will block trying to */
504 /* acquire the allocation lock, and won't yet hold interesting */
505 /* pointers. */
506 LOCK();
507 /* We register the thread here instead of in the parent, so that */
508 /* we don't need to hold the allocation lock during pthread_create. */
509 /* Holding the allocation lock there would make REDIRECT_MALLOC */
510 /* impossible. It probably still doesn't work, but we're a little */
511 /* closer ... */
512 /* This unfortunately means that we have to be careful the parent */
513 /* doesn't try to do a pthread_join before we're registered. */
514 me = GC_new_thread(my_pthread);
515 me -> flags = si -> flags;
516 me -> stack = si -> stack;
517 me -> stack_size = si -> stack_size;
518 me -> stack_ptr = (ptr_t)si -> stack + si -> stack_size - sizeof(word);
519 UNLOCK();
520 start = si -> start_routine;
521 start_arg = si -> arg;
522 sem_post(&(si -> registered));
523 pthread_cleanup_push(GC_thread_exit_proc, 0);
524 result = (*start)(start_arg);
525 me -> status = result;
526 me -> flags |= FINISHED;
527 pthread_cleanup_pop(1);
528 /* This involves acquiring the lock, ensuring that we can't exit */
529 /* while a collection that thinks we're alive is trying to stop */
530 /* us. */
531 return(result);
535 GC_pthread_create(pthread_t *new_thread,
536 const pthread_attr_t *attr,
537 void *(*start_routine)(void *), void *arg)
539 int result;
540 GC_thread t;
541 void * stack;
542 size_t stacksize;
543 pthread_attr_t new_attr;
544 int detachstate;
545 word my_flags = 0;
546 struct start_info * si = GC_malloc(sizeof(struct start_info));
547 /* This is otherwise saved only in an area mmapped by the thread */
548 /* library, which isn't visible to the collector. */
550 if (0 == si) return(ENOMEM);
551 sem_init(&(si -> registered), 0, 0);
552 si -> start_routine = start_routine;
553 si -> arg = arg;
554 LOCK();
555 if (!GC_thr_initialized) GC_thr_init();
556 if (NULL == attr) {
557 stack = 0;
558 (void) pthread_attr_init(&new_attr);
559 } else {
560 new_attr = *attr;
561 pthread_attr_getstackaddr(&new_attr, &stack);
563 pthread_attr_getstacksize(&new_attr, &stacksize);
564 pthread_attr_getdetachstate(&new_attr, &detachstate);
565 if (stacksize < GC_min_stack_sz) ABORT("Stack too small");
566 if (0 == stack) {
567 stack = (void *)GC_stack_alloc(&stacksize);
568 if (0 == stack) {
569 UNLOCK();
570 return(ENOMEM);
572 pthread_attr_setstackaddr(&new_attr, stack);
573 } else {
574 my_flags |= CLIENT_OWNS_STACK;
576 if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
577 si -> flags = my_flags;
578 si -> stack = stack;
579 si -> stack_size = stacksize;
580 result = pthread_create(new_thread, &new_attr, GC_start_routine, si);
581 if (0 == new_thread && !(my_flags & CLIENT_OWNS_STACK)) {
582 GC_stack_free(stack, stacksize);
584 UNLOCK();
585 /* Wait until child has been added to the thread table. */
586 /* This also ensures that we hold onto si until the child is done */
587 /* with it. Thus it doesn't matter whether it is otherwise */
588 /* visible to the collector. */
589 if (0 != sem_wait(&(si -> registered))) ABORT("sem_wait failed");
590 sem_destroy(&(si -> registered));
591 /* pthread_attr_destroy(&new_attr); */
592 return(result);
595 GC_bool GC_collecting = 0; /* A hint that we're in the collector and */
596 /* holding the allocation lock for an */
597 /* extended period. */
599 /* Reasonably fast spin locks. Basically the same implementation */
600 /* as STL alloc.h. This isn't really the right way to do this. */
601 /* but until the POSIX scheduling mess gets straightened out ... */
603 unsigned long GC_allocate_lock = 0;
605 #define SLEEP_THRESHOLD 3
607 void GC_lock()
609 # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */
610 # define high_spin_max 1000 /* spin cycles for multiprocessor */
611 static unsigned spin_max = low_spin_max;
612 unsigned my_spin_max;
613 static unsigned last_spins = 0;
614 unsigned my_last_spins;
615 volatile unsigned junk;
616 # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
617 int i;
619 if (!GC_test_and_set(&GC_allocate_lock, 1)) {
620 return;
622 junk = 0;
623 my_spin_max = spin_max;
624 my_last_spins = last_spins;
625 for (i = 0; i < my_spin_max; i++) {
626 if (GC_collecting) goto yield;
627 if (i < my_last_spins/2 || GC_allocate_lock) {
628 PAUSE;
629 continue;
631 if (!GC_test_and_set(&GC_allocate_lock, 1)) {
633 * got it!
634 * Spinning worked. Thus we're probably not being scheduled
635 * against the other process with which we were contending.
636 * Thus it makes sense to spin longer the next time.
638 last_spins = i;
639 spin_max = high_spin_max;
640 return;
643 /* We are probably being scheduled against the other process. Sleep. */
644 spin_max = low_spin_max;
645 yield:
646 for (i = 0;; ++i) {
647 if (!GC_test_and_set(&GC_allocate_lock, 1)) {
648 return;
650 if (i < SLEEP_THRESHOLD) {
651 sched_yield();
652 } else {
653 struct timespec ts;
655 if (i > 26) i = 26;
656 /* Don't wait for more than about 60msecs, even */
657 /* under extreme contention. */
658 ts.tv_sec = 0;
659 ts.tv_nsec = 1 << i;
660 nanosleep(&ts, 0);
667 # else
669 #ifndef LINT
670 int GC_no_Irix_threads;
671 #endif
673 # endif /* IRIX_THREADS */