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