OSDN Git Service

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