OSDN Git Service

Initial revision
[pf3gnuchains/gcc-fork.git] / boehm-gc / irix_threads.c
1 /* 
2  * Copyright (c) 1994 by Xerox Corporation.  All rights reserved.
3  * Copyright (c) 1996 by Silicon Graphics.  All rights reserved.
4  *
5  * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
6  * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
7  *
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.
13  */
14 /*
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.
18  *
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.
22  */
23
24 # if defined(IRIX_THREADS)
25
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>
33
34 #undef pthread_create
35 #undef pthread_sigmask
36 #undef pthread_join
37
38 void GC_thr_init();
39
40 #if 0
41 void GC_print_sig_mask()
42 {
43     sigset_t blocked;
44     int i;
45
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); }
51     }
52     GC_printf0("\n");
53 }
54 #endif
55
56 /* We use the allocation lock to protect thread-related data structures. */
57
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;
90
91 GC_thread GC_lookup_thread(pthread_t id);
92
93 /*
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.
97  */
98 # define SIG_SUSPEND (SIGRTMIN + 6)
99
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;
104
105 void GC_suspend_handler(int sig)
106 {
107     int dummy;
108     GC_thread me;
109     sigset_t all_sigs;
110     sigset_t old_sigs;
111     int i;
112
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;
123     }
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()); */
131 }
132
133
134 GC_bool GC_thr_initialized = FALSE;
135
136 size_t GC_min_stack_sz;
137
138 size_t GC_page_sz;
139
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.            */
145
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)
150 {
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;
155     
156     while (search_sz < requested_sz) {
157         search_sz *= 2;
158         index++;
159     }
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++;
164     }
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;
173     }
174     *stack_size = search_sz;
175     return(result);
176 }
177
178 /* Caller holds allocation lock.                                        */
179 void GC_stack_free(ptr_t stack, size_t size)
180 {
181     register int index = 0;
182     register size_t search_sz = GC_min_stack_sz;
183     
184     while (search_sz < size) {
185         search_sz *= 2;
186         index++;
187     }
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;
191 }
192
193
194
195 # define THREAD_TABLE_SZ 128    /* Must be power of 2   */
196 volatile GC_thread GC_threads[THREAD_TABLE_SZ];
197
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)
201 {
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;
206     
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);
214     }
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);
222 }
223
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)
228 {
229     int hv = ((word)id) % THREAD_TABLE_SZ;
230     register GC_thread p = GC_threads[hv];
231     register GC_thread prev = 0;
232     
233     while (!pthread_equal(p -> id, id)) {
234         prev = p;
235         p = p -> next;
236     }
237     if (prev == 0) {
238         GC_threads[hv] = p -> next;
239     } else {
240         prev -> next = p -> next;
241     }
242 }
243
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)
249 {
250     int hv = ((word)id) % THREAD_TABLE_SZ;
251     register GC_thread p = GC_threads[hv];
252     register GC_thread prev = 0;
253
254     while (p != gc_id) {
255         prev = p;
256         p = p -> next;
257     }
258     if (prev == 0) {
259         GC_threads[hv] = p -> next;
260     } else {
261         prev -> next = p -> next;
262     }
263 }
264
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)
272 {
273     int hv = ((word)id) % THREAD_TABLE_SZ;
274     register GC_thread p = GC_threads[hv];
275     
276     while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
277     return(p);
278 }
279
280
281 /* Caller holds allocation lock.        */
282 void GC_stop_world()
283 {
284     pthread_t my_thread = pthread_self();
285     register int i;
286     register GC_thread p;
287     register int result;
288     struct timespec timeout;
289     
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;
296             }
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");
309             }
310         }
311       }
312     }
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;
322             }
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);
330             }
331         }
332       }
333     }
334     pthread_mutex_unlock(&GC_suspend_lock);
335     /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
336 }
337
338 /* Caller holds allocation lock.        */
339 void GC_start_world()
340 {
341     GC_thread p;
342     unsigned i;
343
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;
348       }
349     }
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);
355 }
356
357 # ifdef MMAP_STACKS
358 --> not really supported yet.
359 int GC_is_thread_stack(ptr_t addr)
360 {
361     register int i;
362     register GC_thread p;
363
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;
370        }
371       }
372     }
373     return 0;
374 }
375 # endif
376
377 /* We hold allocation lock.  We assume the world is stopped.    */
378 void GC_push_all_stacks()
379 {
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();
385     
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;
395         }
396         if (p -> stack_size != 0) {
397             hi = p -> stack + p -> stack_size;
398         } else {
399             /* The original stack. */
400             hi = GC_stackbottom;
401         }
402         GC_push_all_stack(lo, hi);
403       }
404     }
405 }
406
407
408 /* We hold the allocation lock. */
409 void GC_thr_init()
410 {
411     GC_thread t;
412     struct sigaction act;
413
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;
431 }
432
433 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
434 {
435     sigset_t fudged_set;
436     
437     if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
438         fudged_set = *set;
439         sigdelset(&fudged_set, SIG_SUSPEND);
440         set = &fudged_set;
441     }
442     return(pthread_sigmask(how, set, oset));
443 }
444
445 struct start_info {
446     void *(*start_routine)(void *);
447     void *arg;
448 };
449
450 void GC_thread_exit_proc(void *dummy)
451 {
452     GC_thread me;
453
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;
460     }
461     UNLOCK();
462 }
463
464 int GC_pthread_join(pthread_t thread, void **retval)
465 {
466     int result;
467     GC_thread thread_gc_id;
468     
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;
480 }
481
482 void * GC_start_routine(void * arg)
483 {
484     struct start_info * si = arg;
485     void * result;
486     GC_thread me;
487
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);
500 }
501
502 int
503 GC_pthread_create(pthread_t *new_thread,
504                   const pthread_attr_t *attr,
505                   void *(*start_routine)(void *), void *arg)
506 {
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)); 
516
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);
528     }
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);
537         }
538         pthread_attr_setstackaddr(&new_attr, stack);
539     } else {
540         my_flags |= CLIENT_OWNS_STACK;
541     }
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);
555     }        
556     UNLOCK();  
557     /* pthread_attr_destroy(&new_attr); */
558     return(result);
559 }
560
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.                             */
564
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 ...  */
568
569 unsigned long GC_allocate_lock = 0;
570
571 #define SLEEP_THRESHOLD 3
572
573 void GC_lock()
574 {
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;
584
585     if (!GC_test_and_set(&GC_allocate_lock, 1)) {
586         return;
587     }
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;
596         }
597         if (!GC_test_and_set(&GC_allocate_lock, 1)) {
598             /*
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.
603              */
604             last_spins = i;
605             spin_max = high_spin_max;
606             return;
607         }
608     }
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;
615         }
616         if (i < SLEEP_THRESHOLD) {
617             sched_yield();
618         } else {
619             struct timespec ts;
620         
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);
627         }
628     }
629 }
630
631
632
633 # else
634
635 #ifndef LINT
636   int GC_no_Irix_threads;
637 #endif
638
639 # endif /* IRIX_THREADS */
640