OSDN Git Service

2004-11-24 Kelley Cook <kcook@gcc.gnu.org>
[pf3gnuchains/gcc-fork.git] / boehm-gc / aix_irix_threads.c
1 /* 
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-2003 by Hewlett-Packard Company. All rights reserved.
5  *
6  * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
7  * OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
8  *
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.
14  */
15 /*
16  * Support code for Irix (>=6.2) Pthreads and for AIX pthreads.
17  * This relies on properties
18  * not guaranteed by the Pthread standard.  It may or may not be portable
19  * to other implementations.
20  *
21  * Note that there is a lot of code duplication between this file and
22  * (pthread_support.c, pthread_stop_world.c).  They should be merged.
23  * Pthread_support.c should be directly usable.
24  *
25  * Please avoid adding new ports here; use the generic pthread support
26  * as a base instead.
27  */
28
29 # include "private/gc_priv.h"
30
31 # if defined(GC_IRIX_THREADS) || defined(GC_AIX_THREADS)
32
33 # include <pthread.h>
34 # include <assert.h>
35 # include <semaphore.h>
36 # include <time.h>
37 # include <errno.h>
38 # include <unistd.h>
39 # include <sys/mman.h>
40 # include <sys/time.h>
41
42 #undef pthread_create
43 #undef pthread_sigmask
44 #undef pthread_join
45
46 #if defined(GC_IRIX_THREADS) && !defined(MUTEX_RECURSIVE_NP)
47 #define MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE
48 #endif
49
50 void GC_thr_init();
51
52 #if 0
53 void GC_print_sig_mask()
54 {
55     sigset_t blocked;
56     int i;
57
58     if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
59         ABORT("pthread_sigmask");
60     GC_printf0("Blocked: ");
61     for (i = 1; i <= MAXSIG; i++) {
62         if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
63     }
64     GC_printf0("\n");
65 }
66 #endif
67
68 /* We use the allocation lock to protect thread-related data structures. */
69
70 /* The set of all known threads.  We intercept thread creation and      */
71 /* joins.  We never actually create detached threads.  We allocate all  */
72 /* new thread stacks ourselves.  These allow us to maintain this        */
73 /* data structure.                                                      */
74 /* Protected by GC_thr_lock.                                            */
75 /* Some of this should be declared volatile, but that's incosnsistent   */
76 /* with some library routine declarations.                              */
77 typedef struct GC_Thread_Rep {
78     struct GC_Thread_Rep * next;  /* More recently allocated threads    */
79                                   /* with a given pthread id come       */
80                                   /* first.  (All but the first are     */
81                                   /* guaranteed to be dead, but we may  */
82                                   /* not yet have registered the join.) */
83     pthread_t id;
84     word stop;
85 #       define NOT_STOPPED 0
86 #       define PLEASE_STOP 1
87 #       define STOPPED 2
88     word flags;
89 #       define FINISHED 1       /* Thread has exited.   */
90 #       define DETACHED 2       /* Thread is intended to be detached.   */
91     ptr_t stack_cold;           /* cold end of the stack                */
92     ptr_t stack_hot;            /* Valid only when stopped. */
93                                 /* But must be within stack region at   */
94                                 /* all times.                           */
95     void * status;              /* Used only to avoid premature         */
96                                 /* reclamation of any data it might     */
97                                 /* reference.                           */
98 } * GC_thread;
99
100 GC_thread GC_lookup_thread(pthread_t id);
101
102 /*
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.
106  */
107 #if 0 /* DOB: 6.1 */
108 # if defined(GC_AIX_THREADS)
109 #   define SIG_SUSPEND SIGUSR1
110 # else
111 #   define SIG_SUSPEND (SIGRTMIN + 6)
112 # endif
113 #endif
114
115 pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER;
116                                 /* Number of threads stopped so far     */
117 pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER;
118 pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER;
119
120 void GC_suspend_handler(int sig)
121 {
122     int dummy;
123     GC_thread me;
124     sigset_t all_sigs;
125     sigset_t old_sigs;
126     int i;
127
128     if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
129     me = GC_lookup_thread(pthread_self());
130     /* The lookup here is safe, since I'm doing this on behalf  */
131     /* of a thread which holds the allocation lock in order     */
132     /* to stop the world.  Thus concurrent modification of the  */
133     /* data structure is impossible.                            */
134     if (PLEASE_STOP != me -> stop) {
135         /* Misdirected signal.  */
136         pthread_mutex_unlock(&GC_suspend_lock);
137         return;
138     }
139     pthread_mutex_lock(&GC_suspend_lock);
140     me -> stack_hot = (ptr_t)(&dummy);
141     me -> stop = STOPPED;
142     pthread_cond_signal(&GC_suspend_ack_cv);
143     pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock);
144     pthread_mutex_unlock(&GC_suspend_lock);
145     /* GC_printf1("Continuing 0x%x\n", pthread_self()); */
146 }
147
148
149 GC_bool GC_thr_initialized = FALSE;
150
151
152 # define THREAD_TABLE_SZ 128    /* Must be power of 2   */
153 volatile GC_thread GC_threads[THREAD_TABLE_SZ];
154
155 void GC_push_thread_structures GC_PROTO((void))
156 {
157     GC_push_all((ptr_t)(GC_threads), (ptr_t)(GC_threads)+sizeof(GC_threads));
158 }
159
160 /* Add a thread to GC_threads.  We assume it wasn't already there.      */
161 /* Caller holds allocation lock.                                        */
162 GC_thread GC_new_thread(pthread_t id)
163 {
164     int hv = ((word)id) % THREAD_TABLE_SZ;
165     GC_thread result;
166     static struct GC_Thread_Rep first_thread;
167     static GC_bool first_thread_used = FALSE;
168     
169     GC_ASSERT(I_HOLD_LOCK());
170     if (!first_thread_used) {
171         result = &first_thread;
172         first_thread_used = TRUE;
173         /* Dont acquire allocation lock, since we may already hold it. */
174     } else {
175         result = (struct GC_Thread_Rep *)
176                  GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
177     }
178     if (result == 0) return(0);
179     result -> id = id;
180     result -> next = GC_threads[hv];
181     GC_threads[hv] = result;
182     /* result -> flags = 0;     */
183     /* result -> stop = 0;      */
184     return(result);
185 }
186
187 /* Delete a thread from GC_threads.  We assume it is there.     */
188 /* (The code intentionally traps if it wasn't.)                 */
189 /* Caller holds allocation lock.                                */
190 /* We explicitly pass in the GC_thread we're looking for, since */
191 /* if a thread has been joined, but we have not yet             */
192 /* been notified, then there may be more than one thread        */
193 /* in the table with the same pthread id.                       */
194 /* This is OK, but we need a way to delete a specific one.      */
195 void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
196 {
197     int hv = ((word)id) % THREAD_TABLE_SZ;
198     register GC_thread p = GC_threads[hv];
199     register GC_thread prev = 0;
200
201     GC_ASSERT(I_HOLD_LOCK());
202     while (p != gc_id) {
203         prev = p;
204         p = p -> next;
205     }
206     if (prev == 0) {
207         GC_threads[hv] = p -> next;
208     } else {
209         prev -> next = p -> next;
210     }
211 }
212
213 /* Return a GC_thread corresponding to a given thread_t.        */
214 /* Returns 0 if it's not there.                                 */
215 /* Caller holds  allocation lock or otherwise inhibits          */
216 /* updates.                                                     */
217 /* If there is more than one thread with the given id we        */
218 /* return the most recent one.                                  */
219 GC_thread GC_lookup_thread(pthread_t id)
220 {
221     int hv = ((word)id) % THREAD_TABLE_SZ;
222     register GC_thread p = GC_threads[hv];
223     
224     /* I either hold the lock, or i'm being called from the stop-the-world
225      * handler. */
226 #if defined(GC_AIX_THREADS)
227     GC_ASSERT(I_HOLD_LOCK()); /* no stop-the-world handler needed on AIX */
228 #endif
229     while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
230     return(p);
231 }
232
233 #if defined(GC_AIX_THREADS)
234 void GC_stop_world()
235 {
236     pthread_t my_thread = pthread_self();
237     register int i;
238     register GC_thread p;
239     register int result;
240     struct timespec timeout;
241
242     GC_ASSERT(I_HOLD_LOCK());
243     for (i = 0; i < THREAD_TABLE_SZ; i++) {
244       for (p = GC_threads[i]; p != 0; p = p -> next) {
245         if (p -> id != my_thread) {
246           pthread_suspend_np(p->id);
247         }
248       }
249     }
250     /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
251 }
252
253 void GC_start_world()
254 {
255     GC_thread p;
256     unsigned i;
257     pthread_t my_thread = pthread_self();
258
259     /* GC_printf0("World starting\n"); */
260     GC_ASSERT(I_HOLD_LOCK());
261     for (i = 0; i < THREAD_TABLE_SZ; i++) {
262       for (p = GC_threads[i]; p != 0; p = p -> next) {
263         if (p -> id != my_thread) {
264           pthread_continue_np(p->id);
265         }
266       }
267     }
268 }
269
270 #else /* GC_AIX_THREADS */
271
272 /* Caller holds allocation lock.        */
273 void GC_stop_world()
274 {
275     pthread_t my_thread = pthread_self();
276     register int i;
277     register GC_thread p;
278     register int result;
279     struct timespec timeout;
280     
281     GC_ASSERT(I_HOLD_LOCK());
282     for (i = 0; i < THREAD_TABLE_SZ; i++) {
283       for (p = GC_threads[i]; p != 0; p = p -> next) {
284         if (p -> id != my_thread) {
285             if (p -> flags & FINISHED) {
286                 p -> stop = STOPPED;
287                 continue;
288             }
289             p -> stop = PLEASE_STOP;
290             result = pthread_kill(p -> id, SIG_SUSPEND);
291             /* GC_printf1("Sent signal to 0x%x\n", p -> id); */
292             switch(result) {
293                 case ESRCH:
294                     /* Not really there anymore.  Possible? */
295                     p -> stop = STOPPED;
296                     break;
297                 case 0:
298                     break;
299                 default:
300                     ABORT("pthread_kill failed");
301             }
302         }
303       }
304     }
305     pthread_mutex_lock(&GC_suspend_lock);
306     for (i = 0; i < THREAD_TABLE_SZ; i++) {
307       for (p = GC_threads[i]; p != 0; p = p -> next) {
308         while (p -> id != my_thread && p -> stop != STOPPED) {
309             clock_gettime(CLOCK_REALTIME, &timeout);
310             timeout.tv_nsec += 50000000; /* 50 msecs */
311             if (timeout.tv_nsec >= 1000000000) {
312                 timeout.tv_nsec -= 1000000000;
313                 ++timeout.tv_sec;
314             }
315             result = pthread_cond_timedwait(&GC_suspend_ack_cv,
316                                             &GC_suspend_lock,
317                                             &timeout);
318             if (result == ETIMEDOUT) {
319                 /* Signal was lost or misdirected.  Try again.      */
320                 /* Duplicate signals should be benign.              */
321                 result = pthread_kill(p -> id, SIG_SUSPEND);
322             }
323         }
324       }
325     }
326     pthread_mutex_unlock(&GC_suspend_lock);
327     /* GC_printf1("World stopped 0x%x\n", pthread_self()); */
328 }
329
330 /* Caller holds allocation lock.        */
331 void GC_start_world()
332 {
333     GC_thread p;
334     unsigned i;
335
336     /* GC_printf0("World starting\n"); */
337     GC_ASSERT(I_HOLD_LOCK());
338     for (i = 0; i < THREAD_TABLE_SZ; i++) {
339       for (p = GC_threads[i]; p != 0; p = p -> next) {
340         p -> stop = NOT_STOPPED;
341       }
342     }
343     pthread_mutex_lock(&GC_suspend_lock);
344     /* All other threads are at pthread_cond_wait in signal handler.    */
345     /* Otherwise we couldn't have acquired the lock.                    */
346     pthread_mutex_unlock(&GC_suspend_lock);
347     pthread_cond_broadcast(&GC_continue_cv);
348 }
349
350 #endif /* GC_AIX_THREADS */
351
352
353 /* We hold allocation lock.  Should do exactly the right thing if the   */
354 /* world is stopped.  Should not fail if it isn't.                      */
355 void GC_push_all_stacks()
356 {
357     register int i;
358     register GC_thread p;
359     register ptr_t hot, cold;
360     pthread_t me = pthread_self();
361     
362     /* GC_init() should have been called before GC_push_all_stacks is
363      * invoked, and GC_init calls GC_thr_init(), which sets
364      * GC_thr_initialized. */
365     GC_ASSERT(GC_thr_initialized);
366
367     /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
368     GC_ASSERT(I_HOLD_LOCK());
369     for (i = 0; i < THREAD_TABLE_SZ; i++) {
370       for (p = GC_threads[i]; p != 0; p = p -> next) {
371         if (p -> flags & FINISHED) continue;
372         cold = p->stack_cold;
373         if (!cold) cold=GC_stackbottom; /* 0 indicates 'original stack' */
374         if (pthread_equal(p -> id, me)) {
375             hot = GC_approx_sp();
376         } else {
377 #        ifdef GC_AIX_THREADS
378           /* AIX doesn't use signals to suspend, so we need to get an */
379           /* accurate hot stack pointer.                              */
380           /* See http://publib16.boulder.ibm.com/pseries/en_US/libs/basetrf1/pthread_getthrds_np.htm */
381           pthread_t id = p -> id;
382           struct __pthrdsinfo pinfo;
383           int regbuf[64];
384           int val = sizeof(regbuf);
385           int retval = pthread_getthrds_np(&id, PTHRDSINFO_QUERY_ALL, &pinfo,
386                                            sizeof(pinfo), regbuf, &val);
387           if (retval != 0) {
388             printf("ERROR: pthread_getthrds_np() failed in GC\n");
389             abort();
390           }
391           /* according to the AIX ABI, 
392              "the lowest possible valid stack address is 288 bytes (144 + 144)
393              less than the current value of the stack pointer.  Functions may
394              use this stack space as volatile storage which is not preserved
395              across function calls."
396              ftp://ftp.penguinppc64.org/pub/people/amodra/PPC-elf64abi.txt.gz
397           */
398           hot = (ptr_t)(unsigned long)pinfo.__pi_ustk-288;
399           cold = (ptr_t)pinfo.__pi_stackend; /* more precise */
400           /* push the registers too, because they won't be on stack */
401           GC_push_all_eager((ptr_t)&pinfo.__pi_context,
402                             (ptr_t)((&pinfo.__pi_context)+1));
403           GC_push_all_eager((ptr_t)regbuf, ((ptr_t)regbuf)+val);
404 #        else
405               hot = p -> stack_hot;
406 #        endif
407         }
408 #       ifdef STACK_GROWS_UP
409           GC_push_all_stack(cold, hot);
410 #       else
411  /* printf("thread 0x%x: hot=0x%08x cold=0x%08x\n", p -> id, hot, cold); */
412           GC_push_all_stack(hot, cold);
413 #       endif
414       }
415     }
416 }
417
418
419 /* We hold the allocation lock. */
420 void GC_thr_init()
421 {
422     GC_thread t;
423     struct sigaction act;
424
425     if (GC_thr_initialized) return;
426     GC_ASSERT(I_HOLD_LOCK());
427     GC_thr_initialized = TRUE;
428 #ifndef GC_AIX_THREADS
429     (void) sigaction(SIG_SUSPEND, 0, &act);
430     if (act.sa_handler != SIG_DFL)
431         ABORT("Previously installed SIG_SUSPEND handler");
432     /* Install handler. */
433         act.sa_handler = GC_suspend_handler;
434         act.sa_flags = SA_RESTART;
435         (void) sigemptyset(&act.sa_mask);
436         if (0 != sigaction(SIG_SUSPEND, &act, 0))
437             ABORT("Failed to install SIG_SUSPEND handler");
438 #endif
439     /* Add the initial thread, so we can stop it.       */
440       t = GC_new_thread(pthread_self());
441       /* use '0' to indicate GC_stackbottom, since GC_init() has not
442        * completed by the time we are called (from GC_init_inner()) */
443       t -> stack_cold = 0; /* the original stack. */
444       t -> stack_hot = (ptr_t)(&t);
445       t -> flags = DETACHED;
446 }
447
448 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
449 {
450     sigset_t fudged_set;
451     
452 #ifdef GC_AIX_THREADS
453     return(pthread_sigmask(how, set, oset));
454 #endif
455
456     if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
457         fudged_set = *set;
458         sigdelset(&fudged_set, SIG_SUSPEND);
459         set = &fudged_set;
460     }
461     return(pthread_sigmask(how, set, oset));
462 }
463
464 struct start_info {
465     void *(*start_routine)(void *);
466     void *arg;
467     word flags;
468     pthread_mutex_t registeredlock;
469     pthread_cond_t registered;     
470     int volatile registereddone;
471 };
472
473 void GC_thread_exit_proc(void *arg)
474 {
475     GC_thread me;
476
477     LOCK();
478     me = GC_lookup_thread(pthread_self());
479     me -> flags |= FINISHED;
480     /* reclaim DETACHED thread right away; otherwise wait until join() */
481     if (me -> flags & DETACHED) {
482         GC_delete_gc_thread(pthread_self(), me);
483     }
484     UNLOCK();
485 }
486
487 int GC_pthread_join(pthread_t thread, void **retval)
488 {
489     int result;
490     GC_thread thread_gc_id;
491     
492     LOCK();
493     thread_gc_id = GC_lookup_thread(thread);
494     /* This is guaranteed to be the intended one, since the thread id   */
495     /* cant have been recycled by pthreads.                             */
496     UNLOCK();
497     GC_ASSERT(!(thread_gc_id->flags & DETACHED));
498     result = pthread_join(thread, retval);
499     /* Some versions of the Irix pthreads library can erroneously       */
500     /* return EINTR when the call succeeds.                             */
501         if (EINTR == result) result = 0;
502     GC_ASSERT(thread_gc_id->flags & FINISHED);
503     LOCK();
504     /* Here the pthread thread id may have been recycled. */
505     GC_delete_gc_thread(thread, thread_gc_id);
506     UNLOCK();
507     return result;
508 }
509
510 void * GC_start_routine(void * arg)
511 {
512     int dummy;
513     struct start_info * si = arg;
514     void * result;
515     GC_thread me;
516     pthread_t my_pthread;
517     void *(*start)(void *);
518     void *start_arg;
519
520     my_pthread = pthread_self();
521     /* If a GC occurs before the thread is registered, that GC will     */
522     /* ignore this thread.  That's fine, since it will block trying to  */
523     /* acquire the allocation lock, and won't yet hold interesting      */
524     /* pointers.                                                        */
525     LOCK();
526     /* We register the thread here instead of in the parent, so that    */
527     /* we don't need to hold the allocation lock during pthread_create. */
528     /* Holding the allocation lock there would make REDIRECT_MALLOC     */
529     /* impossible.  It probably still doesn't work, but we're a little  */
530     /* closer ...                                                       */
531     /* This unfortunately means that we have to be careful the parent   */
532     /* doesn't try to do a pthread_join before we're registered.        */
533     me = GC_new_thread(my_pthread);
534     me -> flags = si -> flags;
535     me -> stack_cold = (ptr_t) &dummy; /* this now the 'start of stack' */
536     me -> stack_hot = me->stack_cold;/* this field should always be sensible */
537     UNLOCK();
538     start = si -> start_routine;
539     start_arg = si -> arg;
540
541     pthread_mutex_lock(&(si->registeredlock));
542     si->registereddone = 1;
543     pthread_cond_signal(&(si->registered));
544     pthread_mutex_unlock(&(si->registeredlock));
545     /* si went away as soon as we did this unlock */
546
547     pthread_cleanup_push(GC_thread_exit_proc, 0);
548     result = (*start)(start_arg);
549     me -> status = result;
550     pthread_cleanup_pop(1);
551         /* This involves acquiring the lock, ensuring that we can't exit */
552         /* while a collection that thinks we're alive is trying to stop  */
553         /* us.                                                           */
554     return(result);
555 }
556
557 int
558 GC_pthread_create(pthread_t *new_thread,
559                   const pthread_attr_t *attr,
560                   void *(*start_routine)(void *), void *arg)
561 {
562     int result;
563     GC_thread t;
564     int detachstate;
565     word my_flags = 0;
566     struct start_info * si;
567         /* This is otherwise saved only in an area mmapped by the thread */
568         /* library, which isn't visible to the collector.                */
569
570     LOCK();
571     /* GC_INTERNAL_MALLOC implicitly calls GC_init() if required */
572     si = (struct start_info *)GC_INTERNAL_MALLOC(sizeof(struct start_info),
573                                                  NORMAL);
574     GC_ASSERT(GC_thr_initialized); /* initialized by GC_init() */
575     UNLOCK();
576     if (0 == si) return(ENOMEM);
577     pthread_mutex_init(&(si->registeredlock), NULL);
578     pthread_cond_init(&(si->registered),NULL);
579     pthread_mutex_lock(&(si->registeredlock));
580     si -> start_routine = start_routine;
581     si -> arg = arg;
582
583     pthread_attr_getdetachstate(attr, &detachstate);
584     if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
585     si -> flags = my_flags;
586     result = pthread_create(new_thread, attr, GC_start_routine, si); 
587
588     /* Wait until child has been added to the thread table.             */
589     /* This also ensures that we hold onto si until the child is done   */
590     /* with it.  Thus it doesn't matter whether it is otherwise         */
591     /* visible to the collector.                                        */
592
593     if (0 == result) {
594       si->registereddone = 0;
595       while (!si->registereddone) 
596         pthread_cond_wait(&(si->registered), &(si->registeredlock));
597     }
598     pthread_mutex_unlock(&(si->registeredlock));
599
600     pthread_cond_destroy(&(si->registered));
601     pthread_mutex_destroy(&(si->registeredlock));
602     LOCK();
603     GC_INTERNAL_FREE(si);
604     UNLOCK();
605
606     return(result);
607 }
608
609 /* For now we use the pthreads locking primitives on HP/UX */
610
611 VOLATILE GC_bool GC_collecting = 0; /* A hint that we're in the collector and       */
612                         /* holding the allocation lock for an           */
613                         /* extended period.                             */
614
615 /* Reasonably fast spin locks.  Basically the same implementation */
616 /* as STL alloc.h.                                                */
617
618 #define SLEEP_THRESHOLD 3
619
620 volatile unsigned int GC_allocate_lock = 0;
621 #define GC_TRY_LOCK() !GC_test_and_set(&GC_allocate_lock)
622 #define GC_LOCK_TAKEN GC_allocate_lock
623
624 void GC_lock()
625 {
626 #   define low_spin_max 30  /* spin cycles if we suspect uniprocessor */
627 #   define high_spin_max 1000 /* spin cycles for multiprocessor */
628     static unsigned spin_max = low_spin_max;
629     unsigned my_spin_max;
630     static unsigned last_spins = 0;
631     unsigned my_last_spins;
632     volatile unsigned junk;
633 #   define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
634     int i;
635
636     if (GC_TRY_LOCK()) {
637         return;
638     }
639     junk = 0;
640     my_spin_max = spin_max;
641     my_last_spins = last_spins;
642     for (i = 0; i < my_spin_max; i++) {
643         if (GC_collecting) goto yield;
644         if (i < my_last_spins/2 || GC_LOCK_TAKEN) {
645             PAUSE; 
646             continue;
647         }
648         if (GC_TRY_LOCK()) {
649             /*
650              * got it!
651              * Spinning worked.  Thus we're probably not being scheduled
652              * against the other process with which we were contending.
653              * Thus it makes sense to spin longer the next time.
654              */
655             last_spins = i;
656             spin_max = high_spin_max;
657             return;
658         }
659     }
660     /* We are probably being scheduled against the other process.  Sleep. */
661     spin_max = low_spin_max;
662 yield:
663     for (i = 0;; ++i) {
664         if (GC_TRY_LOCK()) {
665             return;
666         }
667         if (i < SLEEP_THRESHOLD) {
668             sched_yield();
669         } else {
670             struct timespec ts;
671         
672             if (i > 26) i = 26;
673                         /* Don't wait for more than about 60msecs, even */
674                         /* under extreme contention.                    */
675             ts.tv_sec = 0;
676             ts.tv_nsec = 1 << i;
677             nanosleep(&ts, 0);
678         }
679     }
680 }
681
682 # else  /* !GC_IRIX_THREADS && !GC_AIX_THREADS */
683
684 #ifndef LINT
685   int GC_no_Irix_threads;
686 #endif
687
688 # endif /* IRIX_THREADS */
689