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