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