2 * Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved.
3 * Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved.
4 * Copyright (c) 1999 by Hewlett-Packard Company. All rights reserved.
6 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
7 * OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
9 * Permission is hereby granted to use or copy this program
10 * for any purpose, provided the above notices are retained on all copies.
11 * Permission to modify the code and to distribute modified code is granted,
12 * provided the above notices are retained, and a notice that the code was
13 * modified is included with the above copyright notice.
16 * Support code for Irix (>=6.2) Pthreads. This relies on properties
17 * not guaranteed by the Pthread standard. It may or may not be portable
18 * to other implementations.
20 * This now also includes an initial attempt at thread support for
23 * Note that there is a lot of code duplication between linux_threads.c
24 * and hpux_irix_threads.c; any changes made here may need to be reflected
28 # if defined(IRIX_THREADS) || defined(HPUX_THREADS)
30 # if defined(HPUX_THREADS)
31 # include <sys/semaphore.h>
36 # include <semaphore.h>
40 # include <sys/mman.h>
41 # include <sys/time.h>
44 #undef pthread_sigmask
50 void GC_print_sig_mask()
55 if (pthread_sigmask(SIG_BLOCK
, NULL
, &blocked
) != 0)
56 ABORT("pthread_sigmask");
57 GC_printf0("Blocked: ");
58 for (i
= 1; i
<= MAXSIG
; i
++) {
59 if (sigismember(&blocked
, i
)) { GC_printf1("%ld ",(long) i
); }
65 /* We use the allocation lock to protect thread-related data structures. */
67 /* The set of all known threads. We intercept thread creation and */
68 /* joins. We never actually create detached threads. We allocate all */
69 /* new thread stacks ourselves. These allow us to maintain this */
71 /* Protected by GC_thr_lock. */
72 /* Some of this should be declared volatile, but that's incosnsistent */
73 /* with some library routine declarations. */
74 typedef struct GC_Thread_Rep
{
75 struct GC_Thread_Rep
* next
; /* More recently allocated threads */
76 /* with a given pthread id come */
77 /* first. (All but the first are */
78 /* guaranteed to be dead, but we may */
79 /* not yet have registered the join.) */
82 # define NOT_STOPPED 0
83 # define PLEASE_STOP 1
86 # define FINISHED 1 /* Thread has exited. */
87 # define DETACHED 2 /* Thread is intended to be detached. */
88 # define CLIENT_OWNS_STACK 4
89 /* Stack was supplied by client. */
91 ptr_t stack_ptr
; /* Valid only when stopped. */
92 /* But must be within stack region at */
94 size_t stack_size
; /* 0 for original thread. */
95 void * status
; /* Used only to avoid premature */
96 /* reclamation of any data it might */
100 GC_thread
GC_lookup_thread(pthread_t id
);
103 * The only way to suspend threads given the pthread interface is to send
104 * signals. Unfortunately, this means we have to reserve
105 * a signal, and intercept client calls to change the signal mask.
107 # define SIG_SUSPEND (SIGRTMIN + 6)
109 pthread_mutex_t GC_suspend_lock
= PTHREAD_MUTEX_INITIALIZER
;
110 /* Number of threads stopped so far */
111 pthread_cond_t GC_suspend_ack_cv
= PTHREAD_COND_INITIALIZER
;
112 pthread_cond_t GC_continue_cv
= PTHREAD_COND_INITIALIZER
;
114 void GC_suspend_handler(int sig
)
122 if (sig
!= SIG_SUSPEND
) ABORT("Bad signal in suspend_handler");
123 me
= GC_lookup_thread(pthread_self());
124 /* The lookup here is safe, since I'm doing this on behalf */
125 /* of a thread which holds the allocation lock in order */
126 /* to stop the world. Thus concurrent modification of the */
127 /* data structure is impossible. */
128 if (PLEASE_STOP
!= me
-> stop
) {
129 /* Misdirected signal. */
130 pthread_mutex_unlock(&GC_suspend_lock
);
133 pthread_mutex_lock(&GC_suspend_lock
);
134 me
-> stack_ptr
= (ptr_t
)(&dummy
);
135 me
-> stop
= STOPPED
;
136 pthread_cond_signal(&GC_suspend_ack_cv
);
137 pthread_cond_wait(&GC_continue_cv
, &GC_suspend_lock
);
138 pthread_mutex_unlock(&GC_suspend_lock
);
139 /* GC_printf1("Continuing 0x%x\n", pthread_self()); */
143 GC_bool GC_thr_initialized
= FALSE
;
145 size_t GC_min_stack_sz
;
149 # define N_FREE_LISTS 25
150 ptr_t GC_stack_free_lists
[N_FREE_LISTS
] = { 0 };
151 /* GC_stack_free_lists[i] is free list for stacks of */
152 /* size GC_min_stack_sz*2**i. */
153 /* Free lists are linked through first word. */
155 /* Return a stack of size at least *stack_size. *stack_size is */
156 /* replaced by the actual stack size. */
157 /* Caller holds allocation lock. */
158 ptr_t
GC_stack_alloc(size_t * stack_size
)
160 register size_t requested_sz
= *stack_size
;
161 register size_t search_sz
= GC_min_stack_sz
;
162 register int index
= 0; /* = log2(search_sz/GC_min_stack_sz) */
163 register ptr_t result
;
165 while (search_sz
< requested_sz
) {
169 if ((result
= GC_stack_free_lists
[index
]) == 0
170 && (result
= GC_stack_free_lists
[index
+1]) != 0) {
171 /* Try next size up. */
172 search_sz
*= 2; index
++;
175 GC_stack_free_lists
[index
] = *(ptr_t
*)result
;
177 result
= (ptr_t
) GC_scratch_alloc(search_sz
+ 2*GC_page_sz
);
178 result
= (ptr_t
)(((word
)result
+ GC_page_sz
) & ~(GC_page_sz
- 1));
179 /* Protect hottest page to detect overflow. */
180 # ifdef STACK_GROWS_UP
181 /* mprotect(result + search_sz, GC_page_sz, PROT_NONE); */
183 /* mprotect(result, GC_page_sz, PROT_NONE); */
184 result
+= GC_page_sz
;
187 *stack_size
= search_sz
;
191 /* Caller holds allocation lock. */
192 void GC_stack_free(ptr_t stack
, size_t size
)
194 register int index
= 0;
195 register size_t search_sz
= GC_min_stack_sz
;
197 while (search_sz
< size
) {
201 if (search_sz
!= size
) ABORT("Bad stack size");
202 *(ptr_t
*)stack
= GC_stack_free_lists
[index
];
203 GC_stack_free_lists
[index
] = stack
;
208 # define THREAD_TABLE_SZ 128 /* Must be power of 2 */
209 volatile GC_thread GC_threads
[THREAD_TABLE_SZ
];
211 /* Add a thread to GC_threads. We assume it wasn't already there. */
212 /* Caller holds allocation lock. */
213 GC_thread
GC_new_thread(pthread_t id
)
215 int hv
= ((word
)id
) % THREAD_TABLE_SZ
;
217 static struct GC_Thread_Rep first_thread
;
218 static GC_bool first_thread_used
= FALSE
;
220 if (!first_thread_used
) {
221 result
= &first_thread
;
222 first_thread_used
= TRUE
;
223 /* Dont acquire allocation lock, since we may already hold it. */
225 result
= (struct GC_Thread_Rep
*)
226 GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep
), NORMAL
);
228 if (result
== 0) return(0);
230 result
-> next
= GC_threads
[hv
];
231 GC_threads
[hv
] = result
;
232 /* result -> flags = 0; */
233 /* result -> stop = 0; */
237 /* Delete a thread from GC_threads. We assume it is there. */
238 /* (The code intentionally traps if it wasn't.) */
239 /* Caller holds allocation lock. */
240 void GC_delete_thread(pthread_t id
)
242 int hv
= ((word
)id
) % THREAD_TABLE_SZ
;
243 register GC_thread p
= GC_threads
[hv
];
244 register GC_thread prev
= 0;
246 while (!pthread_equal(p
-> id
, id
)) {
251 GC_threads
[hv
] = p
-> next
;
253 prev
-> next
= p
-> next
;
257 /* If a thread has been joined, but we have not yet */
258 /* been notified, then there may be more than one thread */
259 /* in the table with the same pthread id. */
260 /* This is OK, but we need a way to delete a specific one. */
261 void GC_delete_gc_thread(pthread_t id
, GC_thread gc_id
)
263 int hv
= ((word
)id
) % THREAD_TABLE_SZ
;
264 register GC_thread p
= GC_threads
[hv
];
265 register GC_thread prev
= 0;
272 GC_threads
[hv
] = p
-> next
;
274 prev
-> next
= p
-> next
;
278 /* Return a GC_thread corresponding to a given thread_t. */
279 /* Returns 0 if it's not there. */
280 /* Caller holds allocation lock or otherwise inhibits */
282 /* If there is more than one thread with the given id we */
283 /* return the most recent one. */
284 GC_thread
GC_lookup_thread(pthread_t id
)
286 int hv
= ((word
)id
) % THREAD_TABLE_SZ
;
287 register GC_thread p
= GC_threads
[hv
];
289 while (p
!= 0 && !pthread_equal(p
-> id
, id
)) p
= p
-> next
;
294 /* Caller holds allocation lock. */
297 pthread_t my_thread
= pthread_self();
299 register GC_thread p
;
301 struct timespec timeout
;
303 for (i
= 0; i
< THREAD_TABLE_SZ
; i
++) {
304 for (p
= GC_threads
[i
]; p
!= 0; p
= p
-> next
) {
305 if (p
-> id
!= my_thread
) {
306 if (p
-> flags
& FINISHED
) {
310 p
-> stop
= PLEASE_STOP
;
311 result
= pthread_kill(p
-> id
, SIG_SUSPEND
);
312 /* GC_printf1("Sent signal to 0x%x\n", p -> id); */
315 /* Not really there anymore. Possible? */
321 ABORT("pthread_kill failed");
326 pthread_mutex_lock(&GC_suspend_lock
);
327 for (i
= 0; i
< THREAD_TABLE_SZ
; i
++) {
328 for (p
= GC_threads
[i
]; p
!= 0; p
= p
-> next
) {
329 while (p
-> id
!= my_thread
&& p
-> stop
!= STOPPED
) {
330 clock_gettime(CLOCK_REALTIME
, &timeout
);
331 timeout
.tv_nsec
+= 50000000; /* 50 msecs */
332 if (timeout
.tv_nsec
>= 1000000000) {
333 timeout
.tv_nsec
-= 1000000000;
336 result
= pthread_cond_timedwait(&GC_suspend_ack_cv
,
339 if (result
== ETIMEDOUT
) {
340 /* Signal was lost or misdirected. Try again. */
341 /* Duplicate signals should be benign. */
342 result
= pthread_kill(p
-> id
, SIG_SUSPEND
);
347 pthread_mutex_unlock(&GC_suspend_lock
);
348 /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
351 /* Caller holds allocation lock. */
352 void GC_start_world()
357 /* GC_printf0("World starting\n"); */
358 for (i
= 0; i
< THREAD_TABLE_SZ
; i
++) {
359 for (p
= GC_threads
[i
]; p
!= 0; p
= p
-> next
) {
360 p
-> stop
= NOT_STOPPED
;
363 pthread_mutex_lock(&GC_suspend_lock
);
364 /* All other threads are at pthread_cond_wait in signal handler. */
365 /* Otherwise we couldn't have acquired the lock. */
366 pthread_mutex_unlock(&GC_suspend_lock
);
367 pthread_cond_broadcast(&GC_continue_cv
);
371 --> not really supported yet
.
372 int GC_is_thread_stack(ptr_t addr
)
375 register GC_thread p
;
377 for (i
= 0; i
< THREAD_TABLE_SZ
; i
++) {
378 for (p
= GC_threads
[i
]; p
!= 0; p
= p
-> next
) {
379 if (p
-> stack_size
!= 0) {
380 if (p
-> stack
<= addr
&&
381 addr
< p
-> stack
+ p
-> stack_size
)
390 /* We hold allocation lock. We assume the world is stopped. */
391 void GC_push_all_stacks()
394 register GC_thread p
;
395 register ptr_t sp
= GC_approx_sp();
396 register ptr_t hot
, cold
;
397 pthread_t me
= pthread_self();
399 if (!GC_thr_initialized
) GC_thr_init();
400 /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
401 for (i
= 0; i
< THREAD_TABLE_SZ
; i
++) {
402 for (p
= GC_threads
[i
]; p
!= 0; p
= p
-> next
) {
403 if (p
-> flags
& FINISHED
) continue;
404 if (pthread_equal(p
-> id
, me
)) {
405 hot
= GC_approx_sp();
407 hot
= p
-> stack_ptr
;
409 if (p
-> stack_size
!= 0) {
410 # ifdef STACK_GROWS_UP
413 cold
= p
-> stack
+ p
-> stack_size
;
416 /* The original stack. */
417 cold
= GC_stackbottom
;
419 # ifdef STACK_GROWS_UP
420 GC_push_all_stack(cold
, hot
);
422 GC_push_all_stack(hot
, cold
);
429 /* We hold the allocation lock. */
433 struct sigaction act
;
435 if (GC_thr_initialized
) return;
436 GC_thr_initialized
= TRUE
;
437 GC_min_stack_sz
= HBLKSIZE
;
438 GC_page_sz
= sysconf(_SC_PAGESIZE
);
439 (void) sigaction(SIG_SUSPEND
, 0, &act
);
440 if (act
.sa_handler
!= SIG_DFL
)
441 ABORT("Previously installed SIG_SUSPEND handler");
442 /* Install handler. */
443 act
.sa_handler
= GC_suspend_handler
;
444 act
.sa_flags
= SA_RESTART
;
445 (void) sigemptyset(&act
.sa_mask
);
446 if (0 != sigaction(SIG_SUSPEND
, &act
, 0))
447 ABORT("Failed to install SIG_SUSPEND handler");
448 /* Add the initial thread, so we can stop it. */
449 t
= GC_new_thread(pthread_self());
451 t
-> stack_ptr
= (ptr_t
)(&t
);
452 t
-> flags
= DETACHED
;
455 int GC_pthread_sigmask(int how
, const sigset_t
*set
, sigset_t
*oset
)
459 if (set
!= NULL
&& (how
== SIG_BLOCK
|| how
== SIG_SETMASK
)) {
461 sigdelset(&fudged_set
, SIG_SUSPEND
);
464 return(pthread_sigmask(how
, set
, oset
));
468 void *(*start_routine
)(void *);
473 sem_t registered
; /* 1 ==> in our thread table, but */
474 /* parent hasn't yet noticed. */
477 void GC_thread_exit_proc(void *arg
)
482 me
= GC_lookup_thread(pthread_self());
483 if (me
-> flags
& DETACHED
) {
484 GC_delete_thread(pthread_self());
486 me
-> flags
|= FINISHED
;
491 int GC_pthread_join(pthread_t thread
, void **retval
)
494 GC_thread thread_gc_id
;
497 thread_gc_id
= GC_lookup_thread(thread
);
498 /* This is guaranteed to be the intended one, since the thread id */
499 /* cant have been recycled by pthreads. */
501 result
= pthread_join(thread
, retval
);
502 /* Some versions of the Irix pthreads library can erroneously */
503 /* return EINTR when the call succeeds. */
504 if (EINTR
== result
) result
= 0;
506 /* Here the pthread thread id may have been recycled. */
507 GC_delete_gc_thread(thread
, thread_gc_id
);
512 void * GC_start_routine(void * arg
)
514 struct start_info
* si
= arg
;
517 pthread_t my_pthread
;
518 void *(*start
)(void *);
521 my_pthread
= pthread_self();
522 /* If a GC occurs before the thread is registered, that GC will */
523 /* ignore this thread. That's fine, since it will block trying to */
524 /* acquire the allocation lock, and won't yet hold interesting */
527 /* We register the thread here instead of in the parent, so that */
528 /* we don't need to hold the allocation lock during pthread_create. */
529 /* Holding the allocation lock there would make REDIRECT_MALLOC */
530 /* impossible. It probably still doesn't work, but we're a little */
532 /* This unfortunately means that we have to be careful the parent */
533 /* doesn't try to do a pthread_join before we're registered. */
534 me
= GC_new_thread(my_pthread
);
535 me
-> flags
= si
-> flags
;
536 me
-> stack
= si
-> stack
;
537 me
-> stack_size
= si
-> stack_size
;
538 me
-> stack_ptr
= (ptr_t
)si
-> stack
+ si
-> stack_size
- sizeof(word
);
540 start
= si
-> start_routine
;
541 start_arg
= si
-> arg
;
542 sem_post(&(si
-> registered
));
543 pthread_cleanup_push(GC_thread_exit_proc
, 0);
544 result
= (*start
)(start_arg
);
545 me
-> status
= result
;
546 me
-> flags
|= FINISHED
;
547 pthread_cleanup_pop(1);
548 /* This involves acquiring the lock, ensuring that we can't exit */
549 /* while a collection that thinks we're alive is trying to stop */
555 /* pthread_attr_t is not a structure, thus a simple structure copy */
557 static void copy_attr(pthread_attr_t
* pa_ptr
,
558 const pthread_attr_t
* source
) {
562 struct sched_param sp_tmp
;
563 pthread_spu_t ps_tmp
;
564 (void) pthread_attr_init(pa_ptr
);
565 (void) pthread_attr_getdetachstate(source
, &tmp
);
566 (void) pthread_attr_setdetachstate(pa_ptr
, tmp
);
567 (void) pthread_attr_getinheritsched(source
, &tmp
);
568 (void) pthread_attr_setinheritsched(pa_ptr
, tmp
);
569 (void) pthread_attr_getschedpolicy(source
, &tmp
);
570 (void) pthread_attr_setschedpolicy(pa_ptr
, tmp
);
571 (void) pthread_attr_getstacksize(source
, &stmp
);
572 (void) pthread_attr_setstacksize(pa_ptr
, stmp
);
573 (void) pthread_attr_getguardsize(source
, &stmp
);
574 (void) pthread_attr_setguardsize(pa_ptr
, stmp
);
575 (void) pthread_attr_getstackaddr(source
, &vtmp
);
576 (void) pthread_attr_setstackaddr(pa_ptr
, vtmp
);
577 (void) pthread_attr_getscope(source
, &tmp
);
578 (void) pthread_attr_setscope(pa_ptr
, tmp
);
579 (void) pthread_attr_getschedparam(source
, &sp_tmp
);
580 (void) pthread_attr_setschedparam(pa_ptr
, &sp_tmp
);
581 (void) pthread_attr_getprocessor_np(source
, &ps_tmp
, &tmp
);
582 (void) pthread_attr_setprocessor_np(pa_ptr
, ps_tmp
, tmp
);
585 # define copy_attr(pa_ptr, source) *(pa_ptr) = *(source)
589 GC_pthread_create(pthread_t
*new_thread
,
590 const pthread_attr_t
*attr
,
591 void *(*start_routine
)(void *), void *arg
)
597 pthread_attr_t new_attr
;
600 struct start_info
* si
= GC_malloc(sizeof(struct start_info
));
601 /* This is otherwise saved only in an area mmapped by the thread */
602 /* library, which isn't visible to the collector. */
604 if (0 == si
) return(ENOMEM
);
605 if (0 != sem_init(&(si
-> registered
), 0, 0)) {
606 ABORT("sem_init failed");
608 si
-> start_routine
= start_routine
;
611 if (!GC_thr_initialized
) GC_thr_init();
614 (void) pthread_attr_init(&new_attr
);
616 copy_attr(&new_attr
, attr
);
617 pthread_attr_getstackaddr(&new_attr
, &stack
);
619 pthread_attr_getstacksize(&new_attr
, &stacksize
);
620 pthread_attr_getdetachstate(&new_attr
, &detachstate
);
621 if (stacksize
< GC_min_stack_sz
) ABORT("Stack too small");
623 stack
= (void *)GC_stack_alloc(&stacksize
);
628 pthread_attr_setstackaddr(&new_attr
, stack
);
630 my_flags
|= CLIENT_OWNS_STACK
;
632 if (PTHREAD_CREATE_DETACHED
== detachstate
) my_flags
|= DETACHED
;
633 si
-> flags
= my_flags
;
635 si
-> stack_size
= stacksize
;
636 result
= pthread_create(new_thread
, &new_attr
, GC_start_routine
, si
);
637 if (0 == new_thread
&& !(my_flags
& CLIENT_OWNS_STACK
)) {
638 GC_stack_free(stack
, stacksize
);
641 /* Wait until child has been added to the thread table. */
642 /* This also ensures that we hold onto si until the child is done */
643 /* with it. Thus it doesn't matter whether it is otherwise */
644 /* visible to the collector. */
645 while (0 != sem_wait(&(si
-> registered
))) {
646 if (errno
!= EINTR
) {
647 GC_printf1("Sem_wait: errno = %ld\n", (unsigned long) errno
);
648 ABORT("sem_wait failed");
651 sem_destroy(&(si
-> registered
));
652 pthread_attr_destroy(&new_attr
); /* Not a no-op under HPUX */
657 /* For now we use the pthreads locking primitives on HP/UX */
659 GC_bool GC_collecting
= 0; /* A hint that we're in the collector and */
660 /* holding the allocation lock for an */
661 /* extended period. */
663 /* Reasonably fast spin locks. Basically the same implementation */
664 /* as STL alloc.h. */
666 #define SLEEP_THRESHOLD 3
669 unsigned long GC_allocate_lock
= 1;
670 # define GC_TRY_LOCK() GC_test_and_clear(&GC_allocate_lock)
671 # define GC_LOCK_TAKEN !GC_allocate_lock
673 unsigned long GC_allocate_lock
= 0;
674 # define GC_TRY_LOCK() !GC_test_and_set(&GC_allocate_lock,1)
675 # define GC_LOCK_TAKEN GC_allocate_lock
680 # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */
681 # define high_spin_max 1000 /* spin cycles for multiprocessor */
682 static unsigned spin_max
= low_spin_max
;
683 unsigned my_spin_max
;
684 static unsigned last_spins
= 0;
685 unsigned my_last_spins
;
686 volatile unsigned junk
;
687 # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
694 my_spin_max
= spin_max
;
695 my_last_spins
= last_spins
;
696 for (i
= 0; i
< my_spin_max
; i
++) {
697 if (GC_collecting
) goto yield
;
698 if (i
< my_last_spins
/2 || GC_LOCK_TAKEN
) {
705 * Spinning worked. Thus we're probably not being scheduled
706 * against the other process with which we were contending.
707 * Thus it makes sense to spin longer the next time.
710 spin_max
= high_spin_max
;
714 /* We are probably being scheduled against the other process. Sleep. */
715 spin_max
= low_spin_max
;
721 if (i
< SLEEP_THRESHOLD
) {
727 /* Don't wait for more than about 60msecs, even */
728 /* under extreme contention. */
736 #endif /* !HPUX_THREADS */
741 int GC_no_Irix_threads
;
744 # endif /* IRIX_THREADS */