diff: port Boehm GC multithreading to LinuxThreads
Fergus Henderson
fjh at cs.mu.OZ.AU
Fri May 22 16:03:52 AEST 1998
I've committed the following change, and also merged in the changes
from the latest version (4.13alpha1) of the Boehm collector.
Estimated hours taken: 30
Port the Boehm collector's thread support to Linux.
(Still not quite working -- if/when threads exit, it sometimes deadlocks.)
boehm_gc/config.h:
Port to Linux libc6.
boehm_gc/config.h:
boehm_gc/gc.h:
boehm_gc/gc_priv.h:
boehm_gc/misc.c:
boehm_gc/os_dep.c:
boehm_gc/threadlibs.c:
boehm_gc/test.c:
Various changes to add support for Linux threads,
mostly adding `|| defined(LINUX_THREADS)' to various
#ifdef tests.
boehm_gc/gc_priv.h:
Add code to handle mutex locking for Linux.
boehm_gc/linux_threads.c:
New file, cut-and-paste from irix_threads.c, with
modifications to make it work under Linux.
boehm_gc/Makefile:
Document the LINUX_THREADS macro.
Add linux_threads.{c,o} to the list of source/object files.
boehm_gc/README.Linux:
Document thread support.
boehm_gc/gc_priv.h:
boehm_gc/irix_threads.c:
boehm_gc/misc.c:
Declare GC_approx_sp() in gc_priv.h and
delete declarations of it from .c files.
boehm_gc/misc.c:
Don't declare GC_thr_init(), since it is declared in gc.h.
boehm_gc/irix_threads.c:
Fix a cut-and-paste error in an error message:
s/thr_kill/pthread_kill/
boehm_gc/gc_priv.h:
boehm_gc/irix_threads.c:
Define `GC_test_and_set' instead of `__test_and_set',
to avoid invading the implementation's namespace.
cvs diff -N boehm_gc/Makefile boehm_gc/README.Linux boehm_gc/config.h boehm_gc/gc.h boehm_gc/gc_priv.h boehm_gc/irix_threads.c boehm_gc/linux_threads.c boehm_gc/misc.c boehm_gc/os_dep.c boehm_gc/test.c boehm_gc/threadlibs.c
Index: boehm_gc/Makefile
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/Makefile,v
retrieving revision 1.35
diff -u -r1.35 Makefile
--- Makefile 1998/02/03 09:42:05 1.35
+++ Makefile 1998/05/20 15:05:00
@@ -59,7 +59,9 @@
# Must also define -D_REENTRANT
# -D_SOLARIS_PTHREADS enables support for Solaris pthreads.
# Define SOLARIS_THREADS as well.
-# -DIRIX_THREADS enables support for Irix pthreads. See README.irix.
+# -DIRIX_THREADS enables support for Irix pthreads. See README.sgi.
+# -DLINUX_THREADS (plus -D_REENTRANT) enables support for Linux pthreads.
+# See README.linux.
# -DALL_INTERIOR_POINTERS allows all pointers to the interior
# of objects to be recognized. (See gc_priv.h for consequences.)
# -DSMALL_CONFIG tries to tune the collector for small heap sizes,
@@ -138,9 +140,9 @@
srcdir = .
VPATH = $(srcdir)
-OBJS= alloc.o reclaim.o allchblk.o misc.o mach_dep.o os_dep.o mark_rts.o headers.o mark.o obj_map.o blacklst.o finalize.o new_hblk.o dbg_mlc.o malloc.o stubborn.o checksums.o solaris_threads.o irix_threads.o typd_mlc.o ptr_chck.o mallocx.o solaris_pthreads.o
+OBJS= alloc.o reclaim.o allchblk.o misc.o mach_dep.o os_dep.o mark_rts.o headers.o mark.o obj_map.o blacklst.o finalize.o new_hblk.o dbg_mlc.o malloc.o stubborn.o checksums.o solaris_threads.o irix_threads.o linux_threads.o typd_mlc.o ptr_chck.o mallocx.o solaris_pthreads.o
-CSRCS= reclaim.c allchblk.c misc.c alloc.c mach_dep.c os_dep.c mark_rts.c headers.c mark.c obj_map.c pcr_interface.c blacklst.c finalize.c new_hblk.c real_malloc.c dyn_load.c dbg_mlc.c malloc.c stubborn.c checksums.c solaris_threads.c irix_threads.c typd_mlc.c ptr_chck.c mallocx.c solaris_pthreads.c
+CSRCS= reclaim.c allchblk.c misc.c alloc.c mach_dep.c os_dep.c mark_rts.c headers.c mark.c obj_map.c pcr_interface.c blacklst.c finalize.c new_hblk.c real_malloc.c dyn_load.c dbg_mlc.c malloc.c stubborn.c checksums.c solaris_threads.c irix_threads.c linux_threads.c typd_mlc.c ptr_chck.c mallocx.c solaris_pthreads.c
CORD_SRCS= cord/cordbscs.c cord/cordxtra.c cord/cordprnt.c cord/de.c cord/cordtest.c cord/cord.h cord/ec.h cord/private/cord_pos.h cord/de_win.c cord/de_win.h cord/de_cmds.h cord/de_win.ICO cord/de_win.RC cord/SCOPTIONS.amiga cord/SMakefile.amiga
cvs diff: I know nothing about boehm_gc/README.Linux
Index: boehm_gc/config.h
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/config.h,v
retrieving revision 1.10
diff -u -r1.10 config.h
--- config.h 1997/11/21 11:07:35 1.10
+++ config.h 1998/05/02 09:43:34
@@ -572,20 +572,10 @@
# define MPROTECT_VDB
# ifdef __ELF__
# define DYNAMIC_LOADING
-# ifdef UNDEFINED /* includes ro data */
- extern int _etext;
-# define DATASTART ((ptr_t)((((word) (&_etext)) + 0xfff) & ~0xfff))
-# endif
- extern char **__environ;
-# define DATASTART ((ptr_t)(&__environ))
- /* hideous kludge: __environ is the first */
- /* word in crt0.o, and delimits the start */
- /* of the data segment, no matter which */
- /* ld options were passed through. */
- /* We could use _etext instead, but that */
- /* would include .rodata, which may */
- /* contain large read-only data tables */
- /* that we'd rather not scan. */
+ /* This may require a recent version of libc and/or binutils.
+ Works with libc6 and binutils 2.9. */
+ extern int __data_start;
+# define DATASTART (&__data_start)
extern int _end;
# define DATAEND (&_end)
# else
@@ -906,10 +896,15 @@
# if defined(IRIX_THREADS) && !defined(IRIX5)
--> inconsistent configuration
# endif
+# if defined(LINUX_THREADS) && !defined(LINUX)
+--> inconsistent configuration
+# endif
# if defined(SOLARIS_THREADS) && !defined(SUNOS5)
--> inconsistent configuration
# endif
-# if defined(PCR) || defined(SRC_M3) || defined(SOLARIS_THREADS) || defined(WIN32_THREADS) || defined(IRIX_THREADS)
+# if defined(PCR) || defined(SRC_M3) || \
+ defined(SOLARIS_THREADS) || defined(WIN32_THREADS) || \
+ defined(IRIX_THREADS) || defined(LINUX_THREADS)
# define THREADS
# endif
Index: boehm_gc/gc.h
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/gc.h,v
retrieving revision 1.18
diff -u -r1.18 gc.h
--- gc.h 1997/12/10 06:29:31 1.18
+++ gc.h 1998/05/02 09:50:56
@@ -629,7 +629,7 @@
# endif /* SOLARIS_THREADS */
-#ifdef IRIX_THREADS
+#if defined(IRIX_THREADS) || defined(LINUX_THREADS)
/* We treat these similarly. */
# include <pthread.h>
# include <signal.h>
@@ -644,18 +644,21 @@
# define pthread_sigmask GC_pthread_sigmask
# define pthread_join GC_pthread_join
-#endif /* IRIX_THREADS */
+#endif /* IRIX_THREADS || LINUX_THREADS */
-#if defined(SOLARIS_THREADS) || defined(IRIX_THREADS)
+#if defined(THREADS) && !defined(SRC_M3)
/* This returns a list of objects, linked through their first */
/* word. Its use can greatly reduce lock contention problems, since */
/* the allocation lock can be acquired and released many fewer times. */
GC_PTR GC_malloc_many(size_t lb);
#define GC_NEXT(p) (*(GC_PTR *)(p)) /* Retrieve the next element */
/* in returned list. */
-extern void GC_thr_init(); /* Needed for Solaris/X86 */
+#endif /* THREADS && !SRC_M3 */
-#endif /* SOLARIS_THREADS */
+#if defined(SOLARIS_THREADS) || defined(IRIX_THREADS) || \
+ defined(LINUX_THREADS)
+extern void GC_thr_init(); /* Needed for Solaris/X86 */
+#endif
/*
* If you are planning on putting
Index: boehm_gc/gc_priv.h
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/gc_priv.h,v
retrieving revision 1.7
diff -u -r1.7 gc_priv.h
--- gc_priv.h 1997/11/21 11:07:39 1.7
+++ gc_priv.h 1998/05/02 10:08:49
@@ -416,12 +416,58 @@
# define LOCK() mutex_lock(&GC_allocate_ml);
# define UNLOCK() mutex_unlock(&GC_allocate_ml);
# endif
+# ifdef LINUX_THREADS
+# include <pthread.h>
+#ifdef __i386__
+ extern inline int GC_test_and_set(volatile unsigned int *addr) {
+ int oldval;
+ /* Note: the "xchg" instruction does not need a "lock" prefix */
+ __asm__ __volatile__("xchgl %0, %1"
+ : "=r"(oldval), "=m"(*(addr))
+ : "0"(1), "m"(*(addr)));
+ return oldval;
+ }
+#else
+# error "Need implementation of GC_test_and_set()"
+#endif
+# define GC_clear(addr) (*(addr) = 0)
+
+ extern volatile unsigned int GC_allocate_lock;
+ /* This is not a mutex because mutexes that obey the (optional) */
+ /* POSIX scheduling rules are subject to convoys in high contention */
+ /* applications. This is basically a spin lock. */
+ extern pthread_t GC_lock_holder;
+ extern void GC_lock(void);
+ /* Allocation lock holder. Only set if acquired by client through */
+ /* GC_call_with_alloc_lock. */
+# define SET_LOCK_HOLDER() GC_lock_holder = pthread_self()
+# define NO_THREAD (pthread_t)(-1)
+# define UNSET_LOCK_HOLDER() GC_lock_holder = NO_THREAD
+# define I_HOLD_LOCK() (pthread_equal(GC_lock_holder, pthread_self()))
+# ifdef UNDEFINED
+# define LOCK() pthread_mutex_lock(&GC_allocate_ml)
+# define UNLOCK() pthread_mutex_unlock(&GC_allocate_ml)
+# else
+# define LOCK() \
+ { if (GC_test_and_set(&GC_allocate_lock)) GC_lock(); }
+# define UNLOCK() \
+ GC_clear(&GC_allocate_lock)
+# endif
+ extern bool GC_collecting;
+# define ENTER_GC() \
+ { \
+ GC_collecting = 1; \
+ }
+# define EXIT_GC() GC_collecting = 0;
+# endif /* LINUX_THREADS */
# ifdef IRIX_THREADS
# include <pthread.h>
# include <mutex.h>
# if __mips < 3 || !(defined (_ABIN32) || defined(_ABI64))
-# define __test_and_set(l,v) test_and_set(l,v)
+# define GC_test_and_set(addr) test_and_set(addr,1)
+# else
+# define GC_test_and_set(addr) __test_and_set(addr,1)
# endif
extern unsigned long GC_allocate_lock;
/* This is not a mutex because mutexes that obey the (optional) */
@@ -439,7 +485,7 @@
# define LOCK() pthread_mutex_lock(&GC_allocate_ml)
# define UNLOCK() pthread_mutex_unlock(&GC_allocate_ml)
# else
-# define LOCK() { if (__test_and_set(&GC_allocate_lock, 1)) GC_lock(); }
+# define LOCK() { if (GC_test_and_set(&GC_allocate_lock, 1)) GC_lock(); }
# if __mips >= 3 && (defined (_ABIN32) || defined(_ABI64))
# define UNLOCK() __lock_release(&GC_allocate_lock)
# else
@@ -452,7 +498,7 @@
GC_collecting = 1; \
}
# define EXIT_GC() GC_collecting = 0;
-# endif
+# endif /* IRIX_THREADS */
# ifdef WIN32_THREADS
# include <windows.h>
GC_API CRITICAL_SECTION GC_allocate_ml;
@@ -505,7 +551,8 @@
# else
# if defined(SRC_M3) || defined(AMIGA) || defined(SOLARIS_THREADS) \
|| defined(MSWIN32) || defined(MACOS) || defined(DJGPP) \
- || defined(NO_SIGNALS) || defined(IRIX_THREADS)
+ || defined(NO_SIGNALS) || defined(IRIX_THREADS) \
+ || defined(LINUX_THREADS)
/* Also useful for debugging. */
/* Should probably use thr_sigsetmask for SOLARIS_THREADS. */
# define DISABLE_SIGNALS()
@@ -532,7 +579,8 @@
PCR_allSigsBlocked, \
PCR_waitForever);
# else
-# if defined(SOLARIS_THREADS) || defined(WIN32_THREADS) || defined(IRIX_THREADS)
+# if defined(SOLARIS_THREADS) || defined(WIN32_THREADS) \
+ || defined(IRIX_THREADS) || defined(LINUX_THREADS)
void GC_stop_world();
void GC_start_world();
# define STOP_WORLD() GC_stop_world()
@@ -1129,6 +1177,8 @@
&= ~((word)1 << modWORDSZ(n))
/* Important internal collector routines */
+
+ptr_t GC_approx_sp();
void GC_apply_to_all_blocks(/*fn, client_data*/);
/* Invoke fn(hbp, client_data) for each */
Index: boehm_gc/irix_threads.c
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/irix_threads.c,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 irix_threads.c
--- irix_threads.c 1997/11/21 08:44:43 1.1.1.2
+++ irix_threads.c 1998/05/20 15:23:13
@@ -15,6 +15,10 @@
* 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.
+ *
+ * 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)
@@ -290,7 +294,7 @@
case 0:
break;
default:
- ABORT("thr_kill failed");
+ ABORT("pthread_kill failed");
}
}
}
@@ -336,8 +340,6 @@
}
# endif
-extern ptr_t GC_approx_sp();
-
/* We hold allocation lock. We assume the world is stopped. */
void GC_push_all_stacks()
{
@@ -536,7 +538,7 @@
# define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
int i;
- if (!__test_and_set(&GC_allocate_lock, 1)) {
+ if (!GC_test_and_set(&GC_allocate_lock, 1)) {
return;
}
my_spin_max = spin_max;
@@ -547,7 +549,7 @@
PAUSE;
continue;
}
- if (!__test_and_set(&GC_allocate_lock, 1)) {
+ if (!GC_test_and_set(&GC_allocate_lock, 1)) {
/*
* got it!
* Spinning worked. Thus we're probably not being scheduled
@@ -563,7 +565,7 @@
spin_max = low_spin_max;
yield:
for (;;) {
- if (!__test_and_set(&GC_allocate_lock, 1)) {
+ if (!GC_test_and_set(&GC_allocate_lock, 1)) {
return;
}
sched_yield();
Index: boehm_gc/linux_threads.c
===================================================================
RCS file: linux_threads.c
diff -N linux_threads.c
--- /dev/null Wed May 28 10:49:58 1997
+++ linux_threads.c Thu May 21 01:22:09 1998
@@ -0,0 +1,659 @@
+/*
+ * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
+ * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
+ * Copyright (c) 1998 by Fergus Henderson. 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 LinuxThreads, the clone()-based kernel
+ * thread package for Linux which is included in libc6.
+ *
+ * This code relies on implementation details of LinuxThreads,
+ * (i.e. properties not guaranteed by the Pthread standard):
+ *
+ * - it uses `kill(SIGSTOP, getpid())' to suspend the
+ * current thread. According to POSIX, this should
+ * stop the whole process, not just the thread.
+ * (An alternative would be to use
+ * `pthread_kill(SIGSTOP, pthread_self())' instead,
+ * but we need to do it inside a signal handler,
+ * and the pthread_kill() implementation in LinuxThreads
+ * is not async-signal-safe.)
+ *
+ * - the function GC_linux_thread_top_of_stack(void)
+ * relies on the way LinuxThreads lays out thread stacks
+ * in the address space.
+ *
+ * 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(LINUX_THREADS)
+
+# include "gc_priv.h"
+# include <pthread.h>
+# include <time.h>
+# include <errno.h>
+# include <unistd.h>
+# include <sys/mman.h>
+# include <sys/time.h>
+# include <semaphore.h>
+
+#undef pthread_create
+#undef pthread_sigmask
+#undef pthread_join
+
+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 flags;
+# define FINISHED 1 /* Thread has exited. */
+# define DETACHED 2 /* Thread is intended to be detached. */
+# define MAIN_THREAD 4 /* True for the original thread only. */
+
+ ptr_t stack_end;
+ ptr_t stack_ptr; /* Valid only when stopped. */
+ int signal;
+ void * status; /* The value returned from the thread. */
+ /* 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. We can't use SIGSTOP directly, because we need to get the
+ * thread to save its stack pointer in the GC thread table before
+ * suspending. So we have to reserve a signal of our own for this.
+ * This means we have to intercept client calls to change the signal mask.
+ * The linuxthreads package already uses SIGUSR1 and SIGUSR2,
+ * so we need to reuse something else. I chose SIGPWR.
+ * (Perhaps SIGUNUSED would be a better choice.)
+ */
+#define SIG_SUSPEND SIGPWR
+
+#define SIG_RESTART SIGCONT
+
+sem_t GC_suspend_ack_sem;
+
+/*
+GC_linux_thread_top_of_stack() relies on implementation details of
+LinuxThreads, namely that thread stacks are allocated on 2M boundaries
+and grow to no more than 2M.
+To make sure that we're using LinuxThreads and not some other thread
+package, we generate a dummy reference to `__pthread_initial_thread_bos',
+which is a symbol defined in LinuxThreads, but (hopefully) not in other
+thread packages.
+*/
+extern char * __pthread_initial_thread_bos;
+char **dummy_var_to_force_linux_threads = &__pthread_initial_thread_bos;
+
+#define LINUX_THREADS_STACK_SIZE (2 * 1024 * 1024)
+
+static inline ptr_t GC_linux_thread_top_of_stack(void)
+{
+ char *sp = GC_approx_sp();
+ ptr_t tos = (ptr_t) (((unsigned long)sp | (LINUX_THREADS_STACK_SIZE - 1)) + 1);
+#if DEBUG_THREADS
+ GC_printf1("SP = %lx\n", (unsigned long)sp);
+ GC_printf1("TOS = %lx\n", (unsigned long)tos);
+#endif
+ return tos;
+}
+
+void GC_suspend_handler(int sig)
+{
+ int dummy;
+ pthread_t my_thread = pthread_self();
+ GC_thread me;
+ sigset_t all_sigs;
+ sigset_t old_sigs;
+ int i;
+ sigset_t mask;
+
+ if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
+
+#if DEBUG_THREADS
+ GC_printf1("Suspending 0x%x\n", my_thread);
+#endif
+
+ me = GC_lookup_thread(my_thread);
+ /* 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. */
+ me -> stack_ptr = (ptr_t)(&dummy);
+ me -> stack_end = GC_linux_thread_top_of_stack();
+
+ /* Tell the thread that wants to stop the world that this */
+ /* thread has been stopped. Note that sem_post() is */
+ /* the only async-signal-safe primitive in LinuxThreads. */
+ sem_post(&GC_suspend_ack_sem);
+
+ /* Wait until that thread tells us to restart by sending */
+ /* this thread a SIG_RESTART signal. */
+ if (sigfillset(&mask) != 0) ABORT("sigfillset() failed");
+ if (sigdelset(&mask, SIG_RESTART) != 0) ABORT("sigdelset() failed");
+ if (sigdelset(&mask, SIG_SUSPEND) != 0) ABORT("sigdelset() failed");
+ do {
+ me->signal = 0;
+ sigsuspend(&mask); /* Wait for signal */
+ } while (me->signal != SIG_RESTART);
+
+#if DEBUG_THREADS
+ GC_printf1("Continuing 0x%x\n", my_thread);
+#endif
+}
+
+void GC_restart_handler(int sig)
+{
+ GC_thread me;
+
+ if (sig != SIG_RESTART) ABORT("Bad signal in suspend_handler");
+
+ /* Let the GC_suspend_handler() know that we got a SIG_RESTART. */
+ /* 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. */
+ me = GC_lookup_thread(pthread_self());
+ me->signal = SIG_RESTART;
+
+ /*
+ ** Note: even if we didn't do anything useful here,
+ ** it would still be necessary to have a signal handler,
+ ** rather than ignoring the signals, otherwise
+ ** the signals will not be delivered at all, and
+ ** will thus not interrupt the sigsuspend() above.
+ */
+
+#if DEBUG_THREADS
+ GC_printf1("In GC_restart_handler for 0x%x\n", pthread_self());
+#endif
+}
+
+bool GC_thr_initialized = FALSE;
+
+# define THREAD_TABLE_SZ 128 /* Must be power of 2 */
+volatile GC_thread GC_threads[THREAD_TABLE_SZ];
+
+/* 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 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_generic_malloc_inner(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; */
+ 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);
+}
+
+extern volatile int volatile_counter;
+volatile int prev_counter;
+
+/* Caller holds allocation lock. */
+void GC_stop_world()
+{
+ pthread_t my_thread = pthread_self();
+ register int i;
+ register GC_thread p;
+ register int n_live_threads = 0;
+ register int result;
+
+ /*
+ * It is important to ensure that any threads which were
+ * previously stopped and then woken get time to actually
+ * wake up before we stop then again. Otherwise,
+ * we might try to suspend a process that is already
+ * stopped, and I think that might not work properly.
+ * Hence the following call to sched_yield().
+ */
+ sched_yield();
+
+ 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) continue;
+ n_live_threads++;
+ #if DEBUG_THREADS
+ GC_printf1("Sending suspend signal to 0x%x\n", p -> id);
+ #endif
+ result = pthread_kill(p -> id, SIG_SUSPEND);
+ switch(result) {
+ case ESRCH:
+ /* Not really there anymore. Possible? */
+ n_live_threads--;
+ break;
+ case 0:
+ break;
+ default:
+ ABORT("pthread_kill failed");
+ }
+ }
+ }
+ }
+ for (i = 0; i < n_live_threads; i++) {
+ sem_wait(&GC_suspend_ack_sem);
+ }
+ #if DEBUG_THREADS
+ GC_printf1("World stopped 0x%x\n", pthread_self());
+ #endif
+ prev_counter = volatile_counter;
+}
+
+/* Caller holds allocation lock. */
+void GC_start_world()
+{
+ pthread_t my_thread = pthread_self();
+ register int i;
+ register GC_thread p;
+ register int n_live_threads = 0;
+ register int result;
+
+ if (volatile_counter != prev_counter) {
+ ABORT("GC_stop_world didn't stop everything");
+ }
+ #if DEBUG_THREADS
+ GC_printf0("World starting\n");
+ #endif
+
+ 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) continue;
+ n_live_threads++;
+ #if DEBUG_THREADS
+ GC_printf1("Sending restart signal to 0x%x\n", p -> id);
+ #endif
+ result = pthread_kill(p -> id, SIG_RESTART);
+ switch(result) {
+ case ESRCH:
+ /* Not really there anymore. Possible? */
+ n_live_threads--;
+ break;
+ case 0:
+ break;
+ default:
+ ABORT("pthread_kill failed");
+ }
+ }
+ }
+ }
+ #if DEBUG_THREADS
+ GC_printf0("World started\n");
+ #endif
+}
+
+/* We hold allocation lock. We assume the world is stopped. */
+void GC_push_all_stacks()
+{
+ register int i;
+ register GC_thread p;
+ register ptr_t sp = GC_approx_sp();
+ register ptr_t lo, hi;
+ pthread_t me = pthread_self();
+
+ if (!GC_thr_initialized) GC_thr_init();
+ #if DEBUG_THREADS
+ GC_printf1("Pushing stacks from thread 0x%lx\n", (unsigned long) me);
+ #endif
+ 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)) {
+ lo = GC_approx_sp();
+ } else {
+ lo = p -> stack_ptr;
+ }
+ if ((p -> flags & MAIN_THREAD) == 0) {
+ if (pthread_equal(p -> id, me)) {
+ hi = GC_linux_thread_top_of_stack();
+ } else {
+ hi = p -> stack_end;
+ }
+ } else {
+ /* The original stack. */
+ hi = GC_stackbottom;
+ }
+ #if DEBUG_THREADS
+ GC_printf3("Stack for thread 0x%lx = [%lx,%lx)\n",
+ (unsigned long) p -> id,
+ (unsigned long) lo, (unsigned long) hi);
+ #endif
+ GC_push_all_stack(lo, hi);
+ }
+ }
+}
+
+
+/* We hold the allocation lock. */
+void GC_thr_init()
+{
+ GC_thread t;
+ struct sigaction act;
+
+ GC_thr_initialized = TRUE;
+
+ if (sem_init(&GC_suspend_ack_sem, 0, 0) != 0)
+ ABORT("sem_init failed");
+
+ act.sa_flags = SA_RESTART;
+ if (sigfillset(&act.sa_mask) != 0) {
+ ABORT("sigfillset() failed");
+ }
+ if (sigdelset(&act.sa_mask, SIG_RESTART) != 0) {
+ ABORT("sigdelset() failed");
+ }
+ act.sa_handler = GC_suspend_handler;
+ if (sigaction(SIG_SUSPEND, &act, NULL) != 0) {
+ ABORT("Cannot set SIG_SUSPEND handler");
+ }
+
+ act.sa_flags = SA_RESTART;
+ if (sigfillset(&act.sa_mask) != 0) {
+ ABORT("sigfillset() failed");
+ }
+ if (sigdelset(&act.sa_mask, SIG_RESTART) != 0) {
+ ABORT("sigdelset() failed");
+ }
+ act.sa_handler = GC_restart_handler;
+ if (sigaction(SIG_RESTART, &act, NULL) != 0) {
+ ABORT("Cannot set SIG_SUSPEND handler");
+ }
+
+ /* Add the initial thread, so we can stop it. */
+ t = GC_new_thread(pthread_self());
+ t -> stack_ptr = (ptr_t)(&t);
+ t -> flags = DETACHED | MAIN_THREAD;
+}
+
+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;
+};
+
+void GC_thread_exit_proc(void *dummy)
+{
+ 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);
+ LOCK();
+ /* Here the pthread thread id may have been recycled. */
+ 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;
+
+ LOCK();
+ me = GC_lookup_thread(pthread_self());
+ UNLOCK();
+ pthread_cleanup_push(GC_thread_exit_proc, 0);
+# ifdef DEBUG_THREADS
+ GC_printf1("Starting thread 0x%x\n", pthread_self());
+ GC_printf1("pid = %ld\n", (long) getpid());
+ GC_printf1("sp = 0x%lx\n", (long) &arg);
+# endif
+ result = (*(si -> start_routine))(si -> arg);
+#if DEBUG_THREADS
+ GC_printf1("Finishing thread 0x%x\n", pthread_self());
+#endif
+ 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);
+}
+
+int
+GC_pthread_create(pthread_t *new_thread,
+ const pthread_attr_t *attr,
+ void *(*start_routine)(void *), void *arg)
+{
+ 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));
+
+ if (0 == si) return(ENOMEM);
+ si -> start_routine = start_routine;
+ si -> arg = arg;
+ LOCK();
+ if (!GC_thr_initialized) GC_thr_init();
+ if (NULL == attr) {
+ stack = 0;
+ (void) pthread_attr_init(&new_attr);
+ } else {
+ new_attr = *attr;
+ }
+ pthread_attr_getdetachstate(&new_attr, &detachstate);
+ 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_ptr = 0;
+ t -> stack_end = 0;
+ if (0 != new_thread) *new_thread = my_new_thread;
+ }
+ UNLOCK();
+ /* pthread_attr_destroy(&new_attr); */
+ return(result);
+}
+
+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 ... */
+
+volatile unsigned int GC_allocate_lock = 0;
+
+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;
+ unsigned junk;
+# define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
+ int i;
+
+ if (!GC_test_and_set(&GC_allocate_lock)) {
+ return;
+ }
+ 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_allocate_lock) {
+ PAUSE;
+ continue;
+ }
+ if (!GC_test_and_set(&GC_allocate_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 (;;) {
+ if (!GC_test_and_set(&GC_allocate_lock)) {
+ return;
+ }
+ sched_yield();
+ }
+}
+
+# endif /* LINUX_THREADS */
+
Index: boehm_gc/misc.c
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/misc.c,v
retrieving revision 1.7
diff -u -r1.7 misc.c
--- misc.c 1997/11/21 11:07:44 1.7
+++ misc.c 1998/05/20 14:14:41
@@ -42,7 +42,7 @@
# ifdef WIN32_THREADS
GC_API CRITICAL_SECTION GC_allocate_ml;
# else
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
# ifdef UNDEFINED
pthread_mutex_t GC_allocate_ml = PTHREAD_MUTEX_INITIALIZER;
# endif
@@ -234,8 +234,6 @@
}
#endif
-extern ptr_t GC_approx_sp(); /* in mark_rts.c */
-
/* Clear some of the inaccessible part of the stack. Returns its */
/* argument, so it can be used in a tail call position, hence clearing */
/* another frame. */
@@ -397,10 +395,6 @@
bool GC_is_initialized = FALSE;
-#if defined(SOLARIS_THREADS) || defined(IRIX_THREADS)
- extern void GC_thr_init();
-#endif
-
void GC_init()
{
DCL_LOCK_STATE;
@@ -439,10 +433,11 @@
/* We need dirty bits in order to find live stack sections. */
GC_dirty_init();
# endif
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
GC_thr_init();
# endif
-# if !defined(THREADS) || defined(SOLARIS_THREADS) || defined(WIN32_THREADS) || defined(IRIX_THREADS)
+# if !defined(THREADS) || defined(SOLARIS_THREADS) || defined(WIN32_THREADS) \
+ || defined(IRIX_THREADS) || defined(LINUX_THREADS)
if (GC_stackbottom == 0) {
GC_stackbottom = GC_get_stack_base();
}
Index: boehm_gc/os_dep.c
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/os_dep.c,v
retrieving revision 1.12
diff -u -r1.12 os_dep.c
--- os_dep.c 1998/02/11 03:57:56 1.12
+++ os_dep.c 1998/05/02 10:09:38
@@ -1211,7 +1211,8 @@
# endif /* SRC_M3 */
-# if defined(SOLARIS_THREADS) || defined(WIN32_THREADS) || defined(IRIX_THREADS)
+# if defined(SOLARIS_THREADS) || defined(WIN32_THREADS) \
+ || defined(IRIX_THREADS) || defined(LINUX_THREADS)
extern void GC_push_all_stacks();
Index: boehm_gc/test.c
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/test.c,v
retrieving revision 1.7
diff -u -r1.7 test.c
--- test.c 1997/11/21 11:07:49 1.7
+++ test.c 1998/05/20 15:08:11
@@ -45,7 +45,7 @@
# include <synch.h>
# endif
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
# include <pthread.h>
# endif
@@ -386,7 +386,7 @@
static mutex_t incr_lock;
mutex_lock(&incr_lock);
# endif
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
static pthread_mutex_t incr_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&incr_lock);
# endif
@@ -404,7 +404,7 @@
# ifdef SOLARIS_THREADS
mutex_unlock(&incr_lock);
# endif
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
pthread_mutex_unlock(&incr_lock);
# endif
# ifdef WIN32_THREADS
@@ -465,7 +465,7 @@
static mutex_t incr_lock;
mutex_lock(&incr_lock);
# endif
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
static pthread_mutex_t incr_lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&incr_lock);
# endif
@@ -481,7 +481,7 @@
# ifdef SOLARIS_THREADS
mutex_unlock(&incr_lock);
# endif
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
pthread_mutex_unlock(&incr_lock);
# endif
# ifdef WIN32_THREADS
@@ -914,7 +914,9 @@
}
-#if !defined(PCR) && !defined(SOLARIS_THREADS) && !defined(WIN32_THREADS) && !defined(IRIX_THREADS) || defined(LINT)
+#if !defined(PCR) && !defined(SOLARIS_THREADS) && !defined(WIN32_THREADS) \
+ && !defined(IRIX_THREADS) && !defined(LINUX_THREADS) \
+ || defined(LINT)
#ifdef MSWIN32
int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prev, LPSTR cmd, int n)
#else
@@ -1042,7 +1044,7 @@
}
#endif
-#if defined(SOLARIS_THREADS) || defined(IRIX_THREADS)
+#if defined(SOLARIS_THREADS) || defined(IRIX_THREADS) || defined(LINUX_THREADS)
void * thr_run_one_test(void * arg)
{
run_one_test();
@@ -1103,7 +1105,9 @@
*((char *)&code - 1024*1024) = 0; /* Require 1 Mb */
# endif /* IRIX_THREADS */
pthread_attr_init(&attr);
- pthread_attr_setstacksize(&attr, 1000000);
+# ifdef IRIX_THREADS
+ pthread_attr_setstacksize(&attr, 1000000);
+# endif
n_tests = 0;
GC_enable_incremental();
(void) GC_set_warn_proc(warn_proc);
@@ -1131,4 +1135,4 @@
return(0);
}
#endif /* pthreads */
-#endif /* SOLARIS_THREADS || IRIX_THREADS */
+#endif /* SOLARIS_THREADS || IRIX_THREADS || LINUX_THREADS */
Index: boehm_gc/threadlibs.c
===================================================================
RCS file: /home/staff/zs/imp/mercury/boehm_gc/threadlibs.c,v
retrieving revision 1.1.1.1
diff -u -r1.1.1.1 threadlibs.c
--- threadlibs.c 1996/12/06 10:54:11 1.1.1.1
+++ threadlibs.c 1998/05/02 11:53:08
@@ -3,7 +3,7 @@
int main()
{
-# ifdef IRIX_THREADS
+# if defined(IRIX_THREADS) || defined(LINUX_THREADS)
printf("-lpthread\n");
# endif
# ifdef SOLARIS_THREADS
--
Fergus Henderson <fjh at cs.mu.oz.au> | "I have always known that the pursuit
WWW: <http://www.cs.mu.oz.au/~fjh> | of excellence is a lethal habit"
PGP: finger fjh at 128.250.37.3 | -- the last words of T. S. Garp.
More information about the developers
mailing list