OSDN Git Service

Merged GC 5.0alpha4 with local changes, plus:
[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 <semaphore.h>
29 # include <time.h>
30 # include <errno.h>
31 # include <unistd.h>
32 # include <sys/mman.h>
33 # include <sys/time.h>
34
35 #undef pthread_create
36 #undef pthread_sigmask
37 #undef pthread_join
38
39 void GC_thr_init();
40
41 #if 0
42 void GC_print_sig_mask()
43 {
44     sigset_t blocked;
45     int i;
46
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); }
52     }
53     GC_printf0("\n");
54 }
55 #endif
56
57 /* We use the allocation lock to protect thread-related data structures. */
58
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;
91
92 GC_thread GC_lookup_thread(pthread_t id);
93
94 /*
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.
98  */
99 # define SIG_SUSPEND (SIGRTMIN + 6)
100
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;
105
106 void GC_suspend_handler(int sig)
107 {
108     int dummy;
109     GC_thread me;
110     sigset_t all_sigs;
111     sigset_t old_sigs;
112     int i;
113
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;
124     }
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()); */
132 }
133
134
135 GC_bool GC_thr_initialized = FALSE;
136
137 size_t GC_min_stack_sz;
138
139 size_t GC_page_sz;
140
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.            */
146
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)
151 {
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;
156     
157     while (search_sz < requested_sz) {
158         search_sz *= 2;
159         index++;
160     }
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++;
165     }
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;
174     }
175     *stack_size = search_sz;
176     return(result);
177 }
178
179 /* Caller holds allocation lock.                                        */
180 void GC_stack_free(ptr_t stack, size_t size)
181 {
182     register int index = 0;
183     register size_t search_sz = GC_min_stack_sz;
184     
185     while (search_sz < size) {
186         search_sz *= 2;
187         index++;
188     }
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;
192 }
193
194
195
196 # define THREAD_TABLE_SZ 128    /* Must be power of 2   */
197 volatile GC_thread GC_threads[THREAD_TABLE_SZ];
198
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)
202 {
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;
207     
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);
215     }
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);
223 }
224
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)
229 {
230     int hv = ((word)id) % THREAD_TABLE_SZ;
231     register GC_thread p = GC_threads[hv];
232     register GC_thread prev = 0;
233     
234     while (!pthread_equal(p -> id, id)) {
235         prev = p;
236         p = p -> next;
237     }
238     if (prev == 0) {
239         GC_threads[hv] = p -> next;
240     } else {
241         prev -> next = p -> next;
242     }
243 }
244
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)
250 {
251     int hv = ((word)id) % THREAD_TABLE_SZ;
252     register GC_thread p = GC_threads[hv];
253     register GC_thread prev = 0;
254
255     while (p != gc_id) {
256         prev = p;
257         p = p -> next;
258     }
259     if (prev == 0) {
260         GC_threads[hv] = p -> next;
261     } else {
262         prev -> next = p -> next;
263     }
264 }
265
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)
273 {
274     int hv = ((word)id) % THREAD_TABLE_SZ;
275     register GC_thread p = GC_threads[hv];
276     
277     while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
278     return(p);
279 }
280
281
282 /* Caller holds allocation lock.        */
283 void GC_stop_world()
284 {
285     pthread_t my_thread = pthread_self();
286     register int i;
287     register GC_thread p;
288     register int result;
289     struct timespec timeout;
290     
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;
297             }
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");
310             }
311         }
312       }
313     }
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;
323             }
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);
331             }
332         }
333       }
334     }
335     pthread_mutex_unlock(&GC_suspend_lock);
336     /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
337 }
338
339 /* Caller holds allocation lock.        */
340 void GC_start_world()
341 {
342     GC_thread p;
343     unsigned i;
344
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;
349       }
350     }
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);
356 }
357
358 # ifdef MMAP_STACKS
359 --> not really supported yet.
360 int GC_is_thread_stack(ptr_t addr)
361 {
362     register int i;
363     register GC_thread p;
364
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;
371        }
372       }
373     }
374     return 0;
375 }
376 # endif
377
378 /* We hold allocation lock.  We assume the world is stopped.    */
379 void GC_push_all_stacks()
380 {
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();
386     
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;
396         }
397         if (p -> stack_size != 0) {
398             hi = p -> stack + p -> stack_size;
399         } else {
400             /* The original stack. */
401             hi = GC_stackbottom;
402         }
403         GC_push_all_stack(lo, hi);
404       }
405     }
406 }
407
408
409 /* We hold the allocation lock. */
410 void GC_thr_init()
411 {
412     GC_thread t;
413     struct sigaction act;
414
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;
433 }
434
435 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
436 {
437     sigset_t fudged_set;
438     
439     if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
440         fudged_set = *set;
441         sigdelset(&fudged_set, SIG_SUSPEND);
442         set = &fudged_set;
443     }
444     return(pthread_sigmask(how, set, oset));
445 }
446
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.           */
455 };
456
457 void GC_thread_exit_proc(void *arg)
458 {
459     GC_thread me;
460
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;
467     }
468     UNLOCK();
469 }
470
471 int GC_pthread_join(pthread_t thread, void **retval)
472 {
473     int result;
474     GC_thread thread_gc_id;
475     
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;
490 }
491
492 void * GC_start_routine(void * arg)
493 {
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;
500
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);
532 }
533
534 int
535 GC_pthread_create(pthread_t *new_thread,
536                   const pthread_attr_t *attr,
537                   void *(*start_routine)(void *), void *arg)
538 {
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.                */
549
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);
562     }
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);
571         }
572         pthread_attr_setstackaddr(&new_attr, stack);
573     } else {
574         my_flags |= CLIENT_OWNS_STACK;
575     }
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);
583     }        
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);
593 }
594
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.                             */
598
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 ...  */
602
603 unsigned long GC_allocate_lock = 0;
604
605 #define SLEEP_THRESHOLD 3
606
607 void GC_lock()
608 {
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;
618
619     if (!GC_test_and_set(&GC_allocate_lock, 1)) {
620         return;
621     }
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;
630         }
631         if (!GC_test_and_set(&GC_allocate_lock, 1)) {
632             /*
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.
637              */
638             last_spins = i;
639             spin_max = high_spin_max;
640             return;
641         }
642     }
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;
649         }
650         if (i < SLEEP_THRESHOLD) {
651             sched_yield();
652         } else {
653             struct timespec ts;
654         
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);
661         }
662     }
663 }
664
665
666
667 # else
668
669 #ifndef LINT
670   int GC_no_Irix_threads;
671 #endif
672
673 # endif /* IRIX_THREADS */
674