/*
- * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
- * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
+ * 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.
* 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(IRIX_THREADS)
+# if defined(GC_IRIX_THREADS)
-# include "gc_priv.h"
+# include "private/gc_priv.h"
# include <pthread.h>
+# include <semaphore.h>
# include <time.h>
# include <errno.h>
# include <unistd.h>
#undef pthread_create
#undef pthread_sigmask
#undef pthread_join
+#undef pthread_detach
void GC_thr_init();
* 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.
*/
-# define SIG_SUSPEND (SIGRTMIN + 6)
pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER;
/* Number of threads stopped so far */
size_t GC_min_stack_sz;
-size_t GC_page_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 */
if (result != 0) {
GC_stack_free_lists[index] = *(ptr_t *)result;
} else {
- result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_sz);
- result = (ptr_t)(((word)result + GC_page_sz) & ~(GC_page_sz - 1));
+ 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. */
- /* mprotect(result, GC_page_sz, PROT_NONE); */
- result += GC_page_sz;
+# 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);
# 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)
/* Dont acquire allocation lock, since we may already hold it. */
} else {
result = (struct GC_Thread_Rep *)
- GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
+ GC_INTERNAL_MALLOC(sizeof(struct GC_Thread_Rep), NORMAL);
}
if (result == 0) return(0);
result -> id = id;
}
# endif
-/* We hold allocation lock. We assume the world is stopped. */
+/* 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 lo, hi;
+ register ptr_t hot, cold;
pthread_t me = pthread_self();
if (!GC_thr_initialized) GC_thr_init();
for (p = GC_threads[i]; p != 0; p = p -> next) {
if (p -> flags & FINISHED) continue;
if (pthread_equal(p -> id, me)) {
- lo = GC_approx_sp();
+ hot = GC_approx_sp();
} else {
- lo = p -> stack_ptr;
+ hot = p -> stack_ptr;
}
if (p -> stack_size != 0) {
- hi = p -> stack + p -> stack_size;
+# ifdef STACK_GROWS_UP
+ cold = p -> stack;
+# else
+ cold = p -> stack + p -> stack_size;
+# endif
} else {
/* The original stack. */
- hi = GC_stackbottom;
+ cold = GC_stackbottom;
}
- GC_push_all_stack(lo, hi);
+# ifdef STACK_GROWS_UP
+ GC_push_all_stack(cold, hot);
+# else
+ GC_push_all_stack(hot, cold);
+# endif
}
}
}
GC_thread t;
struct sigaction act;
+ if (GC_thr_initialized) return;
GC_thr_initialized = TRUE;
GC_min_stack_sz = HBLKSIZE;
- GC_page_sz = sysconf(_SC_PAGESIZE);
(void) sigaction(SIG_SUSPEND, 0, &act);
if (act.sa_handler != SIG_DFL)
ABORT("Previously installed SIG_SUSPEND handler");
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 *dummy)
+void GC_thread_exit_proc(void *arg)
{
GC_thread me;
/* 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();
- /* Here the pthread thread id may have been recycled. */
- GC_delete_gc_thread(thread, thread_gc_id);
+ 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;
}
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();
- me = GC_lookup_thread(pthread_self());
+ /* 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 = (*(si -> start_routine))(si -> arg);
+ result = (*start)(start_arg);
me -> status = result;
me -> flags |= FINISHED;
pthread_cleanup_pop(1);
return(result);
}
+# define copy_attr(pa_ptr, source) *(pa_ptr) = *(source)
+
int
GC_pthread_create(pthread_t *new_thread,
const pthread_attr_t *attr,
{
int result;
GC_thread t;
- pthread_t my_new_thread;
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_thr_initialized) GC_thr_init();
+ if (!GC_is_initialized) GC_init();
if (NULL == attr) {
stack = 0;
(void) pthread_attr_init(&new_attr);
} else {
- new_attr = *attr;
+ copy_attr(&new_attr, attr);
pthread_attr_getstackaddr(&new_attr, &stack);
}
pthread_attr_getstacksize(&new_attr, &stacksize);
my_flags |= CLIENT_OWNS_STACK;
}
if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
- result = pthread_create(&my_new_thread, &new_attr, GC_start_routine, si);
- /* No GC can start until the thread is registered, since we hold */
- /* the allocation lock. */
- if (0 == result) {
- t = GC_new_thread(my_new_thread);
- t -> flags = my_flags;
- t -> stack = stack;
- t -> stack_size = stacksize;
- t -> stack_ptr = (ptr_t)stack + stacksize - sizeof(word);
- if (0 != new_thread) *new_thread = my_new_thread;
- } else if (!(my_flags & CLIENT_OWNS_STACK)) {
+ 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();
- /* pthread_attr_destroy(&new_attr); */
+ /* 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);
}
-GC_bool GC_collecting = 0; /* A hint that we're in the collector and */
+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. This isn't really the right way to do this. */
-/* but until the POSIX scheduling mess gets straightened out ... */
-
-unsigned long GC_allocate_lock = 0;
+/* 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 PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
int i;
- if (!GC_test_and_set(&GC_allocate_lock, 1)) {
+ if (GC_TRY_LOCK()) {
return;
}
junk = 0;
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_allocate_lock) {
+ if (i < my_last_spins/2 || GC_LOCK_TAKEN) {
PAUSE;
continue;
}
- if (!GC_test_and_set(&GC_allocate_lock, 1)) {
+ if (GC_TRY_LOCK()) {
/*
* got it!
* Spinning worked. Thus we're probably not being scheduled
spin_max = low_spin_max;
yield:
for (i = 0;; ++i) {
- if (!GC_test_and_set(&GC_allocate_lock, 1)) {
+ if (GC_TRY_LOCK()) {
return;
}
if (i < SLEEP_THRESHOLD) {
}
}
-
-
# else
#ifndef LINT
int GC_no_Irix_threads;
#endif
-# endif /* IRIX_THREADS */
+# endif /* GC_IRIX_THREADS */