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
24 # if defined(IRIX_THREADS)
31 # include <sys/mman.h>
32 # include <sys/time.h>
35 #undef pthread_sigmask
41 void GC_print_sig_mask()
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
); }
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 */
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.) */
73 # define NOT_STOPPED 0
74 # define PLEASE_STOP 1
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. */
82 ptr_t stack_ptr
; /* Valid only when stopped. */
83 /* But must be within stack region at */
85 size_t stack_size
; /* 0 for original thread. */
86 void * status
; /* Used only to avoid premature */
87 /* reclamation of any data it might */
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
)
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
);
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
;
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
) {
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
++;
166 GC_stack_free_lists
[index
] = *(ptr_t
*)result
;
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
;
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
) {
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
;
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. */
212 result
= (struct GC_Thread_Rep
*)
213 GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep
), NORMAL
);
215 if (result
== 0) return(0);
217 result
-> next
= GC_threads
[hv
];
218 GC_threads
[hv
] = result
;
219 /* result -> flags = 0; */
220 /* result -> stop = 0; */
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
)) {
238 GC_threads
[hv
] = p
-> next
;
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;
259 GC_threads
[hv
] = p
-> next
;
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 */
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
;
281 /* Caller holds allocation lock. */
284 pthread_t my_thread
= pthread_self();
286 register GC_thread p
;
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
) {
297 p
-> stop
= PLEASE_STOP
;
298 result
= pthread_kill(p
-> id
, SIG_SUSPEND
);
299 /* GC_printf1("Sent signal to 0x%x\n", p -> id); */
302 /* Not really there anymore. Possible? */
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;
323 result
= pthread_cond_timedwait(&GC_suspend_ack_cv
,
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()
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
);
358 --> not really supported yet
.
359 int GC_is_thread_stack(ptr_t addr
)
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
)
377 /* We hold allocation lock. We assume the world is stopped. */
378 void GC_push_all_stacks()
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
)) {
396 if (p
-> stack_size
!= 0) {
397 hi
= p
-> stack
+ p
-> stack_size
;
399 /* The original stack. */
402 GC_push_all_stack(lo
, hi
);
408 /* We hold the allocation lock. */
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());
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
)
437 if (set
!= NULL
&& (how
== SIG_BLOCK
|| how
== SIG_SETMASK
)) {
439 sigdelset(&fudged_set
, SIG_SUSPEND
);
442 return(pthread_sigmask(how
, set
, oset
));
446 void *(*start_routine
)(void *);
450 void GC_thread_exit_proc(void *dummy
)
455 me
= GC_lookup_thread(pthread_self());
456 if (me
-> flags
& DETACHED
) {
457 GC_delete_thread(pthread_self());
459 me
-> flags
|= FINISHED
;
464 int GC_pthread_join(pthread_t thread
, void **retval
)
467 GC_thread thread_gc_id
;
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. */
474 result
= pthread_join(thread
, retval
);
476 /* Here the pthread thread id may have been recycled. */
477 GC_delete_gc_thread(thread
, thread_gc_id
);
482 void * GC_start_routine(void * arg
)
484 struct start_info
* si
= arg
;
489 me
= GC_lookup_thread(pthread_self());
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 */
503 GC_pthread_create(pthread_t
*new_thread
,
504 const pthread_attr_t
*attr
,
505 void *(*start_routine
)(void *), void *arg
)
509 pthread_t my_new_thread
;
512 pthread_attr_t new_attr
;
515 struct start_info
* si
= GC_malloc(sizeof(struct start_info
));
517 if (0 == si
) return(ENOMEM
);
518 si
-> start_routine
= start_routine
;
521 if (!GC_thr_initialized
) GC_thr_init();
524 (void) pthread_attr_init(&new_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");
533 stack
= (void *)GC_stack_alloc(&stacksize
);
538 pthread_attr_setstackaddr(&new_attr
, stack
);
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. */
547 t
= GC_new_thread(my_new_thread
);
548 t
-> flags
= my_flags
;
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
);
557 /* pthread_attr_destroy(&new_attr); */
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
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
585 if (!GC_test_and_set(&GC_allocate_lock
, 1)) {
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
) {
597 if (!GC_test_and_set(&GC_allocate_lock
, 1)) {
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.
605 spin_max
= high_spin_max
;
609 /* We are probably being scheduled against the other process. Sleep. */
610 spin_max
= low_spin_max
;
613 if (!GC_test_and_set(&GC_allocate_lock
, 1)) {
616 if (i
< SLEEP_THRESHOLD
) {
622 /* Don't wait for more than about 60msecs, even */
623 /* under extreme contention. */
636 int GC_no_Irix_threads
;
639 # endif /* IRIX_THREADS */