/* * Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved. * Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved. * Copyright (c) 1999 by Hewlett-Packard Company. All rights reserved. * * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. * * Permission is hereby granted to use or copy this program * for any purpose, provided the above notices are retained on all copies. * Permission to modify the code and to distribute modified code is granted, * provided the above notices are retained, and a notice that the code was * modified is included with the above copyright notice. */ /* * Support code for Irix (>=6.2) Pthreads. This relies on properties * not guaranteed by the Pthread standard. It may or may not be portable * to other implementations. * * This now also includes an initial attempt at thread support for * HP/UX 11. * * Note that there is a lot of code duplication between linux_threads.c * and irix_threads.c; any changes made here may need to be reflected * there too. */ # if defined(GC_IRIX_THREADS) # include "private/gc_priv.h" # include # include # include # include # include # include # include #undef pthread_create #undef pthread_sigmask #undef pthread_join #undef pthread_detach #ifdef HANDLE_FORK --> Not yet supported. Try porting the code from linux_threads.c. #endif void GC_thr_init(); #if 0 void GC_print_sig_mask() { sigset_t blocked; int i; if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0) ABORT("pthread_sigmask"); GC_printf0("Blocked: "); for (i = 1; i <= MAXSIG; i++) { if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); } } GC_printf0("\n"); } #endif /* We use the allocation lock to protect thread-related data structures. */ /* The set of all known threads. We intercept thread creation and */ /* joins. We never actually create detached threads. We allocate all */ /* new thread stacks ourselves. These allow us to maintain this */ /* data structure. */ /* Protected by GC_thr_lock. */ /* Some of this should be declared volatile, but that's incosnsistent */ /* with some library routine declarations. */ typedef struct GC_Thread_Rep { struct GC_Thread_Rep * next; /* More recently allocated threads */ /* with a given pthread id come */ /* first. (All but the first are */ /* guaranteed to be dead, but we may */ /* not yet have registered the join.) */ pthread_t id; word stop; # define NOT_STOPPED 0 # define PLEASE_STOP 1 # define STOPPED 2 word flags; # define FINISHED 1 /* Thread has exited. */ # define DETACHED 2 /* Thread is intended to be detached. */ # define CLIENT_OWNS_STACK 4 /* Stack was supplied by client. */ ptr_t stack; ptr_t stack_ptr; /* Valid only when stopped. */ /* But must be within stack region at */ /* all times. */ size_t stack_size; /* 0 for original thread. */ void * status; /* Used only to avoid premature */ /* reclamation of any data it might */ /* reference. */ } * GC_thread; GC_thread GC_lookup_thread(pthread_t id); /* * The only way to suspend threads given the pthread interface is to send * signals. Unfortunately, this means we have to reserve * a signal, and intercept client calls to change the signal mask. * We use SIG_SUSPEND, defined in gc_priv.h. */ pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER; /* Number of threads stopped so far */ pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER; pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER; void GC_suspend_handler(int sig) { int dummy; GC_thread me; sigset_t all_sigs; sigset_t old_sigs; int i; if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler"); me = GC_lookup_thread(pthread_self()); /* The lookup here is safe, since I'm doing this on behalf */ /* of a thread which holds the allocation lock in order */ /* to stop the world. Thus concurrent modification of the */ /* data structure is impossible. */ if (PLEASE_STOP != me -> stop) { /* Misdirected signal. */ pthread_mutex_unlock(&GC_suspend_lock); return; } pthread_mutex_lock(&GC_suspend_lock); me -> stack_ptr = (ptr_t)(&dummy); me -> stop = STOPPED; pthread_cond_signal(&GC_suspend_ack_cv); pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock); pthread_mutex_unlock(&GC_suspend_lock); /* GC_printf1("Continuing 0x%x\n", pthread_self()); */ } GC_bool GC_thr_initialized = FALSE; size_t GC_min_stack_sz; # define N_FREE_LISTS 25 ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 }; /* GC_stack_free_lists[i] is free list for stacks of */ /* size GC_min_stack_sz*2**i. */ /* Free lists are linked through first word. */ /* Return a stack of size at least *stack_size. *stack_size is */ /* replaced by the actual stack size. */ /* Caller holds allocation lock. */ ptr_t GC_stack_alloc(size_t * stack_size) { register size_t requested_sz = *stack_size; register size_t search_sz = GC_min_stack_sz; register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */ register ptr_t result; while (search_sz < requested_sz) { search_sz *= 2; index++; } if ((result = GC_stack_free_lists[index]) == 0 && (result = GC_stack_free_lists[index+1]) != 0) { /* Try next size up. */ search_sz *= 2; index++; } if (result != 0) { GC_stack_free_lists[index] = *(ptr_t *)result; } else { result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_size); result = (ptr_t)(((word)result + GC_page_size) & ~(GC_page_size - 1)); /* Protect hottest page to detect overflow. */ # ifdef STACK_GROWS_UP /* mprotect(result + search_sz, GC_page_size, PROT_NONE); */ # else /* mprotect(result, GC_page_size, PROT_NONE); */ result += GC_page_size; # endif } *stack_size = search_sz; return(result); } /* Caller holds allocation lock. */ void GC_stack_free(ptr_t stack, size_t size) { register int index = 0; register size_t search_sz = GC_min_stack_sz; while (search_sz < size) { search_sz *= 2; index++; } if (search_sz != size) ABORT("Bad stack size"); *(ptr_t *)stack = GC_stack_free_lists[index]; GC_stack_free_lists[index] = stack; } # define THREAD_TABLE_SZ 128 /* Must be power of 2 */ volatile GC_thread GC_threads[THREAD_TABLE_SZ]; void GC_push_thread_structures GC_PROTO((void)) { GC_push_all((ptr_t)(GC_threads), (ptr_t)(GC_threads)+sizeof(GC_threads)); } /* Add a thread to GC_threads. We assume it wasn't already there. */ /* Caller holds allocation lock. */ GC_thread GC_new_thread(pthread_t id) { int hv = ((word)id) % THREAD_TABLE_SZ; GC_thread result; static struct GC_Thread_Rep first_thread; static GC_bool first_thread_used = FALSE; if (!first_thread_used) { result = &first_thread; first_thread_used = TRUE; /* Dont acquire allocation lock, since we may already hold it. */ } else { result = (struct GC_Thread_Rep *) GC_INTERNAL_MALLOC(sizeof(struct GC_Thread_Rep), NORMAL); } if (result == 0) return(0); result -> id = id; result -> next = GC_threads[hv]; GC_threads[hv] = result; /* result -> flags = 0; */ /* result -> stop = 0; */ return(result); } /* Delete a thread from GC_threads. We assume it is there. */ /* (The code intentionally traps if it wasn't.) */ /* Caller holds allocation lock. */ void GC_delete_thread(pthread_t id) { int hv = ((word)id) % THREAD_TABLE_SZ; register GC_thread p = GC_threads[hv]; register GC_thread prev = 0; while (!pthread_equal(p -> id, id)) { prev = p; p = p -> next; } if (prev == 0) { GC_threads[hv] = p -> next; } else { prev -> next = p -> next; } } /* If a thread has been joined, but we have not yet */ /* been notified, then there may be more than one thread */ /* in the table with the same pthread id. */ /* This is OK, but we need a way to delete a specific one. */ void GC_delete_gc_thread(pthread_t id, GC_thread gc_id) { int hv = ((word)id) % THREAD_TABLE_SZ; register GC_thread p = GC_threads[hv]; register GC_thread prev = 0; while (p != gc_id) { prev = p; p = p -> next; } if (prev == 0) { GC_threads[hv] = p -> next; } else { prev -> next = p -> next; } } /* Return a GC_thread corresponding to a given thread_t. */ /* Returns 0 if it's not there. */ /* Caller holds allocation lock or otherwise inhibits */ /* updates. */ /* If there is more than one thread with the given id we */ /* return the most recent one. */ GC_thread GC_lookup_thread(pthread_t id) { int hv = ((word)id) % THREAD_TABLE_SZ; register GC_thread p = GC_threads[hv]; while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next; return(p); } /* Caller holds allocation lock. */ void GC_stop_world() { pthread_t my_thread = pthread_self(); register int i; register GC_thread p; register int result; struct timespec timeout; for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { if (p -> id != my_thread) { if (p -> flags & FINISHED) { p -> stop = STOPPED; continue; } p -> stop = PLEASE_STOP; result = pthread_kill(p -> id, SIG_SUSPEND); /* GC_printf1("Sent signal to 0x%x\n", p -> id); */ switch(result) { case ESRCH: /* Not really there anymore. Possible? */ p -> stop = STOPPED; break; case 0: break; default: ABORT("pthread_kill failed"); } } } } pthread_mutex_lock(&GC_suspend_lock); for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { while (p -> id != my_thread && p -> stop != STOPPED) { clock_gettime(CLOCK_REALTIME, &timeout); timeout.tv_nsec += 50000000; /* 50 msecs */ if (timeout.tv_nsec >= 1000000000) { timeout.tv_nsec -= 1000000000; ++timeout.tv_sec; } result = pthread_cond_timedwait(&GC_suspend_ack_cv, &GC_suspend_lock, &timeout); if (result == ETIMEDOUT) { /* Signal was lost or misdirected. Try again. */ /* Duplicate signals should be benign. */ result = pthread_kill(p -> id, SIG_SUSPEND); } } } } pthread_mutex_unlock(&GC_suspend_lock); /* GC_printf1("World stopped 0x%x\n", pthread_self()); */ } /* Caller holds allocation lock. */ void GC_start_world() { GC_thread p; unsigned i; /* GC_printf0("World starting\n"); */ for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { p -> stop = NOT_STOPPED; } } pthread_mutex_lock(&GC_suspend_lock); /* All other threads are at pthread_cond_wait in signal handler. */ /* Otherwise we couldn't have acquired the lock. */ pthread_mutex_unlock(&GC_suspend_lock); pthread_cond_broadcast(&GC_continue_cv); } # ifdef MMAP_STACKS --> not really supported yet. int GC_is_thread_stack(ptr_t addr) { register int i; register GC_thread p; for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { if (p -> stack_size != 0) { if (p -> stack <= addr && addr < p -> stack + p -> stack_size) return 1; } } } return 0; } # endif /* We hold allocation lock. Should do exactly the right thing if the */ /* world is stopped. Should not fail if it isn't. */ void GC_push_all_stacks() { register int i; register GC_thread p; register ptr_t sp = GC_approx_sp(); register ptr_t hot, cold; pthread_t me = pthread_self(); if (!GC_thr_initialized) GC_thr_init(); /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */ for (i = 0; i < THREAD_TABLE_SZ; i++) { for (p = GC_threads[i]; p != 0; p = p -> next) { if (p -> flags & FINISHED) continue; if (pthread_equal(p -> id, me)) { hot = GC_approx_sp(); } else { hot = p -> stack_ptr; } if (p -> stack_size != 0) { # ifdef STACK_GROWS_UP cold = p -> stack; # else cold = p -> stack + p -> stack_size; # endif } else { /* The original stack. */ cold = GC_stackbottom; } # ifdef STACK_GROWS_UP GC_push_all_stack(cold, hot); # else GC_push_all_stack(hot, cold); # endif } } } /* We hold the allocation lock. */ void GC_thr_init() { GC_thread t; struct sigaction act; if (GC_thr_initialized) return; GC_thr_initialized = TRUE; GC_min_stack_sz = HBLKSIZE; (void) sigaction(SIG_SUSPEND, 0, &act); if (act.sa_handler != SIG_DFL) ABORT("Previously installed SIG_SUSPEND handler"); /* Install handler. */ act.sa_handler = GC_suspend_handler; act.sa_flags = SA_RESTART; (void) sigemptyset(&act.sa_mask); if (0 != sigaction(SIG_SUSPEND, &act, 0)) ABORT("Failed to install SIG_SUSPEND handler"); /* Add the initial thread, so we can stop it. */ t = GC_new_thread(pthread_self()); t -> stack_size = 0; t -> stack_ptr = (ptr_t)(&t); t -> flags = DETACHED; } int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) { sigset_t fudged_set; if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) { fudged_set = *set; sigdelset(&fudged_set, SIG_SUSPEND); set = &fudged_set; } return(pthread_sigmask(how, set, oset)); } struct start_info { void *(*start_routine)(void *); void *arg; word flags; ptr_t stack; size_t stack_size; sem_t registered; /* 1 ==> in our thread table, but */ /* parent hasn't yet noticed. */ }; void GC_thread_exit_proc(void *arg) { GC_thread me; LOCK(); me = GC_lookup_thread(pthread_self()); if (me -> flags & DETACHED) { GC_delete_thread(pthread_self()); } else { me -> flags |= FINISHED; } UNLOCK(); } int GC_pthread_join(pthread_t thread, void **retval) { int result; GC_thread thread_gc_id; LOCK(); thread_gc_id = GC_lookup_thread(thread); /* This is guaranteed to be the intended one, since the thread id */ /* cant have been recycled by pthreads. */ UNLOCK(); result = pthread_join(thread, retval); /* Some versions of the Irix pthreads library can erroneously */ /* return EINTR when the call succeeds. */ if (EINTR == result) result = 0; if (result == 0) { LOCK(); /* Here the pthread thread id may have been recycled. */ GC_delete_gc_thread(thread, thread_gc_id); UNLOCK(); } return result; } int GC_pthread_detach(pthread_t thread) { int result; GC_thread thread_gc_id; LOCK(); thread_gc_id = GC_lookup_thread(thread); UNLOCK(); result = pthread_detach(thread); if (result == 0) { LOCK(); thread_gc_id -> flags |= DETACHED; /* Here the pthread thread id may have been recycled. */ if (thread_gc_id -> flags & FINISHED) { GC_delete_gc_thread(thread, thread_gc_id); } UNLOCK(); } return result; } void * GC_start_routine(void * arg) { struct start_info * si = arg; void * result; GC_thread me; pthread_t my_pthread; void *(*start)(void *); void *start_arg; my_pthread = pthread_self(); /* If a GC occurs before the thread is registered, that GC will */ /* ignore this thread. That's fine, since it will block trying to */ /* acquire the allocation lock, and won't yet hold interesting */ /* pointers. */ LOCK(); /* We register the thread here instead of in the parent, so that */ /* we don't need to hold the allocation lock during pthread_create. */ /* Holding the allocation lock there would make REDIRECT_MALLOC */ /* impossible. It probably still doesn't work, but we're a little */ /* closer ... */ /* This unfortunately means that we have to be careful the parent */ /* doesn't try to do a pthread_join before we're registered. */ me = GC_new_thread(my_pthread); me -> flags = si -> flags; me -> stack = si -> stack; me -> stack_size = si -> stack_size; me -> stack_ptr = (ptr_t)si -> stack + si -> stack_size - sizeof(word); UNLOCK(); start = si -> start_routine; start_arg = si -> arg; sem_post(&(si -> registered)); pthread_cleanup_push(GC_thread_exit_proc, 0); result = (*start)(start_arg); me -> status = result; me -> flags |= FINISHED; pthread_cleanup_pop(1); /* This involves acquiring the lock, ensuring that we can't exit */ /* while a collection that thinks we're alive is trying to stop */ /* us. */ return(result); } # define copy_attr(pa_ptr, source) *(pa_ptr) = *(source) int GC_pthread_create(pthread_t *new_thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) { int result; GC_thread t; void * stack; size_t stacksize; pthread_attr_t new_attr; int detachstate; word my_flags = 0; struct start_info * si = GC_malloc(sizeof(struct start_info)); /* This is otherwise saved only in an area mmapped by the thread */ /* library, which isn't visible to the collector. */ if (0 == si) return(ENOMEM); if (0 != sem_init(&(si -> registered), 0, 0)) { ABORT("sem_init failed"); } si -> start_routine = start_routine; si -> arg = arg; LOCK(); if (!GC_is_initialized) GC_init(); if (NULL == attr) { stack = 0; (void) pthread_attr_init(&new_attr); } else { copy_attr(&new_attr, attr); pthread_attr_getstackaddr(&new_attr, &stack); } pthread_attr_getstacksize(&new_attr, &stacksize); pthread_attr_getdetachstate(&new_attr, &detachstate); if (stacksize < GC_min_stack_sz) ABORT("Stack too small"); if (0 == stack) { stack = (void *)GC_stack_alloc(&stacksize); if (0 == stack) { UNLOCK(); return(ENOMEM); } pthread_attr_setstackaddr(&new_attr, stack); } else { my_flags |= CLIENT_OWNS_STACK; } if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED; si -> flags = my_flags; si -> stack = stack; si -> stack_size = stacksize; result = pthread_create(new_thread, &new_attr, GC_start_routine, si); if (0 == new_thread && !(my_flags & CLIENT_OWNS_STACK)) { GC_stack_free(stack, stacksize); } UNLOCK(); /* Wait until child has been added to the thread table. */ /* This also ensures that we hold onto si until the child is done */ /* with it. Thus it doesn't matter whether it is otherwise */ /* visible to the collector. */ while (0 != sem_wait(&(si -> registered))) { if (errno != EINTR) { GC_printf1("Sem_wait: errno = %ld\n", (unsigned long) errno); ABORT("sem_wait failed"); } } sem_destroy(&(si -> registered)); pthread_attr_destroy(&new_attr); /* Probably unnecessary under Irix */ return(result); } VOLATILE GC_bool GC_collecting = 0; /* A hint that we're in the collector and */ /* holding the allocation lock for an */ /* extended period. */ /* Reasonably fast spin locks. Basically the same implementation */ /* as STL alloc.h. */ #define SLEEP_THRESHOLD 3 unsigned long GC_allocate_lock = 0; # define GC_TRY_LOCK() !GC_test_and_set(&GC_allocate_lock) # define GC_LOCK_TAKEN GC_allocate_lock void GC_lock() { # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */ # define high_spin_max 1000 /* spin cycles for multiprocessor */ static unsigned spin_max = low_spin_max; unsigned my_spin_max; static unsigned last_spins = 0; unsigned my_last_spins; volatile unsigned junk; # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk int i; if (GC_TRY_LOCK()) { return; } junk = 0; my_spin_max = spin_max; my_last_spins = last_spins; for (i = 0; i < my_spin_max; i++) { if (GC_collecting) goto yield; if (i < my_last_spins/2 || GC_LOCK_TAKEN) { PAUSE; continue; } if (GC_TRY_LOCK()) { /* * got it! * Spinning worked. Thus we're probably not being scheduled * against the other process with which we were contending. * Thus it makes sense to spin longer the next time. */ last_spins = i; spin_max = high_spin_max; return; } } /* We are probably being scheduled against the other process. Sleep. */ spin_max = low_spin_max; yield: for (i = 0;; ++i) { if (GC_TRY_LOCK()) { return; } if (i < SLEEP_THRESHOLD) { sched_yield(); } else { struct timespec ts; if (i > 26) i = 26; /* Don't wait for more than about 60msecs, even */ /* under extreme contention. */ ts.tv_sec = 0; ts.tv_nsec = 1 << i; nanosleep(&ts, 0); } } } # else #ifndef LINT int GC_no_Irix_threads; #endif # endif /* GC_IRIX_THREADS */