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