mirror of git://gcc.gnu.org/git/gcc.git
parent
b4acb5ef4b
commit
6991c6c926
|
@ -0,0 +1,693 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved.
|
||||||
|
* Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved.
|
||||||
|
* Copyright (c) 1999-2003 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 and for AIX 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 this file and
|
||||||
|
* (pthread_support.c, pthread_stop_world.c). They should be merged.
|
||||||
|
* Pthread_support.c should be directly usable.
|
||||||
|
*
|
||||||
|
* Please avoid adding new ports here; use the generic pthread support
|
||||||
|
* as a base instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
# if defined(GC_IRIX_THREADS) || defined(GC_AIX_THREADS)
|
||||||
|
|
||||||
|
# include "private/gc_priv.h"
|
||||||
|
# include <pthread.h>
|
||||||
|
# include <assert.h>
|
||||||
|
# include <semaphore.h>
|
||||||
|
# include <time.h>
|
||||||
|
# include <errno.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
# include <sys/mman.h>
|
||||||
|
# include <sys/time.h>
|
||||||
|
|
||||||
|
#undef pthread_create
|
||||||
|
#undef pthread_sigmask
|
||||||
|
#undef pthread_join
|
||||||
|
|
||||||
|
#if defined(GC_IRIX_THREADS) && !defined(MUTEX_RECURSIVE_NP)
|
||||||
|
#define MUTEX_RECURSIVE_NP PTHREAD_MUTEX_RECURSIVE
|
||||||
|
#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. */
|
||||||
|
ptr_t stack_cold; /* cold end of the stack */
|
||||||
|
ptr_t stack_hot; /* Valid only when stopped. */
|
||||||
|
/* But must be within stack region at */
|
||||||
|
/* all times. */
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
#if 0 /* DOB: 6.1 */
|
||||||
|
# if defined(GC_AIX_THREADS)
|
||||||
|
# define SIG_SUSPEND SIGUSR1
|
||||||
|
# else
|
||||||
|
# define SIG_SUSPEND (SIGRTMIN + 6)
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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_hot = (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;
|
||||||
|
|
||||||
|
|
||||||
|
# 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;
|
||||||
|
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
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; */
|
||||||
|
/* 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. */
|
||||||
|
/* We explicitly pass in the GC_thread we're looking for, since */
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
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];
|
||||||
|
|
||||||
|
/* I either hold the lock, or i'm being called from the stop-the-world
|
||||||
|
* handler. */
|
||||||
|
#if defined(GC_AIX_THREADS)
|
||||||
|
GC_ASSERT(I_HOLD_LOCK()); /* no stop-the-world handler needed on AIX */
|
||||||
|
#endif
|
||||||
|
while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
|
||||||
|
return(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(GC_AIX_THREADS)
|
||||||
|
void GC_stop_world()
|
||||||
|
{
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
register int i;
|
||||||
|
register GC_thread p;
|
||||||
|
register int result;
|
||||||
|
struct timespec timeout;
|
||||||
|
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||||
|
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||||
|
if (p -> id != my_thread) {
|
||||||
|
pthread_suspend_np(p->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* GC_printf1("World stopped 0x%x\n", pthread_self()); */
|
||||||
|
}
|
||||||
|
|
||||||
|
void GC_start_world()
|
||||||
|
{
|
||||||
|
GC_thread p;
|
||||||
|
unsigned i;
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
|
||||||
|
/* GC_printf0("World starting\n"); */
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||||
|
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||||
|
if (p -> id != my_thread) {
|
||||||
|
pthread_continue_np(p->id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* GC_AIX_THREADS */
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
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"); */
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* GC_AIX_THREADS */
|
||||||
|
|
||||||
|
|
||||||
|
/* 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 hot, cold;
|
||||||
|
pthread_t me = pthread_self();
|
||||||
|
|
||||||
|
/* GC_init() should have been called before GC_push_all_stacks is
|
||||||
|
* invoked, and GC_init calls GC_thr_init(), which sets
|
||||||
|
* GC_thr_initialized. */
|
||||||
|
GC_ASSERT(GC_thr_initialized);
|
||||||
|
|
||||||
|
/* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||||
|
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||||
|
if (p -> flags & FINISHED) continue;
|
||||||
|
cold = p->stack_cold;
|
||||||
|
if (!cold) cold=GC_stackbottom; /* 0 indicates 'original stack' */
|
||||||
|
if (pthread_equal(p -> id, me)) {
|
||||||
|
hot = GC_approx_sp();
|
||||||
|
} else {
|
||||||
|
# ifdef GC_AIX_THREADS
|
||||||
|
/* AIX doesn't use signals to suspend, so we need to get an */
|
||||||
|
/* accurate hot stack pointer. */
|
||||||
|
/* See http://publib16.boulder.ibm.com/pseries/en_US/libs/basetrf1/pthread_getthrds_np.htm */
|
||||||
|
pthread_t id = p -> id;
|
||||||
|
struct __pthrdsinfo pinfo;
|
||||||
|
int regbuf[64];
|
||||||
|
int val = sizeof(regbuf);
|
||||||
|
int retval = pthread_getthrds_np(&id, PTHRDSINFO_QUERY_ALL, &pinfo,
|
||||||
|
sizeof(pinfo), regbuf, &val);
|
||||||
|
if (retval != 0) {
|
||||||
|
printf("ERROR: pthread_getthrds_np() failed in GC\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
/* according to the AIX ABI,
|
||||||
|
"the lowest possible valid stack address is 288 bytes (144 + 144)
|
||||||
|
less than the current value of the stack pointer. Functions may
|
||||||
|
use this stack space as volatile storage which is not preserved
|
||||||
|
across function calls."
|
||||||
|
ftp://ftp.penguinppc64.org/pub/people/amodra/PPC-elf64abi.txt.gz
|
||||||
|
*/
|
||||||
|
hot = (ptr_t)(unsigned long)pinfo.__pi_ustk-288;
|
||||||
|
cold = (ptr_t)pinfo.__pi_stackend; /* more precise */
|
||||||
|
/* push the registers too, because they won't be on stack */
|
||||||
|
GC_push_all_eager((ptr_t)&pinfo.__pi_context,
|
||||||
|
(ptr_t)((&pinfo.__pi_context)+1));
|
||||||
|
GC_push_all_eager((ptr_t)regbuf, ((ptr_t)regbuf)+val);
|
||||||
|
# else
|
||||||
|
hot = p -> stack_hot;
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
# ifdef STACK_GROWS_UP
|
||||||
|
GC_push_all_stack(cold, hot);
|
||||||
|
# else
|
||||||
|
/* printf("thread 0x%x: hot=0x%08x cold=0x%08x\n", p -> id, hot, cold); */
|
||||||
|
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;
|
||||||
|
#if 0
|
||||||
|
/* unfortunately, GC_init_inner calls us without the lock, so
|
||||||
|
* this assertion is not always true. */
|
||||||
|
/* Why doesn't GC_init_inner hold the lock? - HB */
|
||||||
|
GC_ASSERT(I_HOLD_LOCK());
|
||||||
|
#endif
|
||||||
|
GC_thr_initialized = TRUE;
|
||||||
|
#ifndef GC_AIX_THREADS
|
||||||
|
(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");
|
||||||
|
#endif
|
||||||
|
/* Add the initial thread, so we can stop it. */
|
||||||
|
t = GC_new_thread(pthread_self());
|
||||||
|
/* use '0' to indicate GC_stackbottom, since GC_init() has not
|
||||||
|
* completed by the time we are called (from GC_init_inner()) */
|
||||||
|
t -> stack_cold = 0; /* the original stack. */
|
||||||
|
t -> stack_hot = (ptr_t)(&t);
|
||||||
|
t -> flags = DETACHED;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
|
||||||
|
{
|
||||||
|
sigset_t fudged_set;
|
||||||
|
|
||||||
|
#ifdef GC_AIX_THREADS
|
||||||
|
return(pthread_sigmask(how, set, oset));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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;
|
||||||
|
pthread_mutex_t registeredlock;
|
||||||
|
pthread_cond_t registered;
|
||||||
|
int volatile registereddone;
|
||||||
|
};
|
||||||
|
|
||||||
|
void GC_thread_exit_proc(void *arg)
|
||||||
|
{
|
||||||
|
GC_thread me;
|
||||||
|
|
||||||
|
LOCK();
|
||||||
|
me = GC_lookup_thread(pthread_self());
|
||||||
|
me -> flags |= FINISHED;
|
||||||
|
/* reclaim DETACHED thread right away; otherwise wait until join() */
|
||||||
|
if (me -> flags & DETACHED) {
|
||||||
|
GC_delete_gc_thread(pthread_self(), me);
|
||||||
|
}
|
||||||
|
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();
|
||||||
|
GC_ASSERT(!(thread_gc_id->flags & DETACHED));
|
||||||
|
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;
|
||||||
|
GC_ASSERT(thread_gc_id->flags & FINISHED);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
int dummy;
|
||||||
|
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_cold = (ptr_t) &dummy; /* this now the 'start of stack' */
|
||||||
|
me -> stack_hot = me->stack_cold;/* this field should always be sensible */
|
||||||
|
UNLOCK();
|
||||||
|
start = si -> start_routine;
|
||||||
|
start_arg = si -> arg;
|
||||||
|
|
||||||
|
pthread_mutex_lock(&(si->registeredlock));
|
||||||
|
si->registereddone = 1;
|
||||||
|
pthread_cond_signal(&(si->registered));
|
||||||
|
pthread_mutex_unlock(&(si->registeredlock));
|
||||||
|
/* si went away as soon as we did this unlock */
|
||||||
|
|
||||||
|
pthread_cleanup_push(GC_thread_exit_proc, 0);
|
||||||
|
result = (*start)(start_arg);
|
||||||
|
me -> status = result;
|
||||||
|
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;
|
||||||
|
int detachstate;
|
||||||
|
word my_flags = 0;
|
||||||
|
struct start_info * si;
|
||||||
|
/* This is otherwise saved only in an area mmapped by the thread */
|
||||||
|
/* library, which isn't visible to the collector. */
|
||||||
|
|
||||||
|
LOCK();
|
||||||
|
/* GC_INTERNAL_MALLOC implicitly calls GC_init() if required */
|
||||||
|
si = (struct start_info *)GC_INTERNAL_MALLOC(sizeof(struct start_info),
|
||||||
|
NORMAL);
|
||||||
|
GC_ASSERT(GC_thr_initialized); /* initialized by GC_init() */
|
||||||
|
UNLOCK();
|
||||||
|
if (0 == si) return(ENOMEM);
|
||||||
|
pthread_mutex_init(&(si->registeredlock), NULL);
|
||||||
|
pthread_cond_init(&(si->registered),NULL);
|
||||||
|
pthread_mutex_lock(&(si->registeredlock));
|
||||||
|
si -> start_routine = start_routine;
|
||||||
|
si -> arg = arg;
|
||||||
|
|
||||||
|
pthread_attr_getdetachstate(attr, &detachstate);
|
||||||
|
if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
|
||||||
|
si -> flags = my_flags;
|
||||||
|
result = pthread_create(new_thread, attr, GC_start_routine, si);
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
|
|
||||||
|
if (0 == result) {
|
||||||
|
si->registereddone = 0;
|
||||||
|
while (!si->registereddone)
|
||||||
|
pthread_cond_wait(&(si->registered), &(si->registeredlock));
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&(si->registeredlock));
|
||||||
|
|
||||||
|
pthread_cond_destroy(&(si->registered));
|
||||||
|
pthread_mutex_destroy(&(si->registeredlock));
|
||||||
|
LOCK();
|
||||||
|
GC_INTERNAL_FREE(si);
|
||||||
|
UNLOCK();
|
||||||
|
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For now we use the pthreads locking primitives on HP/UX */
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
volatile unsigned int 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 /* !GC_IRIX_THREADS && !GC_AIX_THREADS */
|
||||||
|
|
||||||
|
#ifndef LINT
|
||||||
|
int GC_no_Irix_threads;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
# endif /* IRIX_THREADS */
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
# $Id: alpha_mach_dep.s,v 1.2 1993/01/18 22:54:51 dosser Exp $
|
||||||
|
.arch ev6
|
||||||
|
|
||||||
|
.text
|
||||||
|
.align 4
|
||||||
|
.globl GC_push_regs
|
||||||
|
.ent GC_push_regs 2
|
||||||
|
GC_push_regs:
|
||||||
|
ldgp $gp, 0($27)
|
||||||
|
lda $sp, -16($sp)
|
||||||
|
stq $26, 0($sp)
|
||||||
|
.mask 0x04000000, 0
|
||||||
|
.frame $sp, 16, $26, 0
|
||||||
|
|
||||||
|
# $0 integer result
|
||||||
|
# $1-$8 temp regs - not preserved cross calls
|
||||||
|
# $9-$15 call saved regs
|
||||||
|
# $16-$21 argument regs - not preserved cross calls
|
||||||
|
# $22-$28 temp regs - not preserved cross calls
|
||||||
|
# $29 global pointer - not preserved cross calls
|
||||||
|
# $30 stack pointer
|
||||||
|
|
||||||
|
# define call_push(x) \
|
||||||
|
mov x, $16; \
|
||||||
|
jsr $26, GC_push_one; \
|
||||||
|
ldgp $gp, 0($26)
|
||||||
|
|
||||||
|
call_push($9)
|
||||||
|
call_push($10)
|
||||||
|
call_push($11)
|
||||||
|
call_push($12)
|
||||||
|
call_push($13)
|
||||||
|
call_push($14)
|
||||||
|
call_push($15)
|
||||||
|
|
||||||
|
# $f0-$f1 floating point results
|
||||||
|
# $f2-$f9 call saved regs
|
||||||
|
# $f10-$f30 temp regs - not preserved cross calls
|
||||||
|
|
||||||
|
# Use the most efficient transfer method for this hardware.
|
||||||
|
# Bit 1 detects the FIX extension, which includes ftoit.
|
||||||
|
amask 2, $0
|
||||||
|
bne $0, $use_stack
|
||||||
|
|
||||||
|
#undef call_push
|
||||||
|
#define call_push(x) \
|
||||||
|
ftoit x, $16; \
|
||||||
|
jsr $26, GC_push_one; \
|
||||||
|
ldgp $gp, 0($26)
|
||||||
|
|
||||||
|
call_push($f2)
|
||||||
|
call_push($f3)
|
||||||
|
call_push($f4)
|
||||||
|
call_push($f5)
|
||||||
|
call_push($f6)
|
||||||
|
call_push($f7)
|
||||||
|
call_push($f8)
|
||||||
|
call_push($f9)
|
||||||
|
|
||||||
|
ldq $26, 0($sp)
|
||||||
|
lda $sp, 16($sp)
|
||||||
|
ret $31, ($26), 1
|
||||||
|
|
||||||
|
.align 4
|
||||||
|
$use_stack:
|
||||||
|
|
||||||
|
#undef call_push
|
||||||
|
#define call_push(x) \
|
||||||
|
stt x, 8($sp); \
|
||||||
|
ldq $16, 8($sp); \
|
||||||
|
jsr $26, GC_push_one; \
|
||||||
|
ldgp $gp, 0($26)
|
||||||
|
|
||||||
|
call_push($f2)
|
||||||
|
call_push($f3)
|
||||||
|
call_push($f4)
|
||||||
|
call_push($f5)
|
||||||
|
call_push($f6)
|
||||||
|
call_push($f7)
|
||||||
|
call_push($f8)
|
||||||
|
call_push($f9)
|
||||||
|
|
||||||
|
ldq $26, 0($sp)
|
||||||
|
lda $sp, 16($sp)
|
||||||
|
ret $31, ($26), 1
|
||||||
|
|
||||||
|
.end GC_push_regs
|
|
@ -0,0 +1,209 @@
|
||||||
|
#include "private/pthread_support.h"
|
||||||
|
|
||||||
|
# if defined(GC_DARWIN_THREADS)
|
||||||
|
|
||||||
|
#define DEBUG_THREADS 0
|
||||||
|
|
||||||
|
/* From "Inside Mac OS X - Mach-O Runtime Architecture" published by Apple
|
||||||
|
Page 49:
|
||||||
|
"The space beneath the stack pointer, where a new stack frame would normally
|
||||||
|
be allocated, is called the red zone. This area as shown in Figure 3-2 may
|
||||||
|
be used for any purpose as long as a new stack frame does not need to be
|
||||||
|
added to the stack."
|
||||||
|
|
||||||
|
Page 50: "If a leaf procedure's red zone usage would exceed 224 bytes, then
|
||||||
|
it must set up a stack frame just like routines that call other routines."
|
||||||
|
*/
|
||||||
|
#define PPC_RED_ZONE_SIZE 224
|
||||||
|
|
||||||
|
void GC_push_all_stacks() {
|
||||||
|
int i;
|
||||||
|
kern_return_t r;
|
||||||
|
GC_thread p;
|
||||||
|
pthread_t me;
|
||||||
|
ptr_t lo, hi;
|
||||||
|
# if defined(POWERPC)
|
||||||
|
ppc_thread_state_t state;
|
||||||
|
# else
|
||||||
|
# error FIXME for non-ppc OS X
|
||||||
|
# endif
|
||||||
|
mach_msg_type_number_t thread_state_count = MACHINE_THREAD_STATE_COUNT;
|
||||||
|
|
||||||
|
me = pthread_self();
|
||||||
|
if (!GC_thr_initialized) GC_thr_init();
|
||||||
|
|
||||||
|
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 {
|
||||||
|
/* Get the thread state (registers, etc) */
|
||||||
|
r = thread_get_state(
|
||||||
|
p->stop_info.mach_thread,
|
||||||
|
MACHINE_THREAD_STATE,
|
||||||
|
(natural_t*)&state,
|
||||||
|
&thread_state_count);
|
||||||
|
if(r != KERN_SUCCESS) ABORT("thread_get_state failed");
|
||||||
|
|
||||||
|
#ifdef POWERPC
|
||||||
|
lo = (void*)(state.r1 - PPC_RED_ZONE_SIZE);
|
||||||
|
|
||||||
|
GC_push_one(state.r0);
|
||||||
|
GC_push_one(state.r2);
|
||||||
|
GC_push_one(state.r3);
|
||||||
|
GC_push_one(state.r4);
|
||||||
|
GC_push_one(state.r5);
|
||||||
|
GC_push_one(state.r6);
|
||||||
|
GC_push_one(state.r7);
|
||||||
|
GC_push_one(state.r8);
|
||||||
|
GC_push_one(state.r9);
|
||||||
|
GC_push_one(state.r10);
|
||||||
|
GC_push_one(state.r11);
|
||||||
|
GC_push_one(state.r12);
|
||||||
|
GC_push_one(state.r13);
|
||||||
|
GC_push_one(state.r14);
|
||||||
|
GC_push_one(state.r15);
|
||||||
|
GC_push_one(state.r16);
|
||||||
|
GC_push_one(state.r17);
|
||||||
|
GC_push_one(state.r18);
|
||||||
|
GC_push_one(state.r19);
|
||||||
|
GC_push_one(state.r20);
|
||||||
|
GC_push_one(state.r21);
|
||||||
|
GC_push_one(state.r22);
|
||||||
|
GC_push_one(state.r23);
|
||||||
|
GC_push_one(state.r24);
|
||||||
|
GC_push_one(state.r25);
|
||||||
|
GC_push_one(state.r26);
|
||||||
|
GC_push_one(state.r27);
|
||||||
|
GC_push_one(state.r28);
|
||||||
|
GC_push_one(state.r29);
|
||||||
|
GC_push_one(state.r30);
|
||||||
|
GC_push_one(state.r31);
|
||||||
|
#else
|
||||||
|
# error FIXME for non-PPC darwin
|
||||||
|
#endif /* !POWERPC */
|
||||||
|
} /* p != me */
|
||||||
|
if(p->flags & MAIN_THREAD)
|
||||||
|
hi = GC_stackbottom;
|
||||||
|
else
|
||||||
|
hi = p->stack_end;
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf3("Darwin: 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);
|
||||||
|
} /* for(p=GC_threads[i]...) */
|
||||||
|
} /* for(i=0;i<THREAD_TABLE_SZ...) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caller holds allocation lock. */
|
||||||
|
void GC_stop_world()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
GC_thread p;
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
kern_return_t kern_result;
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Stopping the world from 0x%lx\n", pthread_self());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Make sure all free list construction has stopped before we start. */
|
||||||
|
/* No new construction can start, since free list construction is */
|
||||||
|
/* required to acquire and release the GC lock before it starts, */
|
||||||
|
/* and we have the lock. */
|
||||||
|
# ifdef PARALLEL_MARK
|
||||||
|
GC_acquire_mark_lock();
|
||||||
|
GC_ASSERT(GC_fl_builder_count == 0);
|
||||||
|
/* We should have previously waited for it to become zero. */
|
||||||
|
# endif /* PARALLEL_MARK */
|
||||||
|
|
||||||
|
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||||
|
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||||
|
if (p -> id == my_thread) continue;
|
||||||
|
if (p -> flags & FINISHED) continue;
|
||||||
|
if (p -> thread_blocked) /* Will wait */ continue;
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Suspending thread 0x%lx\n", p -> id);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Suspend the thread */
|
||||||
|
kern_result = thread_suspend(p->stop_info.mach_thread);
|
||||||
|
if(kern_result != KERN_SUCCESS) ABORT("thread_suspend failed");
|
||||||
|
|
||||||
|
/* This is only needed if we are modifying the threads
|
||||||
|
state. thread_abort_safely should also be used
|
||||||
|
if this code is ever added in again.
|
||||||
|
|
||||||
|
kern_result = thread_abort(p->stop_info.mach_thread);
|
||||||
|
if(kern_result != KERN_SUCCESS)
|
||||||
|
ABORT("thread_abort failed (%ul)",kern_result);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ifdef MPROTECT_VDB
|
||||||
|
if(GC_incremental) {
|
||||||
|
extern void GC_mprotect_stop();
|
||||||
|
GC_mprotect_stop();
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# ifdef PARALLEL_MARK
|
||||||
|
GC_release_mark_lock();
|
||||||
|
# endif
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("World stopped from 0x%lx\n", pthread_self());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caller holds allocation lock, and has held it continuously since */
|
||||||
|
/* the world stopped. */
|
||||||
|
void GC_start_world()
|
||||||
|
{
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
int i;
|
||||||
|
GC_thread p;
|
||||||
|
kern_return_t kern_result;
|
||||||
|
|
||||||
|
# if DEBUG_THREADS
|
||||||
|
GC_printf0("World starting\n");
|
||||||
|
# endif
|
||||||
|
|
||||||
|
# ifdef MPROTECT_VDB
|
||||||
|
if(GC_incremental) {
|
||||||
|
extern void GC_mprotect_resume();
|
||||||
|
GC_mprotect_resume();
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||||
|
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||||
|
if (p -> id == my_thread) continue;
|
||||||
|
if (p -> flags & FINISHED) continue;
|
||||||
|
if (p -> thread_blocked) continue;
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Resuming 0x%lx\n", p -> id);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Resume the thread */
|
||||||
|
kern_result = thread_resume(p->stop_info.mach_thread);
|
||||||
|
if(kern_result != KERN_SUCCESS) ABORT("thread_resume failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf0("World started\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GC_stop_init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,436 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# depcomp - compile a program generating dependencies as side-effects
|
||||||
|
# Copyright 1999, 2000 Free Software Foundation, Inc.
|
||||||
|
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||||
|
# 02111-1307, USA.
|
||||||
|
|
||||||
|
# As a special exception to the GNU General Public License, if you
|
||||||
|
# distribute this file as part of a program that contains a
|
||||||
|
# configuration script generated by Autoconf, you may include it under
|
||||||
|
# the same distribution terms that you use for the rest of that program.
|
||||||
|
|
||||||
|
# Originally written by Alexandre Oliva <oliva@dcc.unicamp.br>.
|
||||||
|
|
||||||
|
if test -z "$depmode" || test -z "$source" || test -z "$object"; then
|
||||||
|
echo "depcomp: Variables source, object and depmode must be set" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# `libtool' can also be set to `yes' or `no'.
|
||||||
|
|
||||||
|
if test -z "$depfile"; then
|
||||||
|
base=`echo "$object" | sed -e 's,^.*/,,' -e 's,\.\([^.]*\)$,.P\1,'`
|
||||||
|
dir=`echo "$object" | sed 's,/.*$,/,'`
|
||||||
|
if test "$dir" = "$object"; then
|
||||||
|
dir=
|
||||||
|
fi
|
||||||
|
# FIXME: should be _deps on DOS.
|
||||||
|
depfile="$dir.deps/$base"
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`}
|
||||||
|
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
|
||||||
|
# Some modes work just like other modes, but use different flags. We
|
||||||
|
# parameterize here, but still list the modes in the big case below,
|
||||||
|
# to make depend.m4 easier to write. Note that we *cannot* use a case
|
||||||
|
# here, because this file can only contain one case statement.
|
||||||
|
if test "$depmode" = hp; then
|
||||||
|
# HP compiler uses -M and no extra arg.
|
||||||
|
gccflag=-M
|
||||||
|
depmode=gcc
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test "$depmode" = dashXmstdout; then
|
||||||
|
# This is just like dashmstdout with a different argument.
|
||||||
|
dashmflag=-xM
|
||||||
|
depmode=dashmstdout
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$depmode" in
|
||||||
|
gcc3)
|
||||||
|
## gcc 3 implements dependency tracking that does exactly what
|
||||||
|
## we want. Yay! Note: for some reason libtool 1.4 doesn't like
|
||||||
|
## it if -MD -MP comes after the -MF stuff. Hmm.
|
||||||
|
"$@" -MT "$object" -MD -MP -MF "$tmpdepfile"
|
||||||
|
stat=$?
|
||||||
|
if test $stat -eq 0; then :
|
||||||
|
else
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
exit $stat
|
||||||
|
fi
|
||||||
|
mv "$tmpdepfile" "$depfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
gcc)
|
||||||
|
## There are various ways to get dependency output from gcc. Here's
|
||||||
|
## why we pick this rather obscure method:
|
||||||
|
## - Don't want to use -MD because we'd like the dependencies to end
|
||||||
|
## up in a subdir. Having to rename by hand is ugly.
|
||||||
|
## (We might end up doing this anyway to support other compilers.)
|
||||||
|
## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like
|
||||||
|
## -MM, not -M (despite what the docs say).
|
||||||
|
## - Using -M directly means running the compiler twice (even worse
|
||||||
|
## than renaming).
|
||||||
|
if test -z "$gccflag"; then
|
||||||
|
gccflag=-MD,
|
||||||
|
fi
|
||||||
|
"$@" -Wp,"$gccflag$tmpdepfile"
|
||||||
|
stat=$?
|
||||||
|
if test $stat -eq 0; then :
|
||||||
|
else
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
exit $stat
|
||||||
|
fi
|
||||||
|
rm -f "$depfile"
|
||||||
|
echo "$object : \\" > "$depfile"
|
||||||
|
alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
|
||||||
|
## The second -e expression handles DOS-style file names with drive letters.
|
||||||
|
sed -e 's/^[^:]*: / /' \
|
||||||
|
-e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile"
|
||||||
|
## This next piece of magic avoids the `deleted header file' problem.
|
||||||
|
## The problem is that when a header file which appears in a .P file
|
||||||
|
## is deleted, the dependency causes make to die (because there is
|
||||||
|
## typically no way to rebuild the header). We avoid this by adding
|
||||||
|
## dummy dependencies for each header file. Too bad gcc doesn't do
|
||||||
|
## this for us directly.
|
||||||
|
tr ' ' '
|
||||||
|
' < "$tmpdepfile" |
|
||||||
|
## Some versions of gcc put a space before the `:'. On the theory
|
||||||
|
## that the space means something, we add a space to the output as
|
||||||
|
## well.
|
||||||
|
## Some versions of the HPUX 10.20 sed can't process this invocation
|
||||||
|
## correctly. Breaking it into two sed invocations is a workaround.
|
||||||
|
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
hp)
|
||||||
|
# This case exists only to let depend.m4 do its work. It works by
|
||||||
|
# looking at the text of this script. This case will never be run,
|
||||||
|
# since it is checked for above.
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
sgi)
|
||||||
|
if test "$libtool" = yes; then
|
||||||
|
"$@" "-Wp,-MDupdate,$tmpdepfile"
|
||||||
|
else
|
||||||
|
"$@" -MDupdate "$tmpdepfile"
|
||||||
|
fi
|
||||||
|
stat=$?
|
||||||
|
if test $stat -eq 0; then :
|
||||||
|
else
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
exit $stat
|
||||||
|
fi
|
||||||
|
rm -f "$depfile"
|
||||||
|
|
||||||
|
if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files
|
||||||
|
echo "$object : \\" > "$depfile"
|
||||||
|
|
||||||
|
# Clip off the initial element (the dependent). Don't try to be
|
||||||
|
# clever and replace this with sed code, as IRIX sed won't handle
|
||||||
|
# lines with more than a fixed number of characters (4096 in
|
||||||
|
# IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines;
|
||||||
|
# the IRIX cc adds comments like `#:fec' to the end of the
|
||||||
|
# dependency line.
|
||||||
|
tr ' ' '
|
||||||
|
' < "$tmpdepfile" \
|
||||||
|
| sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \
|
||||||
|
tr '
|
||||||
|
' ' ' >> $depfile
|
||||||
|
echo >> $depfile
|
||||||
|
|
||||||
|
# The second pass generates a dummy entry for each header file.
|
||||||
|
tr ' ' '
|
||||||
|
' < "$tmpdepfile" \
|
||||||
|
| sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \
|
||||||
|
>> $depfile
|
||||||
|
else
|
||||||
|
# The sourcefile does not contain any dependencies, so just
|
||||||
|
# store a dummy comment line, to avoid errors with the Makefile
|
||||||
|
# "include basename.Plo" scheme.
|
||||||
|
echo "#dummy" > "$depfile"
|
||||||
|
fi
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
aix)
|
||||||
|
# The C for AIX Compiler uses -M and outputs the dependencies
|
||||||
|
# in a .u file. This file always lives in the current directory.
|
||||||
|
# Also, the AIX compiler puts `$object:' at the start of each line;
|
||||||
|
# $object doesn't have directory information.
|
||||||
|
stripped=`echo "$object" | sed -e 's,^.*/,,' -e 's/\(.*\)\..*$/\1/'`
|
||||||
|
tmpdepfile="$stripped.u"
|
||||||
|
outname="$stripped.o"
|
||||||
|
if test "$libtool" = yes; then
|
||||||
|
"$@" -Wc,-M
|
||||||
|
else
|
||||||
|
"$@" -M
|
||||||
|
fi
|
||||||
|
|
||||||
|
stat=$?
|
||||||
|
if test $stat -eq 0; then :
|
||||||
|
else
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
exit $stat
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f "$tmpdepfile"; then
|
||||||
|
# Each line is of the form `foo.o: dependent.h'.
|
||||||
|
# Do two passes, one to just change these to
|
||||||
|
# `$object: dependent.h' and one to simply `dependent.h:'.
|
||||||
|
sed -e "s,^$outname:,$object :," < "$tmpdepfile" > "$depfile"
|
||||||
|
sed -e "s,^$outname: \(.*\)$,\1:," < "$tmpdepfile" >> "$depfile"
|
||||||
|
else
|
||||||
|
# The sourcefile does not contain any dependencies, so just
|
||||||
|
# store a dummy comment line, to avoid errors with the Makefile
|
||||||
|
# "include basename.Plo" scheme.
|
||||||
|
echo "#dummy" > "$depfile"
|
||||||
|
fi
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
tru64)
|
||||||
|
# The Tru64 compiler uses -MD to generate dependencies as a side
|
||||||
|
# effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'.
|
||||||
|
# At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put
|
||||||
|
# dependencies in `foo.d' instead, so we check for that too.
|
||||||
|
# Subdirectories are respected.
|
||||||
|
|
||||||
|
base=`echo "$object" | sed -e 's/\.o$//' -e 's/\.lo$//'`
|
||||||
|
tmpdepfile1="$base.o.d"
|
||||||
|
tmpdepfile2="$base.d"
|
||||||
|
if test "$libtool" = yes; then
|
||||||
|
"$@" -Wc,-MD
|
||||||
|
else
|
||||||
|
"$@" -MD
|
||||||
|
fi
|
||||||
|
|
||||||
|
stat=$?
|
||||||
|
if test $stat -eq 0; then :
|
||||||
|
else
|
||||||
|
rm -f "$tmpdepfile1" "$tmpdepfile2"
|
||||||
|
exit $stat
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f "$tmpdepfile1"; then
|
||||||
|
tmpdepfile="$tmpdepfile1"
|
||||||
|
else
|
||||||
|
tmpdepfile="$tmpdepfile2"
|
||||||
|
fi
|
||||||
|
if test -f "$tmpdepfile"; then
|
||||||
|
sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile"
|
||||||
|
# That's a space and a tab in the [].
|
||||||
|
sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile"
|
||||||
|
else
|
||||||
|
echo "#dummy" > "$depfile"
|
||||||
|
fi
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
#nosideeffect)
|
||||||
|
# This comment above is used by automake to tell side-effect
|
||||||
|
# dependency tracking mechanisms from slower ones.
|
||||||
|
|
||||||
|
dashmstdout)
|
||||||
|
# Important note: in order to support this mode, a compiler *must*
|
||||||
|
# always write the proprocessed file to stdout, regardless of -o,
|
||||||
|
# because we must use -o when running libtool.
|
||||||
|
test -z "$dashmflag" && dashmflag=-M
|
||||||
|
( IFS=" "
|
||||||
|
case " $* " in
|
||||||
|
*" --mode=compile "*) # this is libtool, let us make it quiet
|
||||||
|
for arg
|
||||||
|
do # cycle over the arguments
|
||||||
|
case "$arg" in
|
||||||
|
"--mode=compile")
|
||||||
|
# insert --quiet before "--mode=compile"
|
||||||
|
set fnord "$@" --quiet
|
||||||
|
shift # fnord
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set fnord "$@" "$arg"
|
||||||
|
shift # fnord
|
||||||
|
shift # "$arg"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
"$@" $dashmflag | sed 's:^[^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile"
|
||||||
|
) &
|
||||||
|
proc=$!
|
||||||
|
"$@"
|
||||||
|
stat=$?
|
||||||
|
wait "$proc"
|
||||||
|
if test "$stat" != 0; then exit $stat; fi
|
||||||
|
rm -f "$depfile"
|
||||||
|
cat < "$tmpdepfile" > "$depfile"
|
||||||
|
tr ' ' '
|
||||||
|
' < "$tmpdepfile" | \
|
||||||
|
## Some versions of the HPUX 10.20 sed can't process this invocation
|
||||||
|
## correctly. Breaking it into two sed invocations is a workaround.
|
||||||
|
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
dashXmstdout)
|
||||||
|
# This case only exists to satisfy depend.m4. It is never actually
|
||||||
|
# run, as this mode is specially recognized in the preamble.
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
makedepend)
|
||||||
|
# X makedepend
|
||||||
|
(
|
||||||
|
shift
|
||||||
|
cleared=no
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $cleared in no)
|
||||||
|
set ""; shift
|
||||||
|
cleared=yes
|
||||||
|
esac
|
||||||
|
case "$arg" in
|
||||||
|
-D*|-I*)
|
||||||
|
set fnord "$@" "$arg"; shift;;
|
||||||
|
-*)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
set fnord "$@" "$arg"; shift;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
obj_suffix="`echo $object | sed 's/^.*\././'`"
|
||||||
|
touch "$tmpdepfile"
|
||||||
|
${MAKEDEPEND-makedepend} 2>/dev/null -o"$obj_suffix" -f"$tmpdepfile" "$@"
|
||||||
|
) &
|
||||||
|
proc=$!
|
||||||
|
"$@"
|
||||||
|
stat=$?
|
||||||
|
wait "$proc"
|
||||||
|
if test "$stat" != 0; then exit $stat; fi
|
||||||
|
rm -f "$depfile"
|
||||||
|
cat < "$tmpdepfile" > "$depfile"
|
||||||
|
sed '1,2d' "$tmpdepfile" | tr ' ' '
|
||||||
|
' | \
|
||||||
|
## Some versions of the HPUX 10.20 sed can't process this invocation
|
||||||
|
## correctly. Breaking it into two sed invocations is a workaround.
|
||||||
|
sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile"
|
||||||
|
rm -f "$tmpdepfile" "$tmpdepfile".bak
|
||||||
|
;;
|
||||||
|
|
||||||
|
cpp)
|
||||||
|
# Important note: in order to support this mode, a compiler *must*
|
||||||
|
# always write the proprocessed file to stdout, regardless of -o,
|
||||||
|
# because we must use -o when running libtool.
|
||||||
|
( IFS=" "
|
||||||
|
case " $* " in
|
||||||
|
*" --mode=compile "*)
|
||||||
|
for arg
|
||||||
|
do # cycle over the arguments
|
||||||
|
case $arg in
|
||||||
|
"--mode=compile")
|
||||||
|
# insert --quiet before "--mode=compile"
|
||||||
|
set fnord "$@" --quiet
|
||||||
|
shift # fnord
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set fnord "$@" "$arg"
|
||||||
|
shift # fnord
|
||||||
|
shift # "$arg"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
"$@" -E |
|
||||||
|
sed -n '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' |
|
||||||
|
sed '$ s: \\$::' > "$tmpdepfile"
|
||||||
|
) &
|
||||||
|
proc=$!
|
||||||
|
"$@"
|
||||||
|
stat=$?
|
||||||
|
wait "$proc"
|
||||||
|
if test "$stat" != 0; then exit $stat; fi
|
||||||
|
rm -f "$depfile"
|
||||||
|
echo "$object : \\" > "$depfile"
|
||||||
|
cat < "$tmpdepfile" >> "$depfile"
|
||||||
|
sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile"
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
msvisualcpp)
|
||||||
|
# Important note: in order to support this mode, a compiler *must*
|
||||||
|
# always write the proprocessed file to stdout, regardless of -o,
|
||||||
|
# because we must use -o when running libtool.
|
||||||
|
( IFS=" "
|
||||||
|
case " $* " in
|
||||||
|
*" --mode=compile "*)
|
||||||
|
for arg
|
||||||
|
do # cycle over the arguments
|
||||||
|
case $arg in
|
||||||
|
"--mode=compile")
|
||||||
|
# insert --quiet before "--mode=compile"
|
||||||
|
set fnord "$@" --quiet
|
||||||
|
shift # fnord
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
set fnord "$@" "$arg"
|
||||||
|
shift # fnord
|
||||||
|
shift # "$arg"
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
for arg
|
||||||
|
do
|
||||||
|
case "$arg" in
|
||||||
|
"-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI")
|
||||||
|
set fnord "$@"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
set fnord "$@" "$arg"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
"$@" -E |
|
||||||
|
sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::echo "`cygpath -u \\"\1\\"`":p' | sort | uniq > "$tmpdepfile"
|
||||||
|
) &
|
||||||
|
proc=$!
|
||||||
|
"$@"
|
||||||
|
stat=$?
|
||||||
|
wait "$proc"
|
||||||
|
if test "$stat" != 0; then exit $stat; fi
|
||||||
|
rm -f "$depfile"
|
||||||
|
echo "$object : \\" > "$depfile"
|
||||||
|
. "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile"
|
||||||
|
echo " " >> "$depfile"
|
||||||
|
. "$tmpdepfile" | sed 's% %\\ %g' | sed -n '/^\(.*\)$/ s::\1\::p' >> "$depfile"
|
||||||
|
rm -f "$tmpdepfile"
|
||||||
|
;;
|
||||||
|
|
||||||
|
none)
|
||||||
|
exec "$@"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Unknown depmode $depmode" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,27 @@
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Modified by: Grzegorz Jakacki <jakacki at acm dot org>
|
||||||
|
|
||||||
|
## Process this file with automake to produce Makefile.in.
|
||||||
|
|
||||||
|
# installed documentation
|
||||||
|
#
|
||||||
|
dist_pkgdata_DATA = barrett_diagram debugging.html gc.man \
|
||||||
|
gcdescr.html README README.amiga README.arm.cross \
|
||||||
|
README.autoconf README.changes README.contributors \
|
||||||
|
README.cords README.DGUX386 README.dj README.environment \
|
||||||
|
README.ews4800 README.hp README.linux README.Mac \
|
||||||
|
README.MacOSX README.macros README.OS2 README.rs6000 \
|
||||||
|
README.sgi README.solaris2 README.uts README.win32 \
|
||||||
|
tree.html leak.html gcinterface.html scale.html \
|
||||||
|
README.darwin
|
||||||
|
|
|
@ -0,0 +1,282 @@
|
||||||
|
# Makefile.in generated by automake 1.6.3 from Makefile.am.
|
||||||
|
# @configure_input@
|
||||||
|
|
||||||
|
# Copyright 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002
|
||||||
|
# Free Software Foundation, Inc.
|
||||||
|
# This Makefile.in is free software; the Free Software Foundation
|
||||||
|
# gives unlimited permission to copy and/or distribute it,
|
||||||
|
# with or without modifications, as long as this notice is preserved.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
|
||||||
|
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||||
|
# PARTICULAR PURPOSE.
|
||||||
|
|
||||||
|
@SET_MAKE@
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
# Modified by: Grzegorz Jakacki <jakacki at acm dot org>
|
||||||
|
SHELL = @SHELL@
|
||||||
|
|
||||||
|
srcdir = @srcdir@
|
||||||
|
top_srcdir = @top_srcdir@
|
||||||
|
VPATH = @srcdir@
|
||||||
|
prefix = @prefix@
|
||||||
|
exec_prefix = @exec_prefix@
|
||||||
|
|
||||||
|
bindir = @bindir@
|
||||||
|
sbindir = @sbindir@
|
||||||
|
libexecdir = @libexecdir@
|
||||||
|
datadir = @datadir@
|
||||||
|
sysconfdir = @sysconfdir@
|
||||||
|
sharedstatedir = @sharedstatedir@
|
||||||
|
localstatedir = @localstatedir@
|
||||||
|
libdir = @libdir@
|
||||||
|
infodir = @infodir@
|
||||||
|
mandir = @mandir@
|
||||||
|
includedir = @includedir@
|
||||||
|
oldincludedir = /usr/include
|
||||||
|
pkgdatadir = $(datadir)/@PACKAGE@
|
||||||
|
pkglibdir = $(libdir)/@PACKAGE@
|
||||||
|
pkgincludedir = $(includedir)/@PACKAGE@
|
||||||
|
top_builddir = ..
|
||||||
|
|
||||||
|
ACLOCAL = @ACLOCAL@
|
||||||
|
AUTOCONF = @AUTOCONF@
|
||||||
|
AUTOMAKE = @AUTOMAKE@
|
||||||
|
AUTOHEADER = @AUTOHEADER@
|
||||||
|
|
||||||
|
am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
|
||||||
|
INSTALL = @INSTALL@
|
||||||
|
INSTALL_PROGRAM = @INSTALL_PROGRAM@
|
||||||
|
INSTALL_DATA = @INSTALL_DATA@
|
||||||
|
install_sh_DATA = $(install_sh) -c -m 644
|
||||||
|
install_sh_PROGRAM = $(install_sh) -c
|
||||||
|
install_sh_SCRIPT = $(install_sh) -c
|
||||||
|
INSTALL_SCRIPT = @INSTALL_SCRIPT@
|
||||||
|
INSTALL_HEADER = $(INSTALL_DATA)
|
||||||
|
transform = @program_transform_name@
|
||||||
|
NORMAL_INSTALL = :
|
||||||
|
PRE_INSTALL = :
|
||||||
|
POST_INSTALL = :
|
||||||
|
NORMAL_UNINSTALL = :
|
||||||
|
PRE_UNINSTALL = :
|
||||||
|
POST_UNINSTALL = :
|
||||||
|
host_alias = @host_alias@
|
||||||
|
host_triplet = @host@
|
||||||
|
|
||||||
|
EXEEXT = @EXEEXT@
|
||||||
|
OBJEXT = @OBJEXT@
|
||||||
|
PATH_SEPARATOR = @PATH_SEPARATOR@
|
||||||
|
AMTAR = @AMTAR@
|
||||||
|
AR = @AR@
|
||||||
|
AS = @AS@
|
||||||
|
AWK = @AWK@
|
||||||
|
CC = @CC@
|
||||||
|
CCAS = @CCAS@
|
||||||
|
CCASFLAGS = @CCASFLAGS@
|
||||||
|
CFLAGS = @CFLAGS@
|
||||||
|
CXX = @CXX@
|
||||||
|
CXXFLAGS = @CXXFLAGS@
|
||||||
|
CXXINCLUDES = @CXXINCLUDES@
|
||||||
|
DEPDIR = @DEPDIR@
|
||||||
|
DLLTOOL = @DLLTOOL@
|
||||||
|
ECHO = @ECHO@
|
||||||
|
EXTRA_TEST_LIBS = @EXTRA_TEST_LIBS@
|
||||||
|
GC_CFLAGS = @GC_CFLAGS@
|
||||||
|
GC_VERSION = @GC_VERSION@
|
||||||
|
INCLUDES = @INCLUDES@
|
||||||
|
INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
|
||||||
|
LIBTOOL = @LIBTOOL@
|
||||||
|
LN_S = @LN_S@
|
||||||
|
MAINT = @MAINT@
|
||||||
|
MY_CFLAGS = @MY_CFLAGS@
|
||||||
|
OBJDUMP = @OBJDUMP@
|
||||||
|
PACKAGE = @PACKAGE@
|
||||||
|
RANLIB = @RANLIB@
|
||||||
|
STRIP = @STRIP@
|
||||||
|
THREADLIBS = @THREADLIBS@
|
||||||
|
VERSION = @VERSION@
|
||||||
|
addincludes = @addincludes@
|
||||||
|
addlibs = @addlibs@
|
||||||
|
addobjs = @addobjs@
|
||||||
|
addtests = @addtests@
|
||||||
|
am__include = @am__include@
|
||||||
|
am__quote = @am__quote@
|
||||||
|
install_sh = @install_sh@
|
||||||
|
target_all = @target_all@
|
||||||
|
|
||||||
|
# installed documentation
|
||||||
|
#
|
||||||
|
dist_pkgdata_DATA = barrett_diagram debugging.html gc.man \
|
||||||
|
gcdescr.html README README.amiga README.arm.cross \
|
||||||
|
README.autoconf README.changes README.contributors \
|
||||||
|
README.cords README.DGUX386 README.dj README.environment \
|
||||||
|
README.ews4800 README.hp README.linux README.Mac \
|
||||||
|
README.MacOSX README.macros README.OS2 README.rs6000 \
|
||||||
|
README.sgi README.solaris2 README.uts README.win32 \
|
||||||
|
tree.html leak.html gcinterface.html scale.html \
|
||||||
|
README.darwin
|
||||||
|
|
||||||
|
subdir = doc
|
||||||
|
mkinstalldirs = $(SHELL) $(top_srcdir)/mkinstalldirs
|
||||||
|
CONFIG_CLEAN_FILES =
|
||||||
|
DIST_SOURCES =
|
||||||
|
DATA = $(dist_pkgdata_DATA)
|
||||||
|
|
||||||
|
DIST_COMMON = README $(dist_pkgdata_DATA) Makefile.am Makefile.in
|
||||||
|
all: all-am
|
||||||
|
|
||||||
|
.SUFFIXES:
|
||||||
|
$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ Makefile.am $(top_srcdir)/configure.in $(ACLOCAL_M4)
|
||||||
|
cd $(top_srcdir) && \
|
||||||
|
$(AUTOMAKE) --gnu doc/Makefile
|
||||||
|
Makefile: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.in $(top_builddir)/config.status
|
||||||
|
cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)
|
||||||
|
|
||||||
|
mostlyclean-libtool:
|
||||||
|
-rm -f *.lo
|
||||||
|
|
||||||
|
clean-libtool:
|
||||||
|
-rm -rf .libs _libs
|
||||||
|
|
||||||
|
distclean-libtool:
|
||||||
|
-rm -f libtool
|
||||||
|
uninstall-info-am:
|
||||||
|
dist_pkgdataDATA_INSTALL = $(INSTALL_DATA)
|
||||||
|
install-dist_pkgdataDATA: $(dist_pkgdata_DATA)
|
||||||
|
@$(NORMAL_INSTALL)
|
||||||
|
$(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
|
||||||
|
@list='$(dist_pkgdata_DATA)'; for p in $$list; do \
|
||||||
|
if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
|
||||||
|
f="`echo $$p | sed -e 's|^.*/||'`"; \
|
||||||
|
echo " $(dist_pkgdataDATA_INSTALL) $$d$$p $(DESTDIR)$(pkgdatadir)/$$f"; \
|
||||||
|
$(dist_pkgdataDATA_INSTALL) $$d$$p $(DESTDIR)$(pkgdatadir)/$$f; \
|
||||||
|
done
|
||||||
|
|
||||||
|
uninstall-dist_pkgdataDATA:
|
||||||
|
@$(NORMAL_UNINSTALL)
|
||||||
|
@list='$(dist_pkgdata_DATA)'; for p in $$list; do \
|
||||||
|
f="`echo $$p | sed -e 's|^.*/||'`"; \
|
||||||
|
echo " rm -f $(DESTDIR)$(pkgdatadir)/$$f"; \
|
||||||
|
rm -f $(DESTDIR)$(pkgdatadir)/$$f; \
|
||||||
|
done
|
||||||
|
tags: TAGS
|
||||||
|
TAGS:
|
||||||
|
|
||||||
|
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||||
|
|
||||||
|
top_distdir = ..
|
||||||
|
distdir = $(top_distdir)/$(PACKAGE)-$(VERSION)
|
||||||
|
|
||||||
|
distdir: $(DISTFILES)
|
||||||
|
@list='$(DISTFILES)'; for file in $$list; do \
|
||||||
|
if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
|
||||||
|
dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \
|
||||||
|
if test "$$dir" != "$$file" && test "$$dir" != "."; then \
|
||||||
|
dir="/$$dir"; \
|
||||||
|
$(mkinstalldirs) "$(distdir)$$dir"; \
|
||||||
|
else \
|
||||||
|
dir=''; \
|
||||||
|
fi; \
|
||||||
|
if test -d $$d/$$file; then \
|
||||||
|
if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
|
||||||
|
cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \
|
||||||
|
fi; \
|
||||||
|
cp -pR $$d/$$file $(distdir)$$dir || exit 1; \
|
||||||
|
else \
|
||||||
|
test -f $(distdir)/$$file \
|
||||||
|
|| cp -p $$d/$$file $(distdir)/$$file \
|
||||||
|
|| exit 1; \
|
||||||
|
fi; \
|
||||||
|
done
|
||||||
|
check-am: all-am
|
||||||
|
check: check-am
|
||||||
|
all-am: Makefile $(DATA)
|
||||||
|
|
||||||
|
installdirs:
|
||||||
|
$(mkinstalldirs) $(DESTDIR)$(pkgdatadir)
|
||||||
|
|
||||||
|
install: install-am
|
||||||
|
install-exec: install-exec-am
|
||||||
|
install-data: install-data-am
|
||||||
|
uninstall: uninstall-am
|
||||||
|
|
||||||
|
install-am: all-am
|
||||||
|
@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
|
||||||
|
|
||||||
|
installcheck: installcheck-am
|
||||||
|
install-strip:
|
||||||
|
$(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
|
||||||
|
INSTALL_STRIP_FLAG=-s \
|
||||||
|
`test -z '$(STRIP)' || \
|
||||||
|
echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install
|
||||||
|
mostlyclean-generic:
|
||||||
|
|
||||||
|
clean-generic:
|
||||||
|
|
||||||
|
distclean-generic:
|
||||||
|
-rm -f Makefile $(CONFIG_CLEAN_FILES)
|
||||||
|
|
||||||
|
maintainer-clean-generic:
|
||||||
|
@echo "This command is intended for maintainers to use"
|
||||||
|
@echo "it deletes files that may require special tools to rebuild."
|
||||||
|
clean: clean-am
|
||||||
|
|
||||||
|
clean-am: clean-generic clean-libtool mostlyclean-am
|
||||||
|
|
||||||
|
distclean: distclean-am
|
||||||
|
|
||||||
|
distclean-am: clean-am distclean-generic distclean-libtool
|
||||||
|
|
||||||
|
dvi: dvi-am
|
||||||
|
|
||||||
|
dvi-am:
|
||||||
|
|
||||||
|
info: info-am
|
||||||
|
|
||||||
|
info-am:
|
||||||
|
|
||||||
|
install-data-am: install-dist_pkgdataDATA
|
||||||
|
|
||||||
|
install-exec-am:
|
||||||
|
|
||||||
|
install-info: install-info-am
|
||||||
|
|
||||||
|
install-man:
|
||||||
|
|
||||||
|
installcheck-am:
|
||||||
|
|
||||||
|
maintainer-clean: maintainer-clean-am
|
||||||
|
|
||||||
|
maintainer-clean-am: distclean-am maintainer-clean-generic
|
||||||
|
|
||||||
|
mostlyclean: mostlyclean-am
|
||||||
|
|
||||||
|
mostlyclean-am: mostlyclean-generic mostlyclean-libtool
|
||||||
|
|
||||||
|
uninstall-am: uninstall-dist_pkgdataDATA uninstall-info-am
|
||||||
|
|
||||||
|
.PHONY: all all-am check check-am clean clean-generic clean-libtool \
|
||||||
|
distclean distclean-generic distclean-libtool distdir dvi \
|
||||||
|
dvi-am info info-am install install-am install-data \
|
||||||
|
install-data-am install-dist_pkgdataDATA install-exec \
|
||||||
|
install-exec-am install-info install-info-am install-man \
|
||||||
|
install-strip installcheck installcheck-am installdirs \
|
||||||
|
maintainer-clean maintainer-clean-generic mostlyclean \
|
||||||
|
mostlyclean-generic mostlyclean-libtool uninstall uninstall-am \
|
||||||
|
uninstall-dist_pkgdataDATA uninstall-info-am
|
||||||
|
|
||||||
|
# Tell versions [3.59,3.63) of GNU make to not export all variables.
|
||||||
|
# Otherwise a system limit (for SysV at least) may be exceeded.
|
||||||
|
.NOEXPORT:
|
|
@ -0,0 +1,215 @@
|
||||||
|
Garbage Collector (parallel iversion) for ix86 DG/UX Release R4.20MU07
|
||||||
|
|
||||||
|
|
||||||
|
*READ* the file README.QUICK.
|
||||||
|
|
||||||
|
You need the GCC-3.0.3 rev (DG/UX) compiler to build this tree.
|
||||||
|
This compiler has the new "dgux386" threads package implemented.
|
||||||
|
It also supports the switch "-pthread" needed to link correctly
|
||||||
|
the DG/UX's -lrte -lthread with -lgcc and the system's -lc.
|
||||||
|
Finally we support parralleli-mark for the SMP DG/UX machines.
|
||||||
|
To build the garbage collector do:
|
||||||
|
|
||||||
|
./configure --enable-parallel-mark
|
||||||
|
make
|
||||||
|
make gctest
|
||||||
|
|
||||||
|
Before you run "gctest" you need to set your LD_LIBRARY_PATH
|
||||||
|
correctly so that "gctest" can find the shared library libgc.
|
||||||
|
Alternatively you can do a configuration
|
||||||
|
|
||||||
|
./configure --enable-parallel-mark --disable-shared
|
||||||
|
|
||||||
|
to build only the static version of libgc.
|
||||||
|
|
||||||
|
To enable debugging messages please do:
|
||||||
|
1) Add the "--enable-full-debug" flag during configuration.
|
||||||
|
2) Edit the file linux-threads.c and uncommnect the line:
|
||||||
|
|
||||||
|
/* #define DEBUG_THREADS 1 */ to --->
|
||||||
|
|
||||||
|
#define DEBUG_THREADS 1
|
||||||
|
|
||||||
|
Then give "make" as usual.
|
||||||
|
|
||||||
|
In a machine with 4 CPUs (my own machine) the option parallel
|
||||||
|
mark (aka --enable-parallel-mark) makes a BIG difference.
|
||||||
|
|
||||||
|
Takis Psarogiannakopoulos
|
||||||
|
University of Cambridge
|
||||||
|
Centre for Mathematical Sciences
|
||||||
|
Department of Pure Mathematics
|
||||||
|
Wilberforce Road
|
||||||
|
Cambridge CB3 0WB ,UK , <takis@XFree86.Org>
|
||||||
|
January 2002
|
||||||
|
|
||||||
|
|
||||||
|
Note (HB):
|
||||||
|
The integration of this patch is currently not complete.
|
||||||
|
The following patches against 6.1alpha3 where hard to move
|
||||||
|
to alpha4, and are not integrated. There may also be minor
|
||||||
|
problems with stylistic corrections made by me.
|
||||||
|
|
||||||
|
|
||||||
|
--- ltconfig.ORIG Mon Jan 28 20:22:18 2002
|
||||||
|
+++ ltconfig Mon Jan 28 20:44:00 2002
|
||||||
|
@@ -689,6 +689,11 @@
|
||||||
|
pic_flag=-Kconform_pic
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
+ dgux*)
|
||||||
|
+ pic_flag='-fPIC'
|
||||||
|
+ link_static='-Bstatic'
|
||||||
|
+ wl='-Wl,'
|
||||||
|
+ ;;
|
||||||
|
*)
|
||||||
|
pic_flag='-fPIC'
|
||||||
|
;;
|
||||||
|
@@ -718,6 +723,12 @@
|
||||||
|
# We can build DLLs from non-PIC.
|
||||||
|
;;
|
||||||
|
|
||||||
|
+ dgux*)
|
||||||
|
+ pic_flag='-KPIC'
|
||||||
|
+ link_static='-Bstatic'
|
||||||
|
+ wl='-Wl,'
|
||||||
|
+ ;;
|
||||||
|
+
|
||||||
|
osf3* | osf4* | osf5*)
|
||||||
|
# All OSF/1 code is PIC.
|
||||||
|
wl='-Wl,'
|
||||||
|
@@ -1154,6 +1165,22 @@
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
+ dgux*)
|
||||||
|
+ ld_shlibs=yes
|
||||||
|
+ # For both C/C++ ommit the deplibs. This is because we relying on the fact
|
||||||
|
+ # that compilation of execitables will put them in correct order
|
||||||
|
+ # in any case and sometimes are wrong when listed as deplibs (or missing some deplibs)
|
||||||
|
+ # However when GNU ld and --whole-archive needs to be used we have the problem
|
||||||
|
+ # that if the -fPIC *_s.a archive is linked through deplibs list we ommiting crucial
|
||||||
|
+ # .lo/.o files from the created shared lib. This I think is not the case here.
|
||||||
|
+ archive_cmds='$CC -shared -h $soname -o $lib $libobjs $linkopts'
|
||||||
|
+ thread_safe_flag_spec='-pthread'
|
||||||
|
+ wlarc=
|
||||||
|
+ hardcode_libdir_flag_spec='-L$libdir'
|
||||||
|
+ hardcode_shlibpath_var=no
|
||||||
|
+ ac_cv_archive_cmds_needs_lc=no
|
||||||
|
+ ;;
|
||||||
|
+
|
||||||
|
cygwin* | mingw*)
|
||||||
|
# hardcode_libdir_flag_spec is actually meaningless, as there is
|
||||||
|
# no search path for DLLs.
|
||||||
|
@@ -1497,7 +1524,7 @@
|
||||||
|
;;
|
||||||
|
|
||||||
|
dgux*)
|
||||||
|
- archive_cmds='$LD -G -h $soname -o $lib $libobjs $deplibs $linkopts'
|
||||||
|
+ archive_cmds='$CC -shared -h $soname -o $lib $libobjs $linkopts'
|
||||||
|
hardcode_libdir_flag_spec='-L$libdir'
|
||||||
|
hardcode_shlibpath_var=no
|
||||||
|
;;
|
||||||
|
@@ -2092,12 +2119,17 @@
|
||||||
|
;;
|
||||||
|
|
||||||
|
dgux*)
|
||||||
|
- version_type=linux
|
||||||
|
+ version_type=dgux
|
||||||
|
need_lib_prefix=no
|
||||||
|
need_version=no
|
||||||
|
- library_names_spec='${libname}${release}.so$versuffix ${libname}${release}.so$major $libname.so'
|
||||||
|
- soname_spec='${libname}${release}.so$major'
|
||||||
|
+ library_names_spec='$libname.so$versuffix'
|
||||||
|
+ soname_spec='$libname.so$versuffix'
|
||||||
|
shlibpath_var=LD_LIBRARY_PATH
|
||||||
|
+ thread_safe_flag_spec='-pthread'
|
||||||
|
+ wlarc=
|
||||||
|
+ hardcode_libdir_flag_spec='-L$libdir'
|
||||||
|
+ hardcode_shlibpath_var=no
|
||||||
|
+ ac_cv_archive_cmds_needs_lc=no
|
||||||
|
;;
|
||||||
|
|
||||||
|
sysv4*MP*)
|
||||||
|
|
||||||
|
|
||||||
|
--- ltmain.sh.ORIG Mon Jan 28 20:31:18 2002
|
||||||
|
+++ ltmain.sh Tue Jan 29 00:11:29 2002
|
||||||
|
@@ -1072,11 +1072,38 @@
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
+ -thread*)
|
||||||
|
+ # DG/UX GCC 2.95.x, 3.x.x rev (DG/UX) links -lthread
|
||||||
|
+ # with the switch -threads
|
||||||
|
+ if test "$arg" = "-threads"; then
|
||||||
|
+ case "$host" in
|
||||||
|
+ i[3456]86-*-dgux*)
|
||||||
|
+ deplibs="$deplibs $arg"
|
||||||
|
+ continue
|
||||||
|
+ ;;
|
||||||
|
+ esac
|
||||||
|
+ fi
|
||||||
|
+ ;;
|
||||||
|
+
|
||||||
|
+ -pthread*)
|
||||||
|
+ # DG/UX GCC 2.95.x, 3.x.x rev (DG/UX) links -lthread
|
||||||
|
+ # with the switch -pthread
|
||||||
|
+ if test "$arg" = "-pthread"; then
|
||||||
|
+ case "$host" in
|
||||||
|
+ i[3456]86-*-dgux*)
|
||||||
|
+ deplibs="$deplibs $arg"
|
||||||
|
+ continue
|
||||||
|
+ ;;
|
||||||
|
+ esac
|
||||||
|
+ fi
|
||||||
|
+ ;;
|
||||||
|
+
|
||||||
|
-l*)
|
||||||
|
if test "$arg" = "-lc"; then
|
||||||
|
case "$host" in
|
||||||
|
- *-*-cygwin* | *-*-mingw* | *-*-os2* | *-*-beos*)
|
||||||
|
+ *-*-cygwin* | *-*-mingw* | *-*-os2* | *-*-beos* | i[3456]86-*-dgux*)
|
||||||
|
# These systems don't actually have c library (as such)
|
||||||
|
+ # It is wrong in DG/UX to add -lc when creating shared/dynamic objs/libs
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
@@ -1248,6 +1275,12 @@
|
||||||
|
temp_deplibs=
|
||||||
|
for deplib in $dependency_libs; do
|
||||||
|
case "$deplib" in
|
||||||
|
+ -thread*)
|
||||||
|
+ temp_deplibs="$temp_deplibs $deplib"
|
||||||
|
+ ;;
|
||||||
|
+ -pthread)
|
||||||
|
+ temp_deplibs="$temp_deplibs $deplib"
|
||||||
|
+ ;;
|
||||||
|
-R*) temp_xrpath=`$echo "X$deplib" | $Xsed -e 's/^-R//'`
|
||||||
|
case " $rpath $xrpath " in
|
||||||
|
*" $temp_xrpath "*) ;;
|
||||||
|
@@ -1709,6 +1742,13 @@
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
|
||||||
|
+ dgux)
|
||||||
|
+ # Leave mostly blank for DG/UX
|
||||||
|
+ major=
|
||||||
|
+ versuffix=".$current.$revision";
|
||||||
|
+ verstring=
|
||||||
|
+ ;;
|
||||||
|
+
|
||||||
|
linux)
|
||||||
|
major=.`expr $current - $age`
|
||||||
|
versuffix="$major.$age.$revision"
|
||||||
|
@@ -1792,8 +1832,9 @@
|
||||||
|
|
||||||
|
dependency_libs="$deplibs"
|
||||||
|
case "$host" in
|
||||||
|
- *-*-cygwin* | *-*-mingw* | *-*-os2* | *-*-beos*)
|
||||||
|
+ *-*-cygwin* | *-*-mingw* | *-*-os2* | *-*-beos* | i[3456]86-*-dgux*)
|
||||||
|
# these systems don't actually have a c library (as such)!
|
||||||
|
+ # It is wrong in DG/UX to add -lc when creating shared/dynamic objs/libs
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Add libc to deplibs on all other systems.
|
|
@ -0,0 +1,68 @@
|
||||||
|
From: Margaret Fleck
|
||||||
|
|
||||||
|
Here's the key details of what worked for me, in case anyone else needs them.
|
||||||
|
There may well be better ways to do some of this, but ....
|
||||||
|
-- Margaret
|
||||||
|
|
||||||
|
|
||||||
|
The badge4 has a StrongArm-1110 processor and a StrongArm-1111 coprocessor.
|
||||||
|
|
||||||
|
Assume that the garbage collector distribution is unpacked into /home/arm/gc6.0,
|
||||||
|
which is visible to both the ARM machine and a linux desktop (e.g. via NFS mounting).
|
||||||
|
|
||||||
|
Assume that you have a file /home/arm/config.site with contents something like the
|
||||||
|
example attached below. Notice that our local ARM toolchain lives in
|
||||||
|
/skiff/local.
|
||||||
|
|
||||||
|
Go to /home/arm/gc6.0 directory. Do
|
||||||
|
CONFIG_SITE=/home/arm/config.site ./configure --target=arm-linux
|
||||||
|
--prefix=/home/arm/gc6.0
|
||||||
|
|
||||||
|
On your desktop, do:
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
The main garbage collector library should now be in ../gc6.0/lib/libgc.so.
|
||||||
|
|
||||||
|
To test the garbage collector, first do the following on your desktop
|
||||||
|
make gctest
|
||||||
|
./gctest
|
||||||
|
Then do the following on the ARM machine
|
||||||
|
cd .libs
|
||||||
|
./lt-gctest
|
||||||
|
|
||||||
|
Do not try to do "make test" (the usual way of running the test
|
||||||
|
program). This does not work and seems to erase some of the important
|
||||||
|
files.
|
||||||
|
|
||||||
|
The gctest program claims to have succeeded. Haven't run any further tests
|
||||||
|
with it, though I'll be doing so in the near future.
|
||||||
|
|
||||||
|
-------------------------------
|
||||||
|
# config.site for configure
|
||||||
|
|
||||||
|
# Modified from the one provided by Bradley D. LaRonde
|
||||||
|
# Edited by Andrej Cedilnik <acedil1@csee.umbc.edu>
|
||||||
|
# Used some of solutions by Tilman Vogel <Tilman.Vogel@web.de>
|
||||||
|
# Ported for iPAQ Familiar by Oliver Kurth <oliver.kurth@innominate.com>
|
||||||
|
# Further modified by Margaret Fleck for the badge4
|
||||||
|
|
||||||
|
HOSTCC=gcc
|
||||||
|
|
||||||
|
# Names of the cross-compilers
|
||||||
|
CC=/skiff/local/bin/arm-linux-gcc
|
||||||
|
CXX=/skiff/local/bin/arm-linux-gcc
|
||||||
|
|
||||||
|
# The cross compiler specific options
|
||||||
|
CFLAGS="-O2 -fno-exceptions"
|
||||||
|
CXXFLAGS="-O2 -fno-exceptions"
|
||||||
|
CPPFLAGS="-O2 -fno-exceptions"
|
||||||
|
LDFLAGS=""
|
||||||
|
|
||||||
|
# Some other programs
|
||||||
|
AR=/skiff/local/bin/arm-linux-ar
|
||||||
|
RANLIB=/skiff/local/bin/arm-linux-ranlib
|
||||||
|
NM=/skiff/local/bin/arm-linux-nm
|
||||||
|
ac_cv_path_NM=/skiff/local/bin/arm-linux-nm
|
||||||
|
ac_cv_func_setpgrp_void=yes
|
||||||
|
x_includes=/skiff/local/arm-linux/include/X11
|
||||||
|
x_libraries=/skiff/local/arm-linux/lib/X11
|
|
@ -0,0 +1,106 @@
|
||||||
|
Darwin/MacOSX Support - July 22, 2003
|
||||||
|
====================================
|
||||||
|
|
||||||
|
Important Usage Notes
|
||||||
|
=====================
|
||||||
|
|
||||||
|
GC_init() MUST be called before calling any other GC functions. This
|
||||||
|
is necessary to properly register segments in dynamic libraries. This
|
||||||
|
call is required even if you code does not use dynamic libraries as the
|
||||||
|
dyld code handles registering all data segments.
|
||||||
|
|
||||||
|
When your use of the garbage collector is confined to dylibs and you
|
||||||
|
cannot call GC_init() before your libraries' static initializers have
|
||||||
|
run and perhaps called GC_malloc(), create an initialization routine
|
||||||
|
for each library to call GC_init():
|
||||||
|
|
||||||
|
#include <gc/gc.h>
|
||||||
|
void my_library_init() { GC_init(); }
|
||||||
|
|
||||||
|
Compile this code into a my_library_init.o, and link it into your
|
||||||
|
dylib. When you link the dylib, pass the -init argument with
|
||||||
|
_my_library_init (e.g. gcc -dynamiclib -o my_library.dylib a.o b.o c.o
|
||||||
|
my_library_init.o -init _my_library_init). This causes
|
||||||
|
my_library_init() to be called before any static initializers, and
|
||||||
|
will initialize the garbage collector properly.
|
||||||
|
|
||||||
|
Note: It doesn't hurt to call GC_init() more than once, so it's best,
|
||||||
|
if you have an application or set of libraries that all use the
|
||||||
|
garbage collector, to create an initialization routine for each of
|
||||||
|
them that calls GC_init(). Better safe than sorry.
|
||||||
|
|
||||||
|
The incremental collector is still a bit flaky on darwin. It seems to
|
||||||
|
work reliably with workarounds for a few possible bugs in place however
|
||||||
|
these workaround may not work correctly in all cases. There may also
|
||||||
|
be additional problems that I have not found.
|
||||||
|
|
||||||
|
Implementation Information
|
||||||
|
==========================
|
||||||
|
Darwin/MacOSX support is nearly complete. Thread support is reliable on
|
||||||
|
Darwin 6.x (MacOSX 10.2) and there have been reports of success on older
|
||||||
|
Darwin versions (MacOSX 10.1). Shared library support had also been
|
||||||
|
added and the gc can be run from a shared library. There is currently only
|
||||||
|
support for Darwin/PPC although adding x86 support should be trivial.
|
||||||
|
|
||||||
|
Thread support is implemented in terms of mach thread_suspend and
|
||||||
|
thread_resume calls. These provide a very clean interface to thread
|
||||||
|
suspension. This implementation doesn't rely on pthread_kill so the
|
||||||
|
code works on Darwin < 6.0 (MacOSX 10.1). All the code to stop the
|
||||||
|
world is located in darwin_stop_world.c.
|
||||||
|
|
||||||
|
The original incremental collector support unfortunatelly no longer works
|
||||||
|
on recent Darwin versions. It also relied on some undocumented kernel
|
||||||
|
structures. Mach, however, does have a very clean interface to exception
|
||||||
|
handing. The current implementation uses Mach's exception handling.
|
||||||
|
|
||||||
|
Much thanks goes to Andrew Stone, Dietmar Planitzer, Andrew Begel,
|
||||||
|
Jeff Sturm, and Jesse Rosenstock for all their work on the
|
||||||
|
Darwin/OS X port.
|
||||||
|
|
||||||
|
-Brian Alliet
|
||||||
|
brian@brianweb.net
|
||||||
|
|
||||||
|
|
||||||
|
Older Information (Most of this no longer applies to the current code)
|
||||||
|
======================================================================
|
||||||
|
|
||||||
|
While the GC should work on MacOS X Server, MacOS X and Darwin, I only tested
|
||||||
|
it on MacOS X Server.
|
||||||
|
I've added a PPC assembly version of GC_push_regs(), thus the setjmp() hack is
|
||||||
|
no longer necessary. Incremental collection is supported via mprotect/signal.
|
||||||
|
The current solution isn't really optimal because the signal handler must decode
|
||||||
|
the faulting PPC machine instruction in order to find the correct heap address.
|
||||||
|
Further, it must poke around in the register state which the kernel saved away
|
||||||
|
in some obscure register state structure before it calls the signal handler -
|
||||||
|
needless to say the layout of this structure is no where documented.
|
||||||
|
Threads and dynamic libraries are not yet supported (adding dynamic library
|
||||||
|
support via the low-level dyld API shouldn't be that hard).
|
||||||
|
|
||||||
|
The original MacOS X port was brought to you by Andrew Stone.
|
||||||
|
|
||||||
|
|
||||||
|
June, 1 2000
|
||||||
|
|
||||||
|
Dietmar Planitzer
|
||||||
|
dave.pl@ping.at
|
||||||
|
|
||||||
|
Note from Andrew Begel:
|
||||||
|
|
||||||
|
One more fix to enable gc.a to link successfully into a shared library for
|
||||||
|
MacOS X. You have to add -fno-common to the CFLAGS in the Makefile. MacOSX
|
||||||
|
disallows common symbols in anything that eventually finds its way into a
|
||||||
|
shared library. (I don't completely understand why, but -fno-common seems to
|
||||||
|
work and doesn't mess up the garbage collector's functionality).
|
||||||
|
|
||||||
|
Feb 26, 2003
|
||||||
|
|
||||||
|
Jeff Sturm and Jesse Rosenstock provided a patch that adds thread support.
|
||||||
|
GC_MACOSX_THREADS should be defined in the build and in clients. Real
|
||||||
|
dynamic library support is still missing, i.e. dynamic library data segments
|
||||||
|
are still not scanned. Code that stores pointers to the garbage collected
|
||||||
|
heap in statically allocated variables should not reside in a dynamic
|
||||||
|
library. This still doesn't appear to be 100% reliable.
|
||||||
|
|
||||||
|
Mar 10, 2003
|
||||||
|
Brian Alliet contributed dynamic library support for MacOSX. It could also
|
||||||
|
use more testing.
|
|
@ -0,0 +1,203 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<HEAD>
|
||||||
|
<TITLE>Garbage Collector Interface</TITLE>
|
||||||
|
</HEAD>
|
||||||
|
<BODY>
|
||||||
|
<H1>C Interface</h1>
|
||||||
|
On many platforms, a single-threaded garbage collector library can be built
|
||||||
|
to act as a plug-in malloc replacement. (Build with -DREDIRECT_MALLOC=GC_malloc
|
||||||
|
-DIGNORE_FREE.) This is often the best way to deal with third-party libraries
|
||||||
|
which leak or prematurely free objects. -DREDIRECT_MALLOC is intended
|
||||||
|
primarily as an easy way to adapt old code, not for new development.
|
||||||
|
<P>
|
||||||
|
New code should use the interface discussed below.
|
||||||
|
<P>
|
||||||
|
Code must be linked against the GC library. On most UNIX platforms,
|
||||||
|
this will be gc.a.
|
||||||
|
<P>
|
||||||
|
The following describes the standard C interface to the garbage collector.
|
||||||
|
It is not a complete definition of the interface. It describes only the
|
||||||
|
most commonly used functionality, approximately in decreasing order of
|
||||||
|
frequency of use. The description assumes an ANSI C compiler.
|
||||||
|
The full interface is described in
|
||||||
|
<A HREF="http://hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gch.txt">gc.h</a>
|
||||||
|
or <TT>gc.h</tt> in the distribution.
|
||||||
|
<P>
|
||||||
|
Clients should include gc.h.
|
||||||
|
<P>
|
||||||
|
In the case of multithreaded code,
|
||||||
|
gc.h should be included after the threads header file, and
|
||||||
|
after defining the appropriate GC_XXXX_THREADS macro.
|
||||||
|
(For 6.2alpha4 and later, simply defining GC_THREADS should suffice.)
|
||||||
|
Gc.h must be included
|
||||||
|
in files that use either GC or threads primitives, since threads primitives
|
||||||
|
will be redefined to cooperate with the GC on many platforms.
|
||||||
|
<DL>
|
||||||
|
<DT> <B>void * GC_MALLOC(size_t <I>nbytes</i>)</b>
|
||||||
|
<DD>
|
||||||
|
Allocates and clears <I>nbytes</i> of storage.
|
||||||
|
Requires (amortized) time proportional to <I>nbytes</i>.
|
||||||
|
The resulting object will be automatically deallocated when unreferenced.
|
||||||
|
References from objects allocated with the system malloc are usually not
|
||||||
|
considered by the collector. (See GC_MALLOC_UNCOLLECTABLE, however.)
|
||||||
|
GC_MALLOC is a macro which invokes GC_malloc by default or, if GC_DEBUG
|
||||||
|
is defined before gc.h is included, a debugging version that checks
|
||||||
|
occasionally for overwrite errors, and the like.
|
||||||
|
<DT> <B>void * GC_MALLOC_ATOMIC(size_t <I>nbytes</i>)</b>
|
||||||
|
<DD>
|
||||||
|
Allocates <I>nbytes</i> of storage.
|
||||||
|
Requires (amortized) time proportional to <I>nbytes</i>.
|
||||||
|
The resulting object will be automatically deallocated when unreferenced.
|
||||||
|
The client promises that the resulting object will never contain any pointers.
|
||||||
|
The memory is not cleared.
|
||||||
|
This is the preferred way to allocate strings, floating point arrays,
|
||||||
|
bitmaps, etc.
|
||||||
|
More precise information about pointer locations can be communicated to the
|
||||||
|
collector using the interface in
|
||||||
|
<A HREF="http://www.hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gc_typedh.txt">gc_typed.h</a> in the distribution.
|
||||||
|
<DT> <B>void * GC_MALLOC_UNCOLLECTABLE(size_t <I>nbytes</i>)</b>
|
||||||
|
<DD>
|
||||||
|
Identical to GC_MALLOC, except that the resulting object is not automatically
|
||||||
|
deallocated. Unlike the system-provided malloc, the collector does
|
||||||
|
scan the object for pointers to garbage-collectable memory, even if the
|
||||||
|
block itself does not appear to be reachable. (Objects allocated in this way
|
||||||
|
are effectively treated as roots by the collector.)
|
||||||
|
<DT> <B> void * GC_REALLOC(void *old, size_t new_size) </b>
|
||||||
|
<DD>
|
||||||
|
Allocate a new object of the indicated size and copy (a prefix of) the
|
||||||
|
old object into the new object. The old object is reused in place if
|
||||||
|
convenient. If the original object was allocated with GC_malloc_atomic,
|
||||||
|
the new object is subject to the same constraints. If it was allocated
|
||||||
|
as an uncollectable object, then the new object is uncollectable, and
|
||||||
|
the old object (if different) is deallocated.
|
||||||
|
(Use GC_REALLOC with GC_MALLOC, etc.)
|
||||||
|
<DT> <B> void GC_FREE(void *dead) </b>
|
||||||
|
<DD>
|
||||||
|
Explicitly deallocate an object. Typically not useful for small
|
||||||
|
collectable objects. (Use GC_FREE with GC_MALLOC, etc.)
|
||||||
|
<DT> <B> void * GC_MALLOC_IGNORE_OFF_PAGE(size_t <I>nbytes</i>) </b>
|
||||||
|
<DD>
|
||||||
|
<DT> <B> void * GC_MALLOC_ATOMIC_IGNORE_OFF_PAGE(size_t <I>nbytes</i>) </b>
|
||||||
|
<DD>
|
||||||
|
Analogous to GC_MALLOC and GC_MALLOC_ATOMIC, except that the client
|
||||||
|
guarantees that as long
|
||||||
|
as the resulting object is of use, a pointer is maintained to someplace
|
||||||
|
inside the first 512 bytes of the object. This pointer should be declared
|
||||||
|
volatile to avoid interference from compiler optimizations.
|
||||||
|
(Other nonvolatile pointers to the object may exist as well.)
|
||||||
|
This is the
|
||||||
|
preferred way to allocate objects that are likely to be > 100KBytes in size.
|
||||||
|
It greatly reduces the risk that such objects will be accidentally retained
|
||||||
|
when they are no longer needed. Thus space usage may be significantly reduced.
|
||||||
|
<DT> <B> void GC_gcollect(void) </b>
|
||||||
|
<DD>
|
||||||
|
Explicitly force a garbage collection.
|
||||||
|
<DT> <B> void GC_enable_incremental(void) </b>
|
||||||
|
<DD>
|
||||||
|
Cause the garbage collector to perform a small amount of work
|
||||||
|
every few invocations of GC_malloc or the like, instead of performing
|
||||||
|
an entire collection at once. This is likely to increase total
|
||||||
|
running time. It will improve response on a platform that either has
|
||||||
|
suitable support in the garbage collector (Irix and most other Unix
|
||||||
|
versions, win32 if the collector was suitably built) or if "stubborn"
|
||||||
|
allocation is used (see <A HREF="http://www.hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gch.txt">gc.h</a>).
|
||||||
|
On many platforms this interacts poorly with system calls
|
||||||
|
that write to the garbage collected heap.
|
||||||
|
<DT> <B> GC_warn_proc GC_set_warn_proc(GC_warn_proc p) </b>
|
||||||
|
<DD>
|
||||||
|
Replace the default procedure used by the collector to print warnings.
|
||||||
|
The collector
|
||||||
|
may otherwise write to sterr, most commonly because GC_malloc was used
|
||||||
|
in a situation in which GC_malloc_ignore_off_page would have been more
|
||||||
|
appropriate. See <A HREF="http://www.hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gch.txt">gc.h</a> for details.
|
||||||
|
<DT> <B> void GC_register_finalizer(...) </b>
|
||||||
|
<DD>
|
||||||
|
Register a function to be called when an object becomes inaccessible.
|
||||||
|
This is often useful as a backup method for releasing system resources
|
||||||
|
(<I>e.g.</i> closing files) when the object referencing them becomes
|
||||||
|
inaccessible.
|
||||||
|
It is not an acceptable method to perform actions that must be performed
|
||||||
|
in a timely fashion.
|
||||||
|
See <A HREF="http://www.hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gch.txt">gc.h</a> for details of the interface.
|
||||||
|
See <A HREF="http://www.hpl.hp.com/personal/Hans_Boehm/gc/finalization.html">here</a> for a more detailed discussion
|
||||||
|
of the design.
|
||||||
|
<P>
|
||||||
|
Note that an object may become inaccessible before client code is done
|
||||||
|
operating on its fields. Suitable synchronization is usually required.
|
||||||
|
See <A HREF="http://portal.acm.org/citation.cfm?doid=604131.604153">here</a>
|
||||||
|
or <A HREF="http://www.hpl.hp.com/techreports/2002/HPL-2002-335.html">here</a>
|
||||||
|
for details.
|
||||||
|
</dl>
|
||||||
|
<P>
|
||||||
|
If you are concerned with multiprocessor performance and scalability,
|
||||||
|
you should consider enabling and using thread local allocation (<I>e.g.</i>
|
||||||
|
GC_LOCAL_MALLOC, see <TT>gc_local_alloc.h</tt>. If your platform
|
||||||
|
supports it, you should build the collector with parallel marking support
|
||||||
|
(-DPARALLEL_MARK, or --enable-parallel-mark).
|
||||||
|
<P>
|
||||||
|
If the collector is used in an environment in which pointer location
|
||||||
|
information for heap objects is easily available, this can be passed on
|
||||||
|
to the colllector using the interfaces in either <TT>gc_typed.h</tt>
|
||||||
|
or <TT>gc_gcj.h</tt>.
|
||||||
|
<P>
|
||||||
|
The collector distribution also includes a <B>string package</b> that takes
|
||||||
|
advantage of the collector. For details see
|
||||||
|
<A HREF="http://www.hpl.hp.com/personal/Hans_Boehm/gc/gc_source/cordh.txt">cord.h</a>
|
||||||
|
|
||||||
|
<H1>C++ Interface</h1>
|
||||||
|
There are three distinct ways to use the collector from C++:
|
||||||
|
<DL>
|
||||||
|
<DT> <B> STL allocators </b>
|
||||||
|
<DD>
|
||||||
|
Users of the <A HREF="http://www.sgi.com/tech/stl">SGI extended STL</a>
|
||||||
|
can include <TT>new_gc_alloc.h</tt> before including
|
||||||
|
STL header files.
|
||||||
|
(<TT>gc_alloc.h</tt> corresponds to now obsolete versions of the
|
||||||
|
SGI STL.)
|
||||||
|
This defines SGI-style allocators
|
||||||
|
<UL>
|
||||||
|
<LI> alloc
|
||||||
|
<LI> single_client_alloc
|
||||||
|
<LI> gc_alloc
|
||||||
|
<LI> single_client_gc_alloc
|
||||||
|
</ul>
|
||||||
|
which may be used either directly to allocate memory or to instantiate
|
||||||
|
container templates. The first two allocate uncollectable but traced
|
||||||
|
memory, while the second two allocate collectable memory.
|
||||||
|
The single_client versions are not safe for concurrent access by
|
||||||
|
multiple threads, but are faster.
|
||||||
|
<P>
|
||||||
|
For an example, click <A HREF="http://hpl.hp.com/personal/Hans_Boehm/gc/gc_alloc_exC.txt">here</a>.
|
||||||
|
<P>
|
||||||
|
Recent versions of the collector also include a more standard-conforming
|
||||||
|
allocator implemention in <TT>gc_allocator.h</tt>. It defines
|
||||||
|
<UL>
|
||||||
|
<LI> traceable_allocator
|
||||||
|
<LI> gc_allocator
|
||||||
|
</ul>
|
||||||
|
Again the former allocates uncollectable but traced memory.
|
||||||
|
This should work with any fully standard-conforming C++ compiler.
|
||||||
|
<DT> <B> Class inheritance based interface </b>
|
||||||
|
<DD>
|
||||||
|
Users may include gc_cpp.h and then cause members of certain classes to
|
||||||
|
be allocated in garbage collectable memory by inheriting from class gc.
|
||||||
|
For details see <A HREF="http://hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gc_cpph.txt">gc_cpp.h</a>.
|
||||||
|
<DT> <B> C interface </b>
|
||||||
|
<DD>
|
||||||
|
It is also possible to use the C interface from
|
||||||
|
<A HREF="http://hpl.hp.com/personal/Hans_Boehm/gc/gc_source/gch.txt">gc.h</a> directly.
|
||||||
|
On platforms which use malloc to implement ::new, it should usually be possible
|
||||||
|
to use a version of the collector that has been compiled as a malloc
|
||||||
|
replacement. It is also possible to replace ::new and other allocation
|
||||||
|
functions suitably.
|
||||||
|
<P>
|
||||||
|
Note that user-implemented small-block allocation often works poorly with
|
||||||
|
an underlying garbage-collected large block allocator, since the collector
|
||||||
|
has to view all objects accessible from the user's free list as reachable.
|
||||||
|
This is likely to cause problems if GC_malloc is used with something like
|
||||||
|
the original HP version of STL.
|
||||||
|
This approach works with the SGI versions of the STL only if the
|
||||||
|
<TT>malloc_alloc</tt> allocator is used.
|
||||||
|
</dl>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,197 @@
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<TITLE>Using the Garbage Collector as Leak Detector</title>
|
||||||
|
</head>
|
||||||
|
<BODY>
|
||||||
|
<H1>Using the Garbage Collector as Leak Detector</h1>
|
||||||
|
The garbage collector may be used as a leak detector.
|
||||||
|
In this case, the primary function of the collector is to report
|
||||||
|
objects that were allocated (typically with <TT>GC_MALLOC</tt>),
|
||||||
|
not deallocated (normally with <TT>GC_FREE</tt>), but are
|
||||||
|
no longer accessible. Since the object is no longer accessible,
|
||||||
|
there in normally no way to deallocate the object at a later time;
|
||||||
|
thus it can safely be assumed that the object has been "leaked".
|
||||||
|
<P>
|
||||||
|
This is substantially different from counting leak detectors,
|
||||||
|
which simply verify that all allocated objects are eventually
|
||||||
|
deallocated. A garbage-collector based leak detector can provide
|
||||||
|
somewhat more precise information when an object was leaked.
|
||||||
|
More importantly, it does not report objects that are never
|
||||||
|
deallocated because they are part of "permanent" data structures.
|
||||||
|
Thus it does not require all objects to be deallocated at process
|
||||||
|
exit time, a potentially useless activity that often triggers
|
||||||
|
large amounts of paging.
|
||||||
|
<P>
|
||||||
|
All non-ancient versions of the garbage collector provide
|
||||||
|
leak detection support. Version 5.3 adds the following
|
||||||
|
features:
|
||||||
|
<OL>
|
||||||
|
<LI> Leak detection mode can be initiated at run-time by
|
||||||
|
setting GC_find_leak instead of building the collector with FIND_LEAK
|
||||||
|
defined. This variable should be set to a nonzero value
|
||||||
|
at program startup.
|
||||||
|
<LI> Leaked objects should be reported and then correctly garbage collected.
|
||||||
|
Prior versions either reported leaks or functioned as a garbage collector.
|
||||||
|
</ol>
|
||||||
|
For the rest of this description we will give instructions that work
|
||||||
|
with any reasonable version of the collector.
|
||||||
|
<P>
|
||||||
|
To use the collector as a leak detector, follow the following steps:
|
||||||
|
<OL>
|
||||||
|
<LI> Build the collector with -DFIND_LEAK. Otherwise use default
|
||||||
|
build options.
|
||||||
|
<LI> Change the program so that all allocation and deallocation goes
|
||||||
|
through the garbage collector.
|
||||||
|
<LI> Arrange to call <TT>GC_gcollect</tt> at appropriate points to check
|
||||||
|
for leaks.
|
||||||
|
(For sufficiently long running programs, this will happen implicitly,
|
||||||
|
but probably not with sufficient frequency.)
|
||||||
|
</ol>
|
||||||
|
The second step can usually be accomplished with the
|
||||||
|
<TT>-DREDIRECT_MALLOC=GC_malloc</tt> option when the collector is built,
|
||||||
|
or by defining <TT>malloc</tt>, <TT>calloc</tt>,
|
||||||
|
<TT>realloc</tt> and <TT>free</tt>
|
||||||
|
to call the corresponding garbage collector functions.
|
||||||
|
But this, by itself, will not yield very informative diagnostics,
|
||||||
|
since the collector does not keep track of information about
|
||||||
|
how objects were allocated. The error reports will include
|
||||||
|
only object addresses.
|
||||||
|
<P>
|
||||||
|
For more precise error reports, as much of the program as possible
|
||||||
|
should use the all uppercase variants of these functions, after
|
||||||
|
defining <TT>GC_DEBUG</tt>, and then including <TT>gc.h</tt>.
|
||||||
|
In this environment <TT>GC_MALLOC</tt> is a macro which causes
|
||||||
|
at least the file name and line number at the allocation point to
|
||||||
|
be saved as part of the object. Leak reports will then also include
|
||||||
|
this information.
|
||||||
|
<P>
|
||||||
|
Many collector features (<I>e.g</i> stubborn objects, finalization,
|
||||||
|
and disappearing links) are less useful in this context, and are not
|
||||||
|
fully supported. Their use will usually generate additional bogus
|
||||||
|
leak reports, since the collector itself drops some associated objects.
|
||||||
|
<P>
|
||||||
|
The same is generally true of thread support. However, as of 6.0alpha4,
|
||||||
|
correct leak reports should be generated with linuxthreads.
|
||||||
|
<P>
|
||||||
|
On a few platforms (currently Solaris/SPARC, Irix, and, with -DSAVE_CALL_CHAIN,
|
||||||
|
Linux/X86), <TT>GC_MALLOC</tt>
|
||||||
|
also causes some more information about its call stack to be saved
|
||||||
|
in the object. Such information is reproduced in the error
|
||||||
|
reports in very non-symbolic form, but it can be very useful with the
|
||||||
|
aid of a debugger.
|
||||||
|
<H2>An Example</h2>
|
||||||
|
The following header file <TT>leak_detector.h</tt> is included in the
|
||||||
|
"include" subdirectory of the distribution:
|
||||||
|
<PRE>
|
||||||
|
#define GC_DEBUG
|
||||||
|
#include "gc.h"
|
||||||
|
#define malloc(n) GC_MALLOC(n)
|
||||||
|
#define calloc(m,n) GC_MALLOC((m)*(n))
|
||||||
|
#define free(p) GC_FREE(p)
|
||||||
|
#define realloc(p,n) GC_REALLOC((p),(n))
|
||||||
|
#define CHECK_LEAKS() GC_gcollect()
|
||||||
|
</pre>
|
||||||
|
<P>
|
||||||
|
Assume the collector has been built with -DFIND_LEAK. (For very
|
||||||
|
new versions of the collector, we could instead add the statement
|
||||||
|
<TT>GC_find_leak = 1</tt> as the first statement in <TT>main</tt>.
|
||||||
|
<P>
|
||||||
|
The program to be tested for leaks can then look like:
|
||||||
|
<PRE>
|
||||||
|
#include "leak_detector.h"
|
||||||
|
|
||||||
|
main() {
|
||||||
|
int *p[10];
|
||||||
|
int i;
|
||||||
|
/* GC_find_leak = 1; for new collector versions not */
|
||||||
|
/* compiled with -DFIND_LEAK. */
|
||||||
|
for (i = 0; i < 10; ++i) {
|
||||||
|
p[i] = malloc(sizeof(int)+i);
|
||||||
|
}
|
||||||
|
for (i = 1; i < 10; ++i) {
|
||||||
|
free(p[i]);
|
||||||
|
}
|
||||||
|
for (i = 0; i < 9; ++i) {
|
||||||
|
p[i] = malloc(sizeof(int)+i);
|
||||||
|
}
|
||||||
|
CHECK_LEAKS();
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
<P>
|
||||||
|
On an Intel X86 Linux system this produces on the stderr stream:
|
||||||
|
<PRE>
|
||||||
|
Leaked composite object at 0x806dff0 (leak_test.c:8, sz=4)
|
||||||
|
</pre>
|
||||||
|
(On most unmentioned operating systems, the output is similar to this.
|
||||||
|
If the collector had been built on Linux/X86 with -DSAVE_CALL_CHAIN,
|
||||||
|
the output would be closer to the Solaris example. For this to work,
|
||||||
|
the program should not be compiled with -fomit_frame_pointer.)
|
||||||
|
<P>
|
||||||
|
On Irix it reports
|
||||||
|
<PRE>
|
||||||
|
Leaked composite object at 0x10040fe0 (leak_test.c:8, sz=4)
|
||||||
|
Caller at allocation:
|
||||||
|
##PC##= 0x10004910
|
||||||
|
</pre>
|
||||||
|
and on Solaris the error report is
|
||||||
|
<PRE>
|
||||||
|
Leaked composite object at 0xef621fc8 (leak_test.c:8, sz=4)
|
||||||
|
Call chain at allocation:
|
||||||
|
args: 4 (0x4), 200656 (0x30FD0)
|
||||||
|
##PC##= 0x14ADC
|
||||||
|
args: 1 (0x1), -268436012 (0xEFFFFDD4)
|
||||||
|
##PC##= 0x14A64
|
||||||
|
</pre>
|
||||||
|
In the latter two cases some additional information is given about
|
||||||
|
how malloc was called when the leaked object was allocated. For
|
||||||
|
Solaris, the first line specifies the arguments to <TT>GC_debug_malloc</tt>
|
||||||
|
(the actual allocation routine), The second the program counter inside
|
||||||
|
main, the third the arguments to <TT>main</tt>, and finally the program
|
||||||
|
counter inside the caller to main (i.e. in the C startup code).
|
||||||
|
<P>
|
||||||
|
In the Irix case, only the address inside the caller to main is given.
|
||||||
|
<P>
|
||||||
|
In many cases, a debugger is needed to interpret the additional information.
|
||||||
|
On systems supporting the "adb" debugger, the <TT>callprocs</tt> script
|
||||||
|
can be used to replace program counter values with symbolic names.
|
||||||
|
As of version 6.1, the collector tries to generate symbolic names for
|
||||||
|
call stacks if it knows how to do so on the platform. This is true on
|
||||||
|
Linux/X86, but not on most other platforms.
|
||||||
|
<H2>Simplified leak detection under Linux</h2>
|
||||||
|
Since version 6.1, it should be possible to run the collector in leak
|
||||||
|
detection mode on a program a.out under Linux/X86 as follows:
|
||||||
|
<OL>
|
||||||
|
<LI> Ensure that a.out is a single-threaded executable. This doesn't yet work
|
||||||
|
for multithreaded programs.
|
||||||
|
<LI> If possible, ensure that the addr2line program is installed in
|
||||||
|
/usr/bin. (It comes with RedHat Linux.)
|
||||||
|
<LI> If possible, compile a.out with full debug information.
|
||||||
|
This will improve the quality of the leak reports. With this approach, it is
|
||||||
|
no longer necessary to call GC_ routines explicitly, though that can also
|
||||||
|
improve the quality of the leak reports.
|
||||||
|
<LI> Build the collector and install it in directory <I>foo</i> as follows:
|
||||||
|
<UL>
|
||||||
|
<LI> configure --prefix=<I>foo</i> --enable-full-debug --enable-redirect-malloc
|
||||||
|
--disable-threads
|
||||||
|
<LI> make
|
||||||
|
<LI> make install
|
||||||
|
</ul>
|
||||||
|
<LI> Set environment variables as follows:
|
||||||
|
<UL>
|
||||||
|
<LI> LD_PRELOAD=<I>foo</i>/lib/libgc.so
|
||||||
|
<LI> GC_FIND_LEAK
|
||||||
|
<LI> You may also want to set GC_PRINT_STATS (to confirm that the collector
|
||||||
|
is running) and/or GC_LOOP_ON_ABORT (to facilitate debugging from another
|
||||||
|
window if something goes wrong).
|
||||||
|
</ul
|
||||||
|
<LI> Simply run a.out as you normally would. Note that if you run anything
|
||||||
|
else (<I>e.g.</i> your editor) with those environment variables set,
|
||||||
|
it will also be leak tested. This may or may not be useful and/or
|
||||||
|
embarrassing. It can generate
|
||||||
|
mountains of leak reports if the application wasn't designed to avoid leaks,
|
||||||
|
<I>e.g.</i> because it's always short-lived.
|
||||||
|
</ol>
|
||||||
|
This has not yet been thropughly tested on large applications, but it's known
|
||||||
|
to do the right thing on at least some small ones.
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,210 @@
|
||||||
|
<HTML>
|
||||||
|
<HEAD>
|
||||||
|
<TITLE>Garbage collector scalability</TITLE>
|
||||||
|
</HEAD>
|
||||||
|
<BODY>
|
||||||
|
<H1>Garbage collector scalability</h1>
|
||||||
|
In its default configuration, the Boehm-Demers-Weiser garbage collector
|
||||||
|
is not thread-safe. It can be made thread-safe for a number of environments
|
||||||
|
by building the collector with the appropriate
|
||||||
|
<TT>-D</tt><I>XXX</i><TT>-THREADS</tt> compilation
|
||||||
|
flag. This has primarily two effects:
|
||||||
|
<OL>
|
||||||
|
<LI> It causes the garbage collector to stop all other threads when
|
||||||
|
it needs to see a consistent memory state.
|
||||||
|
<LI> It causes the collector to acquire a lock around essentially all
|
||||||
|
allocation and garbage collection activity.
|
||||||
|
</ol>
|
||||||
|
Since a single lock is used for all allocation-related activity, only one
|
||||||
|
thread can be allocating or collecting at one point. This inherently
|
||||||
|
limits performance of multi-threaded applications on multiprocessors.
|
||||||
|
<P>
|
||||||
|
On most platforms, the allocator/collector lock is implemented as a
|
||||||
|
spin lock with exponential back-off. Longer wait times are implemented
|
||||||
|
by yielding and/or sleeping. If a collection is in progress, the pure
|
||||||
|
spinning stage is skipped. This has the advantage that uncontested and
|
||||||
|
thus most uniprocessor lock acquisitions are very cheap. It has the
|
||||||
|
disadvantage that the application may sleep for small periods of time
|
||||||
|
even when there is work to be done. And threads may be unnecessarily
|
||||||
|
woken up for short periods. Nonetheless, this scheme empirically
|
||||||
|
outperforms native queue-based mutual exclusion implementations in most
|
||||||
|
cases, sometimes drastically so.
|
||||||
|
<H2>Options for enhanced scalability</h2>
|
||||||
|
Version 6.0 of the collector adds two facilities to enhance collector
|
||||||
|
scalability on multiprocessors. As of 6.0alpha1, these are supported
|
||||||
|
only under Linux on X86 and IA64 processors, though ports to other
|
||||||
|
otherwise supported Pthreads platforms should be straightforward.
|
||||||
|
They are intended to be used together.
|
||||||
|
<UL>
|
||||||
|
<LI>
|
||||||
|
Building the collector with <TT>-DPARALLEL_MARK</tt> allows the collector to
|
||||||
|
run the mark phase in parallel in multiple threads, and thus on multiple
|
||||||
|
processors. The mark phase typically consumes the large majority of the
|
||||||
|
collection time. Thus this largely parallelizes the garbage collector
|
||||||
|
itself, though not the allocation process. Currently the marking is
|
||||||
|
performed by the thread that triggered the collection, together with
|
||||||
|
<I>N</i>-1 dedicated
|
||||||
|
threads, where <I>N</i> is the number of processors detected by the collector.
|
||||||
|
The dedicated threads are created once at initialization time.
|
||||||
|
<P>
|
||||||
|
A second effect of this flag is to switch to a more concurrent
|
||||||
|
implementation of <TT>GC_malloc_many</tt>, so that free lists can be
|
||||||
|
built, and memory can be cleared, by more than one thread concurrently.
|
||||||
|
<LI>
|
||||||
|
Building the collector with -DTHREAD_LOCAL_ALLOC adds support for thread
|
||||||
|
local allocation. It does not, by itself, cause thread local allocation
|
||||||
|
to be used. It simply allows the use of the interface in
|
||||||
|
<TT>gc_local_alloc.h</tt>.
|
||||||
|
<P>
|
||||||
|
Memory returned from thread-local allocators is completely interchangeable
|
||||||
|
with that returned by the standard allocators. It may be used by other
|
||||||
|
threads. The only difference is that, if the thread allocates enough
|
||||||
|
memory of a certain kind, it will build a thread-local free list for
|
||||||
|
objects of that kind, and allocate from that. This greatly reduces
|
||||||
|
locking. The thread-local free lists are refilled using
|
||||||
|
<TT>GC_malloc_many</tt>.
|
||||||
|
<P>
|
||||||
|
An important side effect of this flag is to replace the default
|
||||||
|
spin-then-sleep lock to be replace by a spin-then-queue based implementation.
|
||||||
|
This <I>reduces performance</i> for the standard allocation functions,
|
||||||
|
though it usually improves performance when thread-local allocation is
|
||||||
|
used heavily, and thus the number of short-duration lock acquisitions
|
||||||
|
is greatly reduced.
|
||||||
|
</ul>
|
||||||
|
<P>
|
||||||
|
The easiest way to switch an application to thread-local allocation is to
|
||||||
|
<OL>
|
||||||
|
<LI> Define the macro <TT>GC_REDIRECT_TO_LOCAL</tt>,
|
||||||
|
and then include the <TT>gc.h</tt>
|
||||||
|
header in each client source file.
|
||||||
|
<LI> Invoke <TT>GC_thr_init()</tt> before any allocation.
|
||||||
|
<LI> Allocate using <TT>GC_MALLOC</tt>, <TT>GC_MALLOC_ATOMIC</tt>,
|
||||||
|
and/or <TT>GC_GCJ_MALLOC</tt>.
|
||||||
|
</ol>
|
||||||
|
<H2>The Parallel Marking Algorithm</h2>
|
||||||
|
We use an algorithm similar to
|
||||||
|
<A HREF="http://www.yl.is.s.u-tokyo.ac.jp/gc/">that developed by
|
||||||
|
Endo, Taura, and Yonezawa</a> at the University of Tokyo.
|
||||||
|
However, the data structures and implementation are different,
|
||||||
|
and represent a smaller change to the original collector source,
|
||||||
|
probably at the expense of extreme scalability. Some of
|
||||||
|
the refinements they suggest, <I>e.g.</i> splitting large
|
||||||
|
objects, were also incorporated into out approach.
|
||||||
|
<P>
|
||||||
|
The global mark stack is transformed into a global work queue.
|
||||||
|
Unlike the usual case, it never shrinks during a mark phase.
|
||||||
|
The mark threads remove objects from the queue by copying them to a
|
||||||
|
local mark stack and changing the global descriptor to zero, indicating
|
||||||
|
that there is no more work to be done for this entry.
|
||||||
|
This removal
|
||||||
|
is done with no synchronization. Thus it is possible for more than
|
||||||
|
one worker to remove the same entry, resulting in some work duplication.
|
||||||
|
<P>
|
||||||
|
The global work queue grows only if a marker thread decides to
|
||||||
|
return some of its local mark stack to the global one. This
|
||||||
|
is done if the global queue appears to be running low, or if
|
||||||
|
the local stack is in danger of overflowing. It does require
|
||||||
|
synchronization, but should be relatively rare.
|
||||||
|
<P>
|
||||||
|
The sequential marking code is reused to process local mark stacks.
|
||||||
|
Hence the amount of additional code required for parallel marking
|
||||||
|
is minimal.
|
||||||
|
<P>
|
||||||
|
It should be possible to use generational collection in the presence of the
|
||||||
|
parallel collector, by calling <TT>GC_enable_incremental()</tt>.
|
||||||
|
This does not result in fully incremental collection, since parallel mark
|
||||||
|
phases cannot currently be interrupted, and doing so may be too
|
||||||
|
expensive.
|
||||||
|
<P>
|
||||||
|
Gcj-style mark descriptors do not currently mix with the combination
|
||||||
|
of local allocation and incremental collection. They should work correctly
|
||||||
|
with one or the other, but not both.
|
||||||
|
<P>
|
||||||
|
The number of marker threads is set on startup to the number of
|
||||||
|
available processors (or to the value of the <TT>GC_NPROCS</tt>
|
||||||
|
environment variable). If only a single processor is detected,
|
||||||
|
parallel marking is disabled.
|
||||||
|
<P>
|
||||||
|
Note that setting GC_NPROCS to 1 also causes some lock acquisitions inside
|
||||||
|
the collector to immediately yield the processor instead of busy waiting
|
||||||
|
first. In the case of a multiprocessor and a client with multiple
|
||||||
|
simultaneously runnable threads, this may have disastrous performance
|
||||||
|
consequences (e.g. a factor of 10 slowdown).
|
||||||
|
<H2>Performance</h2>
|
||||||
|
We conducted some simple experiments with a version of
|
||||||
|
<A HREF="gc_bench.html">our GC benchmark</a> that was slightly modified to
|
||||||
|
run multiple concurrent client threads in the same address space.
|
||||||
|
Each client thread does the same work as the original benchmark, but they share
|
||||||
|
a heap.
|
||||||
|
This benchmark involves very little work outside of memory allocation.
|
||||||
|
This was run with GC 6.0alpha3 on a dual processor Pentium III/500 machine
|
||||||
|
under Linux 2.2.12.
|
||||||
|
<P>
|
||||||
|
Running with a thread-unsafe collector, the benchmark ran in 9
|
||||||
|
seconds. With the simple thread-safe collector,
|
||||||
|
built with <TT>-DLINUX_THREADS</tt>, the execution time
|
||||||
|
increased to 10.3 seconds, or 23.5 elapsed seconds with two clients.
|
||||||
|
(The times for the <TT>malloc</tt>/i<TT>free</tt> version
|
||||||
|
with glibc <TT>malloc</tt>
|
||||||
|
are 10.51 (standard library, pthreads not linked),
|
||||||
|
20.90 (one thread, pthreads linked),
|
||||||
|
and 24.55 seconds respectively. The benchmark favors a
|
||||||
|
garbage collector, since most objects are small.)
|
||||||
|
<P>
|
||||||
|
The following table gives execution times for the collector built
|
||||||
|
with parallel marking and thread-local allocation support
|
||||||
|
(<TT>-DGC_LINUX_THREADS -DPARALLEL_MARK -DTHREAD_LOCAL_ALLOC</tt>). We tested
|
||||||
|
the client using either one or two marker threads, and running
|
||||||
|
one or two client threads. Note that the client uses thread local
|
||||||
|
allocation exclusively. With -DTHREAD_LOCAL_ALLOC the collector
|
||||||
|
switches to a locking strategy that is better tuned to less frequent
|
||||||
|
lock acquisition. The standard allocation primitives thus peform
|
||||||
|
slightly worse than without -DTHREAD_LOCAL_ALLOC, and should be
|
||||||
|
avoided in time-critical code.
|
||||||
|
<P>
|
||||||
|
(The results using <TT>pthread_mutex_lock</tt>
|
||||||
|
directly for allocation locking would have been worse still, at
|
||||||
|
least for older versions of linuxthreads.
|
||||||
|
With THREAD_LOCAL_ALLOC, we first repeatedly try to acquire the
|
||||||
|
lock with pthread_mutex_try_lock(), busy_waiting between attempts.
|
||||||
|
After a fixed number of attempts, we use pthread_mutex_lock().)
|
||||||
|
<P>
|
||||||
|
These measurements do not use incremental collection, nor was prefetching
|
||||||
|
enabled in the marker. We used the C version of the benchmark.
|
||||||
|
All measurements are in elapsed seconds on an unloaded machine.
|
||||||
|
<P>
|
||||||
|
<TABLE BORDER ALIGN="CENTER">
|
||||||
|
<TR><TH>Number of threads</th><TH>1 marker thread (secs.)</th>
|
||||||
|
<TH>2 marker threads (secs.)</th></tr>
|
||||||
|
<TR><TD>1 client</td><TD ALIGN="CENTER">10.45</td><TD ALIGN="CENTER">7.85</td>
|
||||||
|
<TR><TD>2 clients</td><TD ALIGN="CENTER">19.95</td><TD ALIGN="CENTER">12.3</td>
|
||||||
|
</table>
|
||||||
|
<PP>
|
||||||
|
The execution time for the single threaded case is slightly worse than with
|
||||||
|
simple locking. However, even the single-threaded benchmark runs faster than
|
||||||
|
even the thread-unsafe version if a second processor is available.
|
||||||
|
The execution time for two clients with thread local allocation time is
|
||||||
|
only 1.4 times the sequential execution time for a single thread in a
|
||||||
|
thread-unsafe environment, even though it involves twice the client work.
|
||||||
|
That represents close to a
|
||||||
|
factor of 2 improvement over the 2 client case with the old collector.
|
||||||
|
The old collector clearly
|
||||||
|
still suffered from some contention overhead, in spite of the fact that the
|
||||||
|
locking scheme had been fairly well tuned.
|
||||||
|
<P>
|
||||||
|
Full linear speedup (i.e. the same execution time for 1 client on one
|
||||||
|
processor as 2 clients on 2 processors)
|
||||||
|
is probably not achievable on this kind of
|
||||||
|
hardware even with such a small number of processors,
|
||||||
|
since the memory system is
|
||||||
|
a major constraint for the garbage collector,
|
||||||
|
the processors usually share a single memory bus, and thus
|
||||||
|
the aggregate memory bandwidth does not increase in
|
||||||
|
proportion to the number of processors.
|
||||||
|
<P>
|
||||||
|
These results are likely to be very sensitive to both hardware and OS
|
||||||
|
issues. Preliminary experiments with an older Pentium Pro machine running
|
||||||
|
an older kernel were far less encouraging.
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,232 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 1996-1997
|
||||||
|
* Silicon Graphics Computer Systems, Inc.
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, distribute and sell this software
|
||||||
|
* and its documentation for any purpose is hereby granted without fee,
|
||||||
|
* provided that the above copyright notice appear in all copies and
|
||||||
|
* that both that copyright notice and this permission notice appear
|
||||||
|
* in supporting documentation. Silicon Graphics makes no
|
||||||
|
* representations about the suitability of this software for any
|
||||||
|
* purpose. It is provided "as is" without express or implied warranty.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2002
|
||||||
|
* Hewlett-Packard Company
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, distribute and sell this software
|
||||||
|
* and its documentation for any purpose is hereby granted without fee,
|
||||||
|
* provided that the above copyright notice appear in all copies and
|
||||||
|
* that both that copyright notice and this permission notice appear
|
||||||
|
* in supporting documentation. Hewlett-Packard Company makes no
|
||||||
|
* representations about the suitability of this software for any
|
||||||
|
* purpose. It is provided "as is" without express or implied warranty.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This implements standard-conforming allocators that interact with
|
||||||
|
* the garbage collector. Gc_alloctor<T> allocates garbage-collectable
|
||||||
|
* objects of type T. Traceable_allocator<T> allocates objects that
|
||||||
|
* are not temselves garbage collected, but are scanned by the
|
||||||
|
* collector for pointers to collectable objects. Traceable_alloc
|
||||||
|
* should be used for explicitly managed STL containers that may
|
||||||
|
* point to collectable objects.
|
||||||
|
*
|
||||||
|
* This code was derived from an earlier version of the GNU C++ standard
|
||||||
|
* library, which itself was derived from the SGI STL implementation.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gc.h" // For size_t
|
||||||
|
|
||||||
|
/* First some helpers to allow us to dispatch on whether or not a type
|
||||||
|
* is known to be pointerfree.
|
||||||
|
* These are private, except that the client may invoke the
|
||||||
|
* GC_DECLARE_PTRFREE macro.
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct GC_true_type {};
|
||||||
|
struct GC_false_type {};
|
||||||
|
|
||||||
|
template <class GC_tp>
|
||||||
|
struct GC_type_traits {
|
||||||
|
GC_false_type GC_is_ptr_free;
|
||||||
|
};
|
||||||
|
|
||||||
|
# define GC_DECLARE_PTRFREE(T) \
|
||||||
|
template<> struct GC_type_traits<T> { GC_true_type GC_is_ptr_free; }
|
||||||
|
|
||||||
|
GC_DECLARE_PTRFREE(signed char);
|
||||||
|
GC_DECLARE_PTRFREE(unsigned char);
|
||||||
|
GC_DECLARE_PTRFREE(signed short);
|
||||||
|
GC_DECLARE_PTRFREE(unsigned short);
|
||||||
|
GC_DECLARE_PTRFREE(signed int);
|
||||||
|
GC_DECLARE_PTRFREE(unsigned int);
|
||||||
|
GC_DECLARE_PTRFREE(signed long);
|
||||||
|
GC_DECLARE_PTRFREE(unsigned long);
|
||||||
|
GC_DECLARE_PTRFREE(float);
|
||||||
|
GC_DECLARE_PTRFREE(double);
|
||||||
|
/* The client may want to add others. */
|
||||||
|
|
||||||
|
// In the following GC_Tp is GC_true_type iff we are allocating a
|
||||||
|
// pointerfree object.
|
||||||
|
template <class GC_Tp>
|
||||||
|
inline void * GC_selective_alloc(size_t n, GC_Tp) {
|
||||||
|
return GC_MALLOC(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline void * GC_selective_alloc<GC_true_type>(size_t n, GC_true_type) {
|
||||||
|
return GC_MALLOC_ATOMIC(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now the public gc_allocator<T> class:
|
||||||
|
*/
|
||||||
|
template <class GC_Tp>
|
||||||
|
class gc_allocator {
|
||||||
|
public:
|
||||||
|
typedef size_t size_type;
|
||||||
|
typedef ptrdiff_t difference_type;
|
||||||
|
typedef GC_Tp* pointer;
|
||||||
|
typedef const GC_Tp* const_pointer;
|
||||||
|
typedef GC_Tp& reference;
|
||||||
|
typedef const GC_Tp& const_reference;
|
||||||
|
typedef GC_Tp value_type;
|
||||||
|
|
||||||
|
template <class GC_Tp1> struct rebind {
|
||||||
|
typedef gc_allocator<GC_Tp1> other;
|
||||||
|
};
|
||||||
|
|
||||||
|
gc_allocator() {}
|
||||||
|
# ifndef _MSC_VER
|
||||||
|
// I'm not sure why this is needed here in addition to the following.
|
||||||
|
// The standard specifies it for the standard allocator, but VC++ rejects
|
||||||
|
// it. -HB
|
||||||
|
gc_allocator(const gc_allocator&) throw() {}
|
||||||
|
# endif
|
||||||
|
template <class GC_Tp1> gc_allocator(const gc_allocator<GC_Tp1>&) throw() {}
|
||||||
|
~gc_allocator() throw() {}
|
||||||
|
|
||||||
|
pointer address(reference GC_x) const { return &GC_x; }
|
||||||
|
const_pointer address(const_reference GC_x) const { return &GC_x; }
|
||||||
|
|
||||||
|
// GC_n is permitted to be 0. The C++ standard says nothing about what
|
||||||
|
// the return value is when GC_n == 0.
|
||||||
|
GC_Tp* allocate(size_type GC_n, const void* = 0) {
|
||||||
|
GC_type_traits<GC_Tp> traits;
|
||||||
|
return static_cast<GC_Tp *>
|
||||||
|
(GC_selective_alloc(GC_n * sizeof(GC_Tp),
|
||||||
|
traits.GC_is_ptr_free));
|
||||||
|
}
|
||||||
|
|
||||||
|
// __p is not permitted to be a null pointer.
|
||||||
|
void deallocate(pointer __p, size_type GC_n)
|
||||||
|
{ GC_FREE(__p); }
|
||||||
|
|
||||||
|
size_type max_size() const throw()
|
||||||
|
{ return size_t(-1) / sizeof(GC_Tp); }
|
||||||
|
|
||||||
|
void construct(pointer __p, const GC_Tp& __val) { new(__p) GC_Tp(__val); }
|
||||||
|
void destroy(pointer __p) { __p->~GC_Tp(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class gc_allocator<void> {
|
||||||
|
typedef size_t size_type;
|
||||||
|
typedef ptrdiff_t difference_type;
|
||||||
|
typedef void* pointer;
|
||||||
|
typedef const void* const_pointer;
|
||||||
|
typedef void value_type;
|
||||||
|
|
||||||
|
template <class GC_Tp1> struct rebind {
|
||||||
|
typedef gc_allocator<GC_Tp1> other;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <class GC_T1, class GC_T2>
|
||||||
|
inline bool operator==(const gc_allocator<GC_T1>&, const gc_allocator<GC_T2>&)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class GC_T1, class GC_T2>
|
||||||
|
inline bool operator!=(const gc_allocator<GC_T1>&, const gc_allocator<GC_T2>&)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* And the public traceable_allocator class.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Note that we currently don't specialize the pointer-free case, since a
|
||||||
|
// pointer-free traceable container doesn't make that much sense,
|
||||||
|
// though it could become an issue due to abstraction boundaries.
|
||||||
|
template <class GC_Tp>
|
||||||
|
class traceable_allocator {
|
||||||
|
public:
|
||||||
|
typedef size_t size_type;
|
||||||
|
typedef ptrdiff_t difference_type;
|
||||||
|
typedef GC_Tp* pointer;
|
||||||
|
typedef const GC_Tp* const_pointer;
|
||||||
|
typedef GC_Tp& reference;
|
||||||
|
typedef const GC_Tp& const_reference;
|
||||||
|
typedef GC_Tp value_type;
|
||||||
|
|
||||||
|
template <class GC_Tp1> struct rebind {
|
||||||
|
typedef traceable_allocator<GC_Tp1> other;
|
||||||
|
};
|
||||||
|
|
||||||
|
traceable_allocator() throw() {}
|
||||||
|
# ifndef _MSC_VER
|
||||||
|
traceable_allocator(const traceable_allocator&) throw() {}
|
||||||
|
# endif
|
||||||
|
template <class GC_Tp1> traceable_allocator
|
||||||
|
(const traceable_allocator<GC_Tp1>&) throw() {}
|
||||||
|
~traceable_allocator() throw() {}
|
||||||
|
|
||||||
|
pointer address(reference GC_x) const { return &GC_x; }
|
||||||
|
const_pointer address(const_reference GC_x) const { return &GC_x; }
|
||||||
|
|
||||||
|
// GC_n is permitted to be 0. The C++ standard says nothing about what
|
||||||
|
// the return value is when GC_n == 0.
|
||||||
|
GC_Tp* allocate(size_type GC_n, const void* = 0) {
|
||||||
|
return static_cast<GC_Tp*>(GC_MALLOC_UNCOLLECTABLE(GC_n * sizeof(GC_Tp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// __p is not permitted to be a null pointer.
|
||||||
|
void deallocate(pointer __p, size_type GC_n)
|
||||||
|
{ GC_FREE(__p); }
|
||||||
|
|
||||||
|
size_type max_size() const throw()
|
||||||
|
{ return size_t(-1) / sizeof(GC_Tp); }
|
||||||
|
|
||||||
|
void construct(pointer __p, const GC_Tp& __val) { new(__p) GC_Tp(__val); }
|
||||||
|
void destroy(pointer __p) { __p->~GC_Tp(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<>
|
||||||
|
class traceable_allocator<void> {
|
||||||
|
typedef size_t size_type;
|
||||||
|
typedef ptrdiff_t difference_type;
|
||||||
|
typedef void* pointer;
|
||||||
|
typedef const void* const_pointer;
|
||||||
|
typedef void value_type;
|
||||||
|
|
||||||
|
template <class GC_Tp1> struct rebind {
|
||||||
|
typedef traceable_allocator<GC_Tp1> other;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <class GC_T1, class GC_T2>
|
||||||
|
inline bool operator==(const traceable_allocator<GC_T1>&, const traceable_allocator<GC_T2>&)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class GC_T1, class GC_T2>
|
||||||
|
inline bool operator!=(const traceable_allocator<GC_T1>&, const traceable_allocator<GC_T2>&)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,147 @@
|
||||||
|
/*
|
||||||
|
* This should never be included directly. It is included only from gc.h.
|
||||||
|
* We separate it only to make gc.h more suitable as documentation.
|
||||||
|
*
|
||||||
|
* Some tests for old macros. These violate our namespace rules and will
|
||||||
|
* disappear shortly. Use the GC_ names.
|
||||||
|
*/
|
||||||
|
#if defined(SOLARIS_THREADS) || defined(_SOLARIS_THREADS)
|
||||||
|
# define GC_SOLARIS_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(_SOLARIS_PTHREADS)
|
||||||
|
# define GC_SOLARIS_PTHREADS
|
||||||
|
#endif
|
||||||
|
#if defined(IRIX_THREADS)
|
||||||
|
# define GC_IRIX_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(DGUX_THREADS)
|
||||||
|
# if !defined(GC_DGUX386_THREADS)
|
||||||
|
# define GC_DGUX386_THREADS
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#if defined(AIX_THREADS)
|
||||||
|
# define GC_AIX_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(HPUX_THREADS)
|
||||||
|
# define GC_HPUX_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(OSF1_THREADS)
|
||||||
|
# define GC_OSF1_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(LINUX_THREADS)
|
||||||
|
# define GC_LINUX_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(WIN32_THREADS)
|
||||||
|
# define GC_WIN32_THREADS
|
||||||
|
#endif
|
||||||
|
#if defined(USE_LD_WRAP)
|
||||||
|
# define GC_USE_LD_WRAP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(_REENTRANT) && (defined(GC_SOLARIS_THREADS) \
|
||||||
|
|| defined(GC_SOLARIS_PTHREADS) \
|
||||||
|
|| defined(GC_HPUX_THREADS) \
|
||||||
|
|| defined(GC_AIX_THREADS) \
|
||||||
|
|| defined(GC_LINUX_THREADS))
|
||||||
|
# define _REENTRANT
|
||||||
|
/* Better late than never. This fails if system headers that */
|
||||||
|
/* depend on this were previously included. */
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GC_DGUX386_THREADS) && !defined(_POSIX4A_DRAFT10_SOURCE)
|
||||||
|
# define _POSIX4A_DRAFT10_SOURCE 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
# if defined(GC_SOLARIS_PTHREADS) || defined(GC_FREEBSD_THREADS) || \
|
||||||
|
defined(GC_IRIX_THREADS) || defined(GC_LINUX_THREADS) || \
|
||||||
|
defined(GC_HPUX_THREADS) || defined(GC_OSF1_THREADS) || \
|
||||||
|
defined(GC_DGUX386_THREADS) || defined(GC_DARWIN_THREADS) || \
|
||||||
|
defined(GC_AIX_THREADS) || \
|
||||||
|
(defined(GC_WIN32_THREADS) && defined(__CYGWIN32__))
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#if defined(GC_THREADS) && !defined(GC_PTHREADS)
|
||||||
|
# if defined(__linux__)
|
||||||
|
# define GC_LINUX_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if !defined(LINUX) && (defined(_PA_RISC1_1) || defined(_PA_RISC2_0) \
|
||||||
|
|| defined(hppa) || defined(__HPPA))
|
||||||
|
# define GC_HPUX_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if !defined(__linux__) && (defined(__alpha) || defined(__alpha__))
|
||||||
|
# define GC_OSF1_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if defined(__mips) && !defined(__linux__)
|
||||||
|
# define GC_IRIX_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if defined(__sparc) && !defined(__linux__)
|
||||||
|
# define GC_SOLARIS_PTHREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if defined(__APPLE__) && defined(__MACH__) && defined(__ppc__)
|
||||||
|
# define GC_DARWIN_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if !defined(GC_PTHREADS) && defined(__FreeBSD__)
|
||||||
|
# define GC_FREEBSD_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
# if defined(DGUX) && (defined(i386) || defined(__i386__))
|
||||||
|
# define GC_DGUX386_THREADS
|
||||||
|
# define GC_PTHREADS
|
||||||
|
# endif
|
||||||
|
#endif /* GC_THREADS */
|
||||||
|
|
||||||
|
#if defined(GC_THREADS) && !defined(GC_PTHREADS) && defined(MSWIN32)
|
||||||
|
# define GC_WIN32_THREADS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(GC_SOLARIS_PTHREADS) && !defined(GC_SOLARIS_THREADS)
|
||||||
|
# define GC_SOLARIS_THREADS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
# define __GC
|
||||||
|
# include <stddef.h>
|
||||||
|
# ifdef _WIN32_WCE
|
||||||
|
/* Yet more kluges for WinCE */
|
||||||
|
# include <stdlib.h> /* size_t is defined here */
|
||||||
|
typedef long ptrdiff_t; /* ptrdiff_t is not defined */
|
||||||
|
# endif
|
||||||
|
|
||||||
|
#if defined(_DLL) && !defined(GC_NOT_DLL) && !defined(GC_DLL)
|
||||||
|
# define GC_DLL
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) && defined(GC_DLL)
|
||||||
|
# ifdef GC_BUILD
|
||||||
|
# define GC_API __declspec(dllexport)
|
||||||
|
# else
|
||||||
|
# define GC_API __declspec(dllimport)
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (defined(__DMC__) || defined(_MSC_VER)) && defined(GC_DLL)
|
||||||
|
# ifdef GC_BUILD
|
||||||
|
# define GC_API extern __declspec(dllexport)
|
||||||
|
# else
|
||||||
|
# define GC_API __declspec(dllimport)
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__WATCOMC__) && defined(GC_DLL)
|
||||||
|
# ifdef GC_BUILD
|
||||||
|
# define GC_API extern __declspec(dllexport)
|
||||||
|
# else
|
||||||
|
# define GC_API extern __declspec(dllimport)
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GC_API
|
||||||
|
#define GC_API extern
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
#ifndef GC_DARWIN_SEMAPHORE_H
|
||||||
|
#define GC_DARWIN_SEMAPHORE_H
|
||||||
|
|
||||||
|
#if !defined(GC_DARWIN_THREADS)
|
||||||
|
#error darwin_semaphore.h included with GC_DARWIN_THREADS not defined
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
This is a very simple semaphore implementation for darwin. It
|
||||||
|
is implemented in terms of pthreads calls so it isn't async signal
|
||||||
|
safe. This isn't a problem because signals aren't used to
|
||||||
|
suspend threads on darwin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
pthread_mutex_t mutex;
|
||||||
|
pthread_cond_t cond;
|
||||||
|
int value;
|
||||||
|
} sem_t;
|
||||||
|
|
||||||
|
static int sem_init(sem_t *sem, int pshared, int value) {
|
||||||
|
int ret;
|
||||||
|
if(pshared)
|
||||||
|
GC_abort("sem_init with pshared set");
|
||||||
|
sem->value = value;
|
||||||
|
|
||||||
|
ret = pthread_mutex_init(&sem->mutex,NULL);
|
||||||
|
if(ret < 0) return -1;
|
||||||
|
ret = pthread_cond_init(&sem->cond,NULL);
|
||||||
|
if(ret < 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sem_post(sem_t *sem) {
|
||||||
|
if(pthread_mutex_lock(&sem->mutex) < 0)
|
||||||
|
return -1;
|
||||||
|
sem->value++;
|
||||||
|
if(pthread_cond_signal(&sem->cond) < 0) {
|
||||||
|
pthread_mutex_unlock(&sem->mutex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(pthread_mutex_unlock(&sem->mutex) < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sem_wait(sem_t *sem) {
|
||||||
|
if(pthread_mutex_lock(&sem->mutex) < 0)
|
||||||
|
return -1;
|
||||||
|
while(sem->value == 0) {
|
||||||
|
pthread_cond_wait(&sem->cond,&sem->mutex);
|
||||||
|
}
|
||||||
|
sem->value--;
|
||||||
|
if(pthread_mutex_unlock(&sem->mutex) < 0)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int sem_destroy(sem_t *sem) {
|
||||||
|
int ret;
|
||||||
|
ret = pthread_cond_destroy(&sem->cond);
|
||||||
|
if(ret < 0) return -1;
|
||||||
|
ret = pthread_mutex_destroy(&sem->mutex);
|
||||||
|
if(ret < 0) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,15 @@
|
||||||
|
#ifndef GC_DARWIN_STOP_WORLD_H
|
||||||
|
#define GC_DARWIN_STOP_WORLD_H
|
||||||
|
|
||||||
|
#if !defined(GC_DARWIN_THREADS)
|
||||||
|
#error darwin_stop_world.h included without GC_DARWIN_THREADS defined
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <mach/mach.h>
|
||||||
|
#include <mach/thread_act.h>
|
||||||
|
|
||||||
|
struct thread_stop_info {
|
||||||
|
mach_port_t mach_thread;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef GC_PTHREAD_STOP_WORLD_H
|
||||||
|
#define GC_PTHREAD_STOP_WORLD_H
|
||||||
|
|
||||||
|
struct thread_stop_info {
|
||||||
|
int signal;
|
||||||
|
word last_stop_count; /* GC_last_stop_count value when thread */
|
||||||
|
/* last successfully handled a suspend */
|
||||||
|
/* signal. */
|
||||||
|
ptr_t stack_ptr; /* Valid only when stopped. */
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef GC_PTHREAD_SUPPORT_H
|
||||||
|
#define GC_PTHREAD_SUPPORT_H
|
||||||
|
|
||||||
|
# include "private/gc_priv.h"
|
||||||
|
|
||||||
|
# if defined(GC_PTHREADS) && !defined(GC_SOLARIS_THREADS) \
|
||||||
|
&& !defined(GC_IRIX_THREADS) && !defined(GC_WIN32_THREADS)
|
||||||
|
|
||||||
|
#if defined(GC_DARWIN_THREADS)
|
||||||
|
# include "private/darwin_stop_world.h"
|
||||||
|
#else
|
||||||
|
# include "private/pthread_stop_world.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* We use the allocation lock to protect thread-related data structures. */
|
||||||
|
|
||||||
|
/* The set of all known threads. We intercept thread creation and */
|
||||||
|
/* joins. */
|
||||||
|
/* Protected by allocation/GC lock. */
|
||||||
|
/* Some of this should be declared volatile, but that's inconsistent */
|
||||||
|
/* 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;
|
||||||
|
/* Extra bookkeeping information the stopping code uses */
|
||||||
|
struct thread_stop_info stop_info;
|
||||||
|
|
||||||
|
short 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. */
|
||||||
|
short thread_blocked; /* Protected by GC lock. */
|
||||||
|
/* Treated as a boolean value. If set, */
|
||||||
|
/* thread will acquire GC lock before */
|
||||||
|
/* doing any pointer manipulations, and */
|
||||||
|
/* has set its sp value. Thus it does */
|
||||||
|
/* not need to be sent a signal to stop */
|
||||||
|
/* it. */
|
||||||
|
ptr_t stack_end; /* Cold end of the stack. */
|
||||||
|
# ifdef IA64
|
||||||
|
ptr_t backing_store_end;
|
||||||
|
ptr_t backing_store_ptr;
|
||||||
|
# endif
|
||||||
|
void * status; /* The value returned from the thread. */
|
||||||
|
/* Used only to avoid premature */
|
||||||
|
/* reclamation of any data it might */
|
||||||
|
/* reference. */
|
||||||
|
# ifdef THREAD_LOCAL_ALLOC
|
||||||
|
# if CPP_WORDSZ == 64 && defined(ALIGN_DOUBLE)
|
||||||
|
# define GRANULARITY 16
|
||||||
|
# define NFREELISTS 49
|
||||||
|
# else
|
||||||
|
# define GRANULARITY 8
|
||||||
|
# define NFREELISTS 65
|
||||||
|
# endif
|
||||||
|
/* The ith free list corresponds to size i*GRANULARITY */
|
||||||
|
# define INDEX_FROM_BYTES(n) ((ADD_SLOP(n) + GRANULARITY - 1)/GRANULARITY)
|
||||||
|
# define BYTES_FROM_INDEX(i) ((i) * GRANULARITY - EXTRA_BYTES)
|
||||||
|
# define SMALL_ENOUGH(bytes) (ADD_SLOP(bytes) <= \
|
||||||
|
(NFREELISTS-1)*GRANULARITY)
|
||||||
|
ptr_t ptrfree_freelists[NFREELISTS];
|
||||||
|
ptr_t normal_freelists[NFREELISTS];
|
||||||
|
# ifdef GC_GCJ_SUPPORT
|
||||||
|
ptr_t gcj_freelists[NFREELISTS];
|
||||||
|
# endif
|
||||||
|
/* Free lists contain either a pointer or a small count */
|
||||||
|
/* reflecting the number of granules allocated at that */
|
||||||
|
/* size. */
|
||||||
|
/* 0 ==> thread-local allocation in use, free list */
|
||||||
|
/* empty. */
|
||||||
|
/* > 0, <= DIRECT_GRANULES ==> Using global allocation, */
|
||||||
|
/* too few objects of this size have been */
|
||||||
|
/* allocated by this thread. */
|
||||||
|
/* >= HBLKSIZE => pointer to nonempty free list. */
|
||||||
|
/* > DIRECT_GRANULES, < HBLKSIZE ==> transition to */
|
||||||
|
/* local alloc, equivalent to 0. */
|
||||||
|
# define DIRECT_GRANULES (HBLKSIZE/GRANULARITY)
|
||||||
|
/* Don't use local free lists for up to this much */
|
||||||
|
/* allocation. */
|
||||||
|
# endif
|
||||||
|
} * GC_thread;
|
||||||
|
|
||||||
|
# define THREAD_TABLE_SZ 128 /* Must be power of 2 */
|
||||||
|
extern volatile GC_thread GC_threads[THREAD_TABLE_SZ];
|
||||||
|
|
||||||
|
extern GC_bool GC_thr_initialized;
|
||||||
|
|
||||||
|
GC_thread GC_lookup_thread(pthread_t id);
|
||||||
|
|
||||||
|
void GC_stop_init();
|
||||||
|
|
||||||
|
#endif /* GC_PTHREADS && !GC_SOLARIS_THREADS.... etc */
|
||||||
|
#endif /* GC_PTHREAD_SUPPORT_H */
|
|
@ -0,0 +1,336 @@
|
||||||
|
#! /bin/sh
|
||||||
|
# Common stub for a few missing GNU programs while installing.
|
||||||
|
# Copyright 1996, 1997, 1999, 2000 Free Software Foundation, Inc.
|
||||||
|
# Originally by Fran,cois Pinard <pinard@iro.umontreal.ca>, 1996.
|
||||||
|
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2, or (at your option)
|
||||||
|
# any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||||
|
# 02111-1307, USA.
|
||||||
|
|
||||||
|
# As a special exception to the GNU General Public License, if you
|
||||||
|
# distribute this file as part of a program that contains a
|
||||||
|
# configuration script generated by Autoconf, you may include it under
|
||||||
|
# the same distribution terms that you use for the rest of that program.
|
||||||
|
|
||||||
|
if test $# -eq 0; then
|
||||||
|
echo 1>&2 "Try \`$0 --help' for more information"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
run=:
|
||||||
|
|
||||||
|
# In the cases where this matters, `missing' is being run in the
|
||||||
|
# srcdir already.
|
||||||
|
if test -f configure.ac; then
|
||||||
|
configure_ac=configure.ac
|
||||||
|
else
|
||||||
|
configure_ac=configure.in
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
--run)
|
||||||
|
# Try to run requested program, and just exit if it succeeds.
|
||||||
|
run=
|
||||||
|
shift
|
||||||
|
"$@" && exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# If it does not exist, or fails to run (possibly an outdated version),
|
||||||
|
# try to emulate it.
|
||||||
|
case "$1" in
|
||||||
|
|
||||||
|
-h|--h|--he|--hel|--help)
|
||||||
|
echo "\
|
||||||
|
$0 [OPTION]... PROGRAM [ARGUMENT]...
|
||||||
|
|
||||||
|
Handle \`PROGRAM [ARGUMENT]...' for when PROGRAM is missing, or return an
|
||||||
|
error status if there is no known handling for PROGRAM.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help display this help and exit
|
||||||
|
-v, --version output version information and exit
|
||||||
|
--run try to run the given command, and emulate it if it fails
|
||||||
|
|
||||||
|
Supported PROGRAM values:
|
||||||
|
aclocal touch file \`aclocal.m4'
|
||||||
|
autoconf touch file \`configure'
|
||||||
|
autoheader touch file \`config.h.in'
|
||||||
|
automake touch all \`Makefile.in' files
|
||||||
|
bison create \`y.tab.[ch]', if possible, from existing .[ch]
|
||||||
|
flex create \`lex.yy.c', if possible, from existing .c
|
||||||
|
help2man touch the output file
|
||||||
|
lex create \`lex.yy.c', if possible, from existing .c
|
||||||
|
makeinfo touch the output file
|
||||||
|
tar try tar, gnutar, gtar, then tar without non-portable flags
|
||||||
|
yacc create \`y.tab.[ch]', if possible, from existing .[ch]"
|
||||||
|
;;
|
||||||
|
|
||||||
|
-v|--v|--ve|--ver|--vers|--versi|--versio|--version)
|
||||||
|
echo "missing 0.4 - GNU automake"
|
||||||
|
;;
|
||||||
|
|
||||||
|
-*)
|
||||||
|
echo 1>&2 "$0: Unknown \`$1' option"
|
||||||
|
echo 1>&2 "Try \`$0 --help' for more information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
aclocal*)
|
||||||
|
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
|
||||||
|
# We have it, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified \`acinclude.m4' or \`${configure_ac}'. You might want
|
||||||
|
to install the \`Automake' and \`Perl' packages. Grab them from
|
||||||
|
any GNU archive site."
|
||||||
|
touch aclocal.m4
|
||||||
|
;;
|
||||||
|
|
||||||
|
autoconf)
|
||||||
|
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
|
||||||
|
# We have it, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified \`${configure_ac}'. You might want to install the
|
||||||
|
\`Autoconf' and \`GNU m4' packages. Grab them from any GNU
|
||||||
|
archive site."
|
||||||
|
touch configure
|
||||||
|
;;
|
||||||
|
|
||||||
|
autoheader)
|
||||||
|
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
|
||||||
|
# We have it, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified \`acconfig.h' or \`${configure_ac}'. You might want
|
||||||
|
to install the \`Autoconf' and \`GNU m4' packages. Grab them
|
||||||
|
from any GNU archive site."
|
||||||
|
files=`sed -n 's/^[ ]*A[CM]_CONFIG_HEADER(\([^)]*\)).*/\1/p' ${configure_ac}`
|
||||||
|
test -z "$files" && files="config.h"
|
||||||
|
touch_files=
|
||||||
|
for f in $files; do
|
||||||
|
case "$f" in
|
||||||
|
*:*) touch_files="$touch_files "`echo "$f" |
|
||||||
|
sed -e 's/^[^:]*://' -e 's/:.*//'`;;
|
||||||
|
*) touch_files="$touch_files $f.in";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
touch $touch_files
|
||||||
|
;;
|
||||||
|
|
||||||
|
automake*)
|
||||||
|
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
|
||||||
|
# We have it, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified \`Makefile.am', \`acinclude.m4' or \`${configure_ac}'.
|
||||||
|
You might want to install the \`Automake' and \`Perl' packages.
|
||||||
|
Grab them from any GNU archive site."
|
||||||
|
find . -type f -name Makefile.am -print |
|
||||||
|
sed 's/\.am$/.in/' |
|
||||||
|
while read f; do touch "$f"; done
|
||||||
|
;;
|
||||||
|
|
||||||
|
autom4te)
|
||||||
|
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
|
||||||
|
# We have it, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is needed, and you do not seem to have it handy on your
|
||||||
|
system. You might have modified some files without having the
|
||||||
|
proper tools for further handling them.
|
||||||
|
You can get \`$1Help2man' as part of \`Autoconf' from any GNU
|
||||||
|
archive site."
|
||||||
|
|
||||||
|
file=`echo "$*" | sed -n 's/.*--output[ =]*\([^ ]*\).*/\1/p'`
|
||||||
|
test -z "$file" && file=`echo "$*" | sed -n 's/.*-o[ ]*\([^ ]*\).*/\1/p'`
|
||||||
|
if test -f "$file"; then
|
||||||
|
touch $file
|
||||||
|
else
|
||||||
|
test -z "$file" || exec >$file
|
||||||
|
echo "#! /bin/sh"
|
||||||
|
echo "# Created by GNU Automake missing as a replacement of"
|
||||||
|
echo "# $ $@"
|
||||||
|
echo "exit 0"
|
||||||
|
chmod +x $file
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
bison|yacc)
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified a \`.y' file. You may need the \`Bison' package
|
||||||
|
in order for those modifications to take effect. You can get
|
||||||
|
\`Bison' from any GNU archive site."
|
||||||
|
rm -f y.tab.c y.tab.h
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
eval LASTARG="\${$#}"
|
||||||
|
case "$LASTARG" in
|
||||||
|
*.y)
|
||||||
|
SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'`
|
||||||
|
if [ -f "$SRCFILE" ]; then
|
||||||
|
cp "$SRCFILE" y.tab.c
|
||||||
|
fi
|
||||||
|
SRCFILE=`echo "$LASTARG" | sed 's/y$/h/'`
|
||||||
|
if [ -f "$SRCFILE" ]; then
|
||||||
|
cp "$SRCFILE" y.tab.h
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ ! -f y.tab.h ]; then
|
||||||
|
echo >y.tab.h
|
||||||
|
fi
|
||||||
|
if [ ! -f y.tab.c ]; then
|
||||||
|
echo 'main() { return 0; }' >y.tab.c
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
lex|flex)
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified a \`.l' file. You may need the \`Flex' package
|
||||||
|
in order for those modifications to take effect. You can get
|
||||||
|
\`Flex' from any GNU archive site."
|
||||||
|
rm -f lex.yy.c
|
||||||
|
if [ $# -ne 1 ]; then
|
||||||
|
eval LASTARG="\${$#}"
|
||||||
|
case "$LASTARG" in
|
||||||
|
*.l)
|
||||||
|
SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'`
|
||||||
|
if [ -f "$SRCFILE" ]; then
|
||||||
|
cp "$SRCFILE" lex.yy.c
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
if [ ! -f lex.yy.c ]; then
|
||||||
|
echo 'main() { return 0; }' >lex.yy.c
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
help2man)
|
||||||
|
if test -z "$run" && ($1 --version) > /dev/null 2>&1; then
|
||||||
|
# We have it, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified a dependency of a manual page. You may need the
|
||||||
|
\`Help2man' package in order for those modifications to take
|
||||||
|
effect. You can get \`Help2man' from any GNU archive site."
|
||||||
|
|
||||||
|
file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
|
||||||
|
if test -z "$file"; then
|
||||||
|
file=`echo "$*" | sed -n 's/.*--output=\([^ ]*\).*/\1/p'`
|
||||||
|
fi
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
touch $file
|
||||||
|
else
|
||||||
|
test -z "$file" || exec >$file
|
||||||
|
echo ".ab help2man is required to generate this page"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
makeinfo)
|
||||||
|
if test -z "$run" && (makeinfo --version) > /dev/null 2>&1; then
|
||||||
|
# We have makeinfo, but it failed.
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is missing on your system. You should only need it if
|
||||||
|
you modified a \`.texi' or \`.texinfo' file, or any other file
|
||||||
|
indirectly affecting the aspect of the manual. The spurious
|
||||||
|
call might also be the consequence of using a buggy \`make' (AIX,
|
||||||
|
DU, IRIX). You might want to install the \`Texinfo' package or
|
||||||
|
the \`GNU make' package. Grab either from any GNU archive site."
|
||||||
|
file=`echo "$*" | sed -n 's/.*-o \([^ ]*\).*/\1/p'`
|
||||||
|
if test -z "$file"; then
|
||||||
|
file=`echo "$*" | sed 's/.* \([^ ]*\) *$/\1/'`
|
||||||
|
file=`sed -n '/^@setfilename/ { s/.* \([^ ]*\) *$/\1/; p; q; }' $file`
|
||||||
|
fi
|
||||||
|
touch $file
|
||||||
|
;;
|
||||||
|
|
||||||
|
tar)
|
||||||
|
shift
|
||||||
|
if test -n "$run"; then
|
||||||
|
echo 1>&2 "ERROR: \`tar' requires --run"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# We have already tried tar in the generic part.
|
||||||
|
# Look for gnutar/gtar before invocation to avoid ugly error
|
||||||
|
# messages.
|
||||||
|
if (gnutar --version > /dev/null 2>&1); then
|
||||||
|
gnutar ${1+"$@"} && exit 0
|
||||||
|
fi
|
||||||
|
if (gtar --version > /dev/null 2>&1); then
|
||||||
|
gtar ${1+"$@"} && exit 0
|
||||||
|
fi
|
||||||
|
firstarg="$1"
|
||||||
|
if shift; then
|
||||||
|
case "$firstarg" in
|
||||||
|
*o*)
|
||||||
|
firstarg=`echo "$firstarg" | sed s/o//`
|
||||||
|
tar "$firstarg" ${1+"$@"} && exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$firstarg" in
|
||||||
|
*h*)
|
||||||
|
firstarg=`echo "$firstarg" | sed s/h//`
|
||||||
|
tar "$firstarg" ${1+"$@"} && exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: I can't seem to be able to run \`tar' with the given arguments.
|
||||||
|
You may want to install GNU tar or Free paxutils, or check the
|
||||||
|
command line arguments."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo 1>&2 "\
|
||||||
|
WARNING: \`$1' is needed, and you do not seem to have it handy on your
|
||||||
|
system. You might have modified some files without having the
|
||||||
|
proper tools for further handling them. Check the \`README' file,
|
||||||
|
it often tells you about the needed prerequirements for installing
|
||||||
|
this package. You may also peek at any GNU archive site, in case
|
||||||
|
some other package would contain this missing \`$1' program."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
|
@ -0,0 +1,84 @@
|
||||||
|
|
||||||
|
; GC_push_regs function. Under some optimization levels GCC will clobber
|
||||||
|
; some of the non-volatile registers before we get a chance to save them
|
||||||
|
; therefore, this can't be inline asm.
|
||||||
|
|
||||||
|
.text
|
||||||
|
.align 2
|
||||||
|
.globl _GC_push_regs
|
||||||
|
_GC_push_regs:
|
||||||
|
|
||||||
|
; Prolog
|
||||||
|
mflr r0
|
||||||
|
stw r0,8(r1)
|
||||||
|
stwu r1,-80(r1)
|
||||||
|
|
||||||
|
; Push r13-r31
|
||||||
|
mr r3,r13
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r14
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r15
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r16
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r17
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r18
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r19
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r20
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r21
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r22
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r23
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r24
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r25
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r26
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r27
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r28
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r29
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r30
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
mr r3,r31
|
||||||
|
bl L_GC_push_one$stub
|
||||||
|
|
||||||
|
;
|
||||||
|
lwz r0,88(r1)
|
||||||
|
addi r1,r1,80
|
||||||
|
mtlr r0
|
||||||
|
|
||||||
|
; Return
|
||||||
|
blr
|
||||||
|
|
||||||
|
; PIC stuff, generated by GCC
|
||||||
|
|
||||||
|
.data
|
||||||
|
.picsymbol_stub
|
||||||
|
L_GC_push_one$stub:
|
||||||
|
.indirect_symbol _GC_push_one
|
||||||
|
mflr r0
|
||||||
|
bcl 20,31,L0$_GC_push_one
|
||||||
|
L0$_GC_push_one:
|
||||||
|
mflr r11
|
||||||
|
addis r11,r11,ha16(L_GC_push_one$lazy_ptr-L0$_GC_push_one)
|
||||||
|
mtlr r0
|
||||||
|
lwz r12,lo16(L_GC_push_one$lazy_ptr-L0$_GC_push_one)(r11)
|
||||||
|
mtctr r12
|
||||||
|
addi r11,r11,lo16(L_GC_push_one$lazy_ptr-L0$_GC_push_one)
|
||||||
|
bctr
|
||||||
|
.data
|
||||||
|
.lazy_symbol_pointer
|
||||||
|
L_GC_push_one$lazy_ptr:
|
||||||
|
.indirect_symbol _GC_push_one
|
||||||
|
.long dyld_stub_binding_helper
|
|
@ -0,0 +1,445 @@
|
||||||
|
#include "private/pthread_support.h"
|
||||||
|
|
||||||
|
#if defined(GC_PTHREADS) && !defined(GC_SOLARIS_THREADS) \
|
||||||
|
&& !defined(GC_IRIX_THREADS) && !defined(GC_WIN32_THREADS) \
|
||||||
|
&& !defined(GC_DARWIN_THREADS) && !defined(GC_AIX_THREADS)
|
||||||
|
|
||||||
|
#include <signal.h>
|
||||||
|
#include <semaphore.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
|
||||||
|
#ifndef NSIG
|
||||||
|
# if defined(MAXSIG)
|
||||||
|
# define NSIG (MAXSIG+1)
|
||||||
|
# elif defined(_NSIG)
|
||||||
|
# define NSIG _NSIG
|
||||||
|
# elif defined(__SIGRTMAX)
|
||||||
|
# define NSIG (__SIGRTMAX+1)
|
||||||
|
# else
|
||||||
|
--> please fix it
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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 < NSIG; i++) {
|
||||||
|
if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
|
||||||
|
}
|
||||||
|
GC_printf0("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
word GC_stop_count; /* Incremented at the beginning of GC_stop_world. */
|
||||||
|
|
||||||
|
#ifdef GC_OSF1_THREADS
|
||||||
|
GC_bool GC_retry_signals = TRUE;
|
||||||
|
#else
|
||||||
|
GC_bool GC_retry_signals = FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We use signals to stop threads during GC.
|
||||||
|
*
|
||||||
|
* Suspended threads wait in signal handler for SIG_THR_RESTART.
|
||||||
|
* That's more portable than semaphores or condition variables.
|
||||||
|
* (We do use sem_post from a signal handler, but that should be portable.)
|
||||||
|
*
|
||||||
|
* The thread suspension signal SIG_SUSPEND is now defined in gc_priv.h.
|
||||||
|
* Note that we can't just stop a thread; we need it to save its stack
|
||||||
|
* pointer(s) and acknowledge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef SIG_THR_RESTART
|
||||||
|
# if defined(GC_HPUX_THREADS) || defined(GC_OSF1_THREADS)
|
||||||
|
# ifdef _SIGRTMIN
|
||||||
|
# define SIG_THR_RESTART _SIGRTMIN + 5
|
||||||
|
# else
|
||||||
|
# define SIG_THR_RESTART SIGRTMIN + 5
|
||||||
|
# endif
|
||||||
|
# else
|
||||||
|
# define SIG_THR_RESTART SIGXCPU
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
sem_t GC_suspend_ack_sem;
|
||||||
|
|
||||||
|
void GC_suspend_handler(int sig)
|
||||||
|
{
|
||||||
|
int dummy;
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
GC_thread me;
|
||||||
|
sigset_t mask;
|
||||||
|
# ifdef PARALLEL_MARK
|
||||||
|
word my_mark_no = GC_mark_no;
|
||||||
|
/* Marker can't proceed until we acknowledge. Thus this is */
|
||||||
|
/* guaranteed to be the mark_no correspending to our */
|
||||||
|
/* suspension, i.e. the marker can't have incremented it yet. */
|
||||||
|
# endif
|
||||||
|
word my_stop_count = GC_stop_count;
|
||||||
|
|
||||||
|
if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Suspending 0x%lx\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. */
|
||||||
|
if (me -> stop_info.last_stop_count == my_stop_count) {
|
||||||
|
/* Duplicate signal. OK if we are retrying. */
|
||||||
|
if (!GC_retry_signals) {
|
||||||
|
WARN("Duplicate suspend signal in thread %lx\n",
|
||||||
|
pthread_self());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
# ifdef SPARC
|
||||||
|
me -> stop_info.stack_ptr = (ptr_t)GC_save_regs_in_stack();
|
||||||
|
# else
|
||||||
|
me -> stop_info.stack_ptr = (ptr_t)(&dummy);
|
||||||
|
# endif
|
||||||
|
# ifdef IA64
|
||||||
|
me -> backing_store_ptr = (ptr_t)GC_save_regs_in_stack();
|
||||||
|
# endif
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
me -> stop_info.last_stop_count = my_stop_count;
|
||||||
|
|
||||||
|
/* Wait until that thread tells us to restart by sending */
|
||||||
|
/* this thread a SIG_THR_RESTART signal. */
|
||||||
|
/* SIG_THR_RESTART should be masked at this point. Thus there */
|
||||||
|
/* is no race. */
|
||||||
|
if (sigfillset(&mask) != 0) ABORT("sigfillset() failed");
|
||||||
|
if (sigdelset(&mask, SIG_THR_RESTART) != 0) ABORT("sigdelset() failed");
|
||||||
|
# ifdef NO_SIGNALS
|
||||||
|
if (sigdelset(&mask, SIGINT) != 0) ABORT("sigdelset() failed");
|
||||||
|
if (sigdelset(&mask, SIGQUIT) != 0) ABORT("sigdelset() failed");
|
||||||
|
if (sigdelset(&mask, SIGTERM) != 0) ABORT("sigdelset() failed");
|
||||||
|
if (sigdelset(&mask, SIGABRT) != 0) ABORT("sigdelset() failed");
|
||||||
|
# endif
|
||||||
|
do {
|
||||||
|
me->stop_info.signal = 0;
|
||||||
|
sigsuspend(&mask); /* Wait for signal */
|
||||||
|
} while (me->stop_info.signal != SIG_THR_RESTART);
|
||||||
|
/* If the RESTART signal gets lost, we can still lose. That should be */
|
||||||
|
/* less likely than losing the SUSPEND signal, since we don't do much */
|
||||||
|
/* between the sem_post and sigsuspend. */
|
||||||
|
/* We'd need more handshaking to work around that, since we don't want */
|
||||||
|
/* to accidentally leave a RESTART signal pending, thus causing us to */
|
||||||
|
/* continue prematurely in a future round. */
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Continuing 0x%lx\n", my_thread);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GC_restart_handler(int sig)
|
||||||
|
{
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
GC_thread me;
|
||||||
|
|
||||||
|
if (sig != SIG_THR_RESTART) ABORT("Bad signal in suspend_handler");
|
||||||
|
|
||||||
|
/* Let the GC_suspend_handler() know that we got a SIG_THR_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(my_thread);
|
||||||
|
me->stop_info.signal = SIG_THR_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%lx\n", pthread_self());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
# ifdef IA64
|
||||||
|
# define IF_IA64(x) x
|
||||||
|
# else
|
||||||
|
# define IF_IA64(x)
|
||||||
|
# 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()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
GC_thread p;
|
||||||
|
ptr_t lo, hi;
|
||||||
|
/* On IA64, we also need to scan the register backing store. */
|
||||||
|
IF_IA64(ptr_t bs_lo; ptr_t bs_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)) {
|
||||||
|
# ifdef SPARC
|
||||||
|
lo = (ptr_t)GC_save_regs_in_stack();
|
||||||
|
# else
|
||||||
|
lo = GC_approx_sp();
|
||||||
|
# endif
|
||||||
|
IF_IA64(bs_hi = (ptr_t)GC_save_regs_in_stack();)
|
||||||
|
} else {
|
||||||
|
lo = p -> stop_info.stack_ptr;
|
||||||
|
IF_IA64(bs_hi = p -> backing_store_ptr;)
|
||||||
|
}
|
||||||
|
if ((p -> flags & MAIN_THREAD) == 0) {
|
||||||
|
hi = p -> stack_end;
|
||||||
|
IF_IA64(bs_lo = p -> backing_store_end);
|
||||||
|
} else {
|
||||||
|
/* The original stack. */
|
||||||
|
hi = GC_stackbottom;
|
||||||
|
IF_IA64(bs_lo = BACKING_STORE_BASE;)
|
||||||
|
}
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf3("Stack for thread 0x%lx = [%lx,%lx)\n",
|
||||||
|
(unsigned long) p -> id,
|
||||||
|
(unsigned long) lo, (unsigned long) hi);
|
||||||
|
#endif
|
||||||
|
if (0 == lo) ABORT("GC_push_all_stacks: sp not set!\n");
|
||||||
|
# ifdef STACK_GROWS_UP
|
||||||
|
/* We got them backwards! */
|
||||||
|
GC_push_all_stack(hi, lo);
|
||||||
|
# else
|
||||||
|
GC_push_all_stack(lo, hi);
|
||||||
|
# endif
|
||||||
|
# ifdef IA64
|
||||||
|
if (pthread_equal(p -> id, me)) {
|
||||||
|
GC_push_all_eager(bs_lo, bs_hi);
|
||||||
|
} else {
|
||||||
|
GC_push_all_stack(bs_lo, bs_hi);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* There seems to be a very rare thread stopping problem. To help us */
|
||||||
|
/* debug that, we save the ids of the stopping thread. */
|
||||||
|
pthread_t GC_stopping_thread;
|
||||||
|
int GC_stopping_pid;
|
||||||
|
|
||||||
|
/* We hold the allocation lock. Suspend all threads that might */
|
||||||
|
/* still be running. Return the number of suspend signals that */
|
||||||
|
/* were sent. */
|
||||||
|
int GC_suspend_all()
|
||||||
|
{
|
||||||
|
int n_live_threads = 0;
|
||||||
|
int i;
|
||||||
|
GC_thread p;
|
||||||
|
int result;
|
||||||
|
pthread_t my_thread = pthread_self();
|
||||||
|
|
||||||
|
GC_stopping_thread = my_thread; /* debugging only. */
|
||||||
|
GC_stopping_pid = getpid(); /* debugging only. */
|
||||||
|
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;
|
||||||
|
if (p -> stop_info.last_stop_count == GC_stop_count) continue;
|
||||||
|
if (p -> thread_blocked) /* Will wait */ continue;
|
||||||
|
n_live_threads++;
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Sending suspend signal to 0x%lx\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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n_live_threads;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caller holds allocation lock. */
|
||||||
|
void GC_stop_world()
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int n_live_threads;
|
||||||
|
int code;
|
||||||
|
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Stopping the world from 0x%lx\n", pthread_self());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Make sure all free list construction has stopped before we start. */
|
||||||
|
/* No new construction can start, since free list construction is */
|
||||||
|
/* required to acquire and release the GC lock before it starts, */
|
||||||
|
/* and we have the lock. */
|
||||||
|
# ifdef PARALLEL_MARK
|
||||||
|
GC_acquire_mark_lock();
|
||||||
|
GC_ASSERT(GC_fl_builder_count == 0);
|
||||||
|
/* We should have previously waited for it to become zero. */
|
||||||
|
# endif /* PARALLEL_MARK */
|
||||||
|
++GC_stop_count;
|
||||||
|
n_live_threads = GC_suspend_all();
|
||||||
|
|
||||||
|
if (GC_retry_signals) {
|
||||||
|
unsigned long wait_usecs = 0; /* Total wait since retry. */
|
||||||
|
# define WAIT_UNIT 3000
|
||||||
|
# define RETRY_INTERVAL 100000
|
||||||
|
for (;;) {
|
||||||
|
int ack_count;
|
||||||
|
|
||||||
|
sem_getvalue(&GC_suspend_ack_sem, &ack_count);
|
||||||
|
if (ack_count == n_live_threads) break;
|
||||||
|
if (wait_usecs > RETRY_INTERVAL) {
|
||||||
|
int newly_sent = GC_suspend_all();
|
||||||
|
|
||||||
|
# ifdef CONDPRINT
|
||||||
|
if (GC_print_stats) {
|
||||||
|
GC_printf1("Resent %ld signals after timeout\n",
|
||||||
|
newly_sent);
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
sem_getvalue(&GC_suspend_ack_sem, &ack_count);
|
||||||
|
if (newly_sent < n_live_threads - ack_count) {
|
||||||
|
WARN("Lost some threads during GC_stop_world?!\n",0);
|
||||||
|
n_live_threads = ack_count + newly_sent;
|
||||||
|
}
|
||||||
|
wait_usecs = 0;
|
||||||
|
}
|
||||||
|
usleep(WAIT_UNIT);
|
||||||
|
wait_usecs += WAIT_UNIT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (i = 0; i < n_live_threads; i++) {
|
||||||
|
if (0 != (code = sem_wait(&GC_suspend_ack_sem))) {
|
||||||
|
GC_err_printf1("Sem_wait returned %ld\n", (unsigned long)code);
|
||||||
|
ABORT("sem_wait for handler failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# ifdef PARALLEL_MARK
|
||||||
|
GC_release_mark_lock();
|
||||||
|
# endif
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("World stopped from 0x%lx\n", pthread_self());
|
||||||
|
#endif
|
||||||
|
GC_stopping_thread = 0; /* debugging only */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Caller holds allocation lock, and has held it continuously since */
|
||||||
|
/* the world stopped. */
|
||||||
|
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 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;
|
||||||
|
if (p -> thread_blocked) continue;
|
||||||
|
n_live_threads++;
|
||||||
|
#if DEBUG_THREADS
|
||||||
|
GC_printf1("Sending restart signal to 0x%lx\n", p -> id);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
result = pthread_kill(p -> id, SIG_THR_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
|
||||||
|
}
|
||||||
|
|
||||||
|
void GC_stop_init() {
|
||||||
|
struct sigaction act;
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
# ifdef NO_SIGNALS
|
||||||
|
if (sigdelset(&act.sa_mask, SIGINT) != 0
|
||||||
|
|| sigdelset(&act.sa_mask, SIGQUIT != 0)
|
||||||
|
|| sigdelset(&act.sa_mask, SIGABRT != 0)
|
||||||
|
|| sigdelset(&act.sa_mask, SIGTERM != 0)) {
|
||||||
|
ABORT("sigdelset() failed");
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
|
/* SIG_THR_RESTART is unmasked by the handler when necessary. */
|
||||||
|
act.sa_handler = GC_suspend_handler;
|
||||||
|
if (sigaction(SIG_SUSPEND, &act, NULL) != 0) {
|
||||||
|
ABORT("Cannot set SIG_SUSPEND handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
act.sa_handler = GC_restart_handler;
|
||||||
|
if (sigaction(SIG_THR_RESTART, &act, NULL) != 0) {
|
||||||
|
ABORT("Cannot set SIG_THR_RESTART handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check for GC_RETRY_SIGNALS. */
|
||||||
|
if (0 != GETENV("GC_RETRY_SIGNALS")) {
|
||||||
|
GC_retry_signals = TRUE;
|
||||||
|
}
|
||||||
|
if (0 != GETENV("GC_NO_RETRY_SIGNALS")) {
|
||||||
|
GC_retry_signals = FALSE;
|
||||||
|
}
|
||||||
|
# ifdef CONDPRINT
|
||||||
|
if (GC_print_stats && GC_retry_signals) {
|
||||||
|
GC_printf0("Will retry suspend signal if necessary.\n");
|
||||||
|
}
|
||||||
|
# endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue