Move 'temporary' bits so they don't conflict with windows/cygwin/dgux bits
[official-gcc.git] / boehm-gc / irix_threads.c
blobf45c4631c6e0bd007e2f8c2976e8ab71ec4835cf
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 <time.h>
29 # include <errno.h>
30 # include <unistd.h>
31 # include <sys/mman.h>
32 # include <sys/time.h>
34 #undef pthread_create
35 #undef pthread_sigmask
36 #undef pthread_join
38 void GC_thr_init();
40 #if 0
41 void GC_print_sig_mask()
43 sigset_t blocked;
44 int i;
46 if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
47 ABORT("pthread_sigmask");
48 GC_printf0("Blocked: ");
49 for (i = 1; i <= MAXSIG; i++) {
50 if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
52 GC_printf0("\n");
54 #endif
56 /* We use the allocation lock to protect thread-related data structures. */
58 /* The set of all known threads. We intercept thread creation and */
59 /* joins. We never actually create detached threads. We allocate all */
60 /* new thread stacks ourselves. These allow us to maintain this */
61 /* data structure. */
62 /* Protected by GC_thr_lock. */
63 /* Some of this should be declared volatile, but that's incosnsistent */
64 /* with some library routine declarations. */
65 typedef struct GC_Thread_Rep {
66 struct GC_Thread_Rep * next; /* More recently allocated threads */
67 /* with a given pthread id come */
68 /* first. (All but the first are */
69 /* guaranteed to be dead, but we may */
70 /* not yet have registered the join.) */
71 pthread_t id;
72 word stop;
73 # define NOT_STOPPED 0
74 # define PLEASE_STOP 1
75 # define STOPPED 2
76 word flags;
77 # define FINISHED 1 /* Thread has exited. */
78 # define DETACHED 2 /* Thread is intended to be detached. */
79 # define CLIENT_OWNS_STACK 4
80 /* Stack was supplied by client. */
81 ptr_t stack;
82 ptr_t stack_ptr; /* Valid only when stopped. */
83 /* But must be within stack region at */
84 /* all times. */
85 size_t stack_size; /* 0 for original thread. */
86 void * status; /* Used only to avoid premature */
87 /* reclamation of any data it might */
88 /* reference. */
89 } * GC_thread;
91 GC_thread GC_lookup_thread(pthread_t id);
94 * The only way to suspend threads given the pthread interface is to send
95 * signals. Unfortunately, this means we have to reserve
96 * a signal, and intercept client calls to change the signal mask.
98 # define SIG_SUSPEND (SIGRTMIN + 6)
100 pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER;
101 /* Number of threads stopped so far */
102 pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER;
103 pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER;
105 void GC_suspend_handler(int sig)
107 int dummy;
108 GC_thread me;
109 sigset_t all_sigs;
110 sigset_t old_sigs;
111 int i;
113 if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
114 me = GC_lookup_thread(pthread_self());
115 /* The lookup here is safe, since I'm doing this on behalf */
116 /* of a thread which holds the allocation lock in order */
117 /* to stop the world. Thus concurrent modification of the */
118 /* data structure is impossible. */
119 if (PLEASE_STOP != me -> stop) {
120 /* Misdirected signal. */
121 pthread_mutex_unlock(&GC_suspend_lock);
122 return;
124 pthread_mutex_lock(&GC_suspend_lock);
125 me -> stack_ptr = (ptr_t)(&dummy);
126 me -> stop = STOPPED;
127 pthread_cond_signal(&GC_suspend_ack_cv);
128 pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock);
129 pthread_mutex_unlock(&GC_suspend_lock);
130 /* GC_printf1("Continuing 0x%x\n", pthread_self()); */
134 GC_bool GC_thr_initialized = FALSE;
136 size_t GC_min_stack_sz;
138 size_t GC_page_sz;
140 # define N_FREE_LISTS 25
141 ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 };
142 /* GC_stack_free_lists[i] is free list for stacks of */
143 /* size GC_min_stack_sz*2**i. */
144 /* Free lists are linked through first word. */
146 /* Return a stack of size at least *stack_size. *stack_size is */
147 /* replaced by the actual stack size. */
148 /* Caller holds allocation lock. */
149 ptr_t GC_stack_alloc(size_t * stack_size)
151 register size_t requested_sz = *stack_size;
152 register size_t search_sz = GC_min_stack_sz;
153 register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */
154 register ptr_t result;
156 while (search_sz < requested_sz) {
157 search_sz *= 2;
158 index++;
160 if ((result = GC_stack_free_lists[index]) == 0
161 && (result = GC_stack_free_lists[index+1]) != 0) {
162 /* Try next size up. */
163 search_sz *= 2; index++;
165 if (result != 0) {
166 GC_stack_free_lists[index] = *(ptr_t *)result;
167 } else {
168 result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_sz);
169 result = (ptr_t)(((word)result + GC_page_sz) & ~(GC_page_sz - 1));
170 /* Protect hottest page to detect overflow. */
171 /* mprotect(result, GC_page_sz, PROT_NONE); */
172 result += GC_page_sz;
174 *stack_size = search_sz;
175 return(result);
178 /* Caller holds allocation lock. */
179 void GC_stack_free(ptr_t stack, size_t size)
181 register int index = 0;
182 register size_t search_sz = GC_min_stack_sz;
184 while (search_sz < size) {
185 search_sz *= 2;
186 index++;
188 if (search_sz != size) ABORT("Bad stack size");
189 *(ptr_t *)stack = GC_stack_free_lists[index];
190 GC_stack_free_lists[index] = stack;
195 # define THREAD_TABLE_SZ 128 /* Must be power of 2 */
196 volatile GC_thread GC_threads[THREAD_TABLE_SZ];
198 /* Add a thread to GC_threads. We assume it wasn't already there. */
199 /* Caller holds allocation lock. */
200 GC_thread GC_new_thread(pthread_t id)
202 int hv = ((word)id) % THREAD_TABLE_SZ;
203 GC_thread result;
204 static struct GC_Thread_Rep first_thread;
205 static GC_bool first_thread_used = FALSE;
207 if (!first_thread_used) {
208 result = &first_thread;
209 first_thread_used = TRUE;
210 /* Dont acquire allocation lock, since we may already hold it. */
211 } else {
212 result = (struct GC_Thread_Rep *)
213 GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
215 if (result == 0) return(0);
216 result -> id = id;
217 result -> next = GC_threads[hv];
218 GC_threads[hv] = result;
219 /* result -> flags = 0; */
220 /* result -> stop = 0; */
221 return(result);
224 /* Delete a thread from GC_threads. We assume it is there. */
225 /* (The code intentionally traps if it wasn't.) */
226 /* Caller holds allocation lock. */
227 void GC_delete_thread(pthread_t id)
229 int hv = ((word)id) % THREAD_TABLE_SZ;
230 register GC_thread p = GC_threads[hv];
231 register GC_thread prev = 0;
233 while (!pthread_equal(p -> id, id)) {
234 prev = p;
235 p = p -> next;
237 if (prev == 0) {
238 GC_threads[hv] = p -> next;
239 } else {
240 prev -> next = p -> next;
244 /* If a thread has been joined, but we have not yet */
245 /* been notified, then there may be more than one thread */
246 /* in the table with the same pthread id. */
247 /* This is OK, but we need a way to delete a specific one. */
248 void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
250 int hv = ((word)id) % THREAD_TABLE_SZ;
251 register GC_thread p = GC_threads[hv];
252 register GC_thread prev = 0;
254 while (p != gc_id) {
255 prev = p;
256 p = p -> next;
258 if (prev == 0) {
259 GC_threads[hv] = p -> next;
260 } else {
261 prev -> next = p -> next;
265 /* Return a GC_thread corresponding to a given thread_t. */
266 /* Returns 0 if it's not there. */
267 /* Caller holds allocation lock or otherwise inhibits */
268 /* updates. */
269 /* If there is more than one thread with the given id we */
270 /* return the most recent one. */
271 GC_thread GC_lookup_thread(pthread_t id)
273 int hv = ((word)id) % THREAD_TABLE_SZ;
274 register GC_thread p = GC_threads[hv];
276 while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
277 return(p);
281 /* Caller holds allocation lock. */
282 void GC_stop_world()
284 pthread_t my_thread = pthread_self();
285 register int i;
286 register GC_thread p;
287 register int result;
288 struct timespec timeout;
290 for (i = 0; i < THREAD_TABLE_SZ; i++) {
291 for (p = GC_threads[i]; p != 0; p = p -> next) {
292 if (p -> id != my_thread) {
293 if (p -> flags & FINISHED) {
294 p -> stop = STOPPED;
295 continue;
297 p -> stop = PLEASE_STOP;
298 result = pthread_kill(p -> id, SIG_SUSPEND);
299 /* GC_printf1("Sent signal to 0x%x\n", p -> id); */
300 switch(result) {
301 case ESRCH:
302 /* Not really there anymore. Possible? */
303 p -> stop = STOPPED;
304 break;
305 case 0:
306 break;
307 default:
308 ABORT("pthread_kill failed");
313 pthread_mutex_lock(&GC_suspend_lock);
314 for (i = 0; i < THREAD_TABLE_SZ; i++) {
315 for (p = GC_threads[i]; p != 0; p = p -> next) {
316 while (p -> id != my_thread && p -> stop != STOPPED) {
317 clock_gettime(CLOCK_REALTIME, &timeout);
318 timeout.tv_nsec += 50000000; /* 50 msecs */
319 if (timeout.tv_nsec >= 1000000000) {
320 timeout.tv_nsec -= 1000000000;
321 ++timeout.tv_sec;
323 result = pthread_cond_timedwait(&GC_suspend_ack_cv,
324 &GC_suspend_lock,
325 &timeout);
326 if (result == ETIMEDOUT) {
327 /* Signal was lost or misdirected. Try again. */
328 /* Duplicate signals should be benign. */
329 result = pthread_kill(p -> id, SIG_SUSPEND);
334 pthread_mutex_unlock(&GC_suspend_lock);
335 /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
338 /* Caller holds allocation lock. */
339 void GC_start_world()
341 GC_thread p;
342 unsigned i;
344 /* GC_printf0("World starting\n"); */
345 for (i = 0; i < THREAD_TABLE_SZ; i++) {
346 for (p = GC_threads[i]; p != 0; p = p -> next) {
347 p -> stop = NOT_STOPPED;
350 pthread_mutex_lock(&GC_suspend_lock);
351 /* All other threads are at pthread_cond_wait in signal handler. */
352 /* Otherwise we couldn't have acquired the lock. */
353 pthread_mutex_unlock(&GC_suspend_lock);
354 pthread_cond_broadcast(&GC_continue_cv);
357 # ifdef MMAP_STACKS
358 --> not really supported yet.
359 int GC_is_thread_stack(ptr_t addr)
361 register int i;
362 register GC_thread p;
364 for (i = 0; i < THREAD_TABLE_SZ; i++) {
365 for (p = GC_threads[i]; p != 0; p = p -> next) {
366 if (p -> stack_size != 0) {
367 if (p -> stack <= addr &&
368 addr < p -> stack + p -> stack_size)
369 return 1;
373 return 0;
375 # endif
377 /* We hold allocation lock. We assume the world is stopped. */
378 void GC_push_all_stacks()
380 register int i;
381 register GC_thread p;
382 register ptr_t sp = GC_approx_sp();
383 register ptr_t lo, hi;
384 pthread_t me = pthread_self();
386 if (!GC_thr_initialized) GC_thr_init();
387 /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
388 for (i = 0; i < THREAD_TABLE_SZ; i++) {
389 for (p = GC_threads[i]; p != 0; p = p -> next) {
390 if (p -> flags & FINISHED) continue;
391 if (pthread_equal(p -> id, me)) {
392 lo = GC_approx_sp();
393 } else {
394 lo = p -> stack_ptr;
396 if (p -> stack_size != 0) {
397 hi = p -> stack + p -> stack_size;
398 } else {
399 /* The original stack. */
400 hi = GC_stackbottom;
402 GC_push_all_stack(lo, hi);
408 /* We hold the allocation lock. */
409 void GC_thr_init()
411 GC_thread t;
412 struct sigaction act;
414 GC_thr_initialized = TRUE;
415 GC_min_stack_sz = HBLKSIZE;
416 GC_page_sz = sysconf(_SC_PAGESIZE);
417 (void) sigaction(SIG_SUSPEND, 0, &act);
418 if (act.sa_handler != SIG_DFL)
419 ABORT("Previously installed SIG_SUSPEND handler");
420 /* Install handler. */
421 act.sa_handler = GC_suspend_handler;
422 act.sa_flags = SA_RESTART;
423 (void) sigemptyset(&act.sa_mask);
424 if (0 != sigaction(SIG_SUSPEND, &act, 0))
425 ABORT("Failed to install SIG_SUSPEND handler");
426 /* Add the initial thread, so we can stop it. */
427 t = GC_new_thread(pthread_self());
428 t -> stack_size = 0;
429 t -> stack_ptr = (ptr_t)(&t);
430 t -> flags = DETACHED;
433 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
435 sigset_t fudged_set;
437 if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
438 fudged_set = *set;
439 sigdelset(&fudged_set, SIG_SUSPEND);
440 set = &fudged_set;
442 return(pthread_sigmask(how, set, oset));
445 struct start_info {
446 void *(*start_routine)(void *);
447 void *arg;
450 void GC_thread_exit_proc(void *dummy)
452 GC_thread me;
454 LOCK();
455 me = GC_lookup_thread(pthread_self());
456 if (me -> flags & DETACHED) {
457 GC_delete_thread(pthread_self());
458 } else {
459 me -> flags |= FINISHED;
461 UNLOCK();
464 int GC_pthread_join(pthread_t thread, void **retval)
466 int result;
467 GC_thread thread_gc_id;
469 LOCK();
470 thread_gc_id = GC_lookup_thread(thread);
471 /* This is guaranteed to be the intended one, since the thread id */
472 /* cant have been recycled by pthreads. */
473 UNLOCK();
474 result = pthread_join(thread, retval);
475 LOCK();
476 /* Here the pthread thread id may have been recycled. */
477 GC_delete_gc_thread(thread, thread_gc_id);
478 UNLOCK();
479 return result;
482 void * GC_start_routine(void * arg)
484 struct start_info * si = arg;
485 void * result;
486 GC_thread me;
488 LOCK();
489 me = GC_lookup_thread(pthread_self());
490 UNLOCK();
491 pthread_cleanup_push(GC_thread_exit_proc, 0);
492 result = (*(si -> start_routine))(si -> arg);
493 me -> status = result;
494 me -> flags |= FINISHED;
495 pthread_cleanup_pop(1);
496 /* This involves acquiring the lock, ensuring that we can't exit */
497 /* while a collection that thinks we're alive is trying to stop */
498 /* us. */
499 return(result);
503 GC_pthread_create(pthread_t *new_thread,
504 const pthread_attr_t *attr,
505 void *(*start_routine)(void *), void *arg)
507 int result;
508 GC_thread t;
509 pthread_t my_new_thread;
510 void * stack;
511 size_t stacksize;
512 pthread_attr_t new_attr;
513 int detachstate;
514 word my_flags = 0;
515 struct start_info * si = GC_malloc(sizeof(struct start_info));
517 if (0 == si) return(ENOMEM);
518 si -> start_routine = start_routine;
519 si -> arg = arg;
520 LOCK();
521 if (!GC_thr_initialized) GC_thr_init();
522 if (NULL == attr) {
523 stack = 0;
524 (void) pthread_attr_init(&new_attr);
525 } else {
526 new_attr = *attr;
527 pthread_attr_getstackaddr(&new_attr, &stack);
529 pthread_attr_getstacksize(&new_attr, &stacksize);
530 pthread_attr_getdetachstate(&new_attr, &detachstate);
531 if (stacksize < GC_min_stack_sz) ABORT("Stack too small");
532 if (0 == stack) {
533 stack = (void *)GC_stack_alloc(&stacksize);
534 if (0 == stack) {
535 UNLOCK();
536 return(ENOMEM);
538 pthread_attr_setstackaddr(&new_attr, stack);
539 } else {
540 my_flags |= CLIENT_OWNS_STACK;
542 if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
543 result = pthread_create(&my_new_thread, &new_attr, GC_start_routine, si);
544 /* No GC can start until the thread is registered, since we hold */
545 /* the allocation lock. */
546 if (0 == result) {
547 t = GC_new_thread(my_new_thread);
548 t -> flags = my_flags;
549 t -> stack = stack;
550 t -> stack_size = stacksize;
551 t -> stack_ptr = (ptr_t)stack + stacksize - sizeof(word);
552 if (0 != new_thread) *new_thread = my_new_thread;
553 } else if (!(my_flags & CLIENT_OWNS_STACK)) {
554 GC_stack_free(stack, stacksize);
556 UNLOCK();
557 /* pthread_attr_destroy(&new_attr); */
558 return(result);
561 GC_bool GC_collecting = 0; /* A hint that we're in the collector and */
562 /* holding the allocation lock for an */
563 /* extended period. */
565 /* Reasonably fast spin locks. Basically the same implementation */
566 /* as STL alloc.h. This isn't really the right way to do this. */
567 /* but until the POSIX scheduling mess gets straightened out ... */
569 unsigned long GC_allocate_lock = 0;
571 #define SLEEP_THRESHOLD 3
573 void GC_lock()
575 # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */
576 # define high_spin_max 1000 /* spin cycles for multiprocessor */
577 static unsigned spin_max = low_spin_max;
578 unsigned my_spin_max;
579 static unsigned last_spins = 0;
580 unsigned my_last_spins;
581 volatile unsigned junk;
582 # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
583 int i;
585 if (!GC_test_and_set(&GC_allocate_lock, 1)) {
586 return;
588 junk = 0;
589 my_spin_max = spin_max;
590 my_last_spins = last_spins;
591 for (i = 0; i < my_spin_max; i++) {
592 if (GC_collecting) goto yield;
593 if (i < my_last_spins/2 || GC_allocate_lock) {
594 PAUSE;
595 continue;
597 if (!GC_test_and_set(&GC_allocate_lock, 1)) {
599 * got it!
600 * Spinning worked. Thus we're probably not being scheduled
601 * against the other process with which we were contending.
602 * Thus it makes sense to spin longer the next time.
604 last_spins = i;
605 spin_max = high_spin_max;
606 return;
609 /* We are probably being scheduled against the other process. Sleep. */
610 spin_max = low_spin_max;
611 yield:
612 for (i = 0;; ++i) {
613 if (!GC_test_and_set(&GC_allocate_lock, 1)) {
614 return;
616 if (i < SLEEP_THRESHOLD) {
617 sched_yield();
618 } else {
619 struct timespec ts;
621 if (i > 26) i = 26;
622 /* Don't wait for more than about 60msecs, even */
623 /* under extreme contention. */
624 ts.tv_sec = 0;
625 ts.tv_nsec = 1 << i;
626 nanosleep(&ts, 0);
633 # else
635 #ifndef LINT
636 int GC_no_Irix_threads;
637 #endif
639 # endif /* IRIX_THREADS */