Initial revision
From-SVN: r30324
This commit is contained in:
parent
d9bba9c3ed
commit
fd6a6309db
9 changed files with 3400 additions and 0 deletions
2087
boehm-gc/NT_THREADS_MAKEFILE
Normal file
2087
boehm-gc/NT_THREADS_MAKEFILE
Normal file
File diff suppressed because it is too large
Load diff
56
boehm-gc/backptr.h
Normal file
56
boehm-gc/backptr.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* This is a simple API to implement pointer back tracing, i.e.
|
||||
* to answer questions such as "who is pointing to this" or
|
||||
* "why is this object being retained by the collector"
|
||||
*
|
||||
* This API assumes that we have an ANSI C compiler.
|
||||
*
|
||||
* Most of these calls yield useful information on only after
|
||||
* a garbage collection. Usually the client will first force
|
||||
* a full collection and then gather information, preferably
|
||||
* before much intervening allocation.
|
||||
*
|
||||
* The implementation of the interface is only about 99.9999%
|
||||
* correct. It is intended to be good enough for profiling,
|
||||
* but is not intended to be used with production code.
|
||||
*
|
||||
* Results are likely to be much more useful if all allocation is
|
||||
* accomplished through the debugging allocators.
|
||||
*
|
||||
* The implementation idea is due to A. Demers.
|
||||
*/
|
||||
|
||||
/* Store information about the object referencing dest in *base_p */
|
||||
/* and *offset_p. */
|
||||
/* If multiple objects or roots point to dest, the one reported */
|
||||
/* will be the last on used by the garbage collector to trace the */
|
||||
/* object. */
|
||||
/* source is root ==> *base_p = address, *offset_p = 0 */
|
||||
/* source is heap object ==> *base_p != 0, *offset_p = offset */
|
||||
/* Returns 1 on success, 0 if source couldn't be determined. */
|
||||
/* Dest can be any address within a heap object. */
|
||||
typedef enum { GC_UNREFERENCED, /* No refence info available. */
|
||||
GC_NO_SPACE, /* Dest not allocated with debug alloc */
|
||||
GC_REFD_FROM_ROOT, /* Referenced directly by root *base_p */
|
||||
GC_REFD_FROM_HEAP, /* Referenced from another heap obj. */
|
||||
GC_FINALIZER_REFD /* Finalizable and hence accessible. */
|
||||
} GC_ref_kind;
|
||||
|
||||
GC_ref_kind GC_get_back_ptr_info(void *dest, void **base_p, size_t *offset_p);
|
||||
|
||||
/* Generate a random heap address. */
|
||||
/* The resulting address is in the heap, but */
|
||||
/* not necessarily inside a valid object. */
|
||||
void * GC_generate_random_heap_address(void);
|
||||
|
||||
/* Generate a random address inside a valid marked heap object. */
|
||||
void * GC_generate_random_valid_address(void);
|
||||
|
||||
/* Force a garbage collection and generate a backtrace from a */
|
||||
/* random heap address. */
|
||||
/* This uses the GC logging mechanism (GC_printf) to produce */
|
||||
/* output. It can often be called from a debugger. The */
|
||||
/* source in dbg_mlc.c also serves as a sample client. */
|
||||
void GC_generate_random_backtrace(void);
|
||||
|
||||
|
745
boehm-gc/hpux_irix_threads.c
Normal file
745
boehm-gc/hpux_irix_threads.c
Normal file
|
@ -0,0 +1,745 @@
|
|||
/*
|
||||
* Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved.
|
||||
* Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved.
|
||||
* Copyright (c) 1999 by Hewlett-Packard Company. All rights reserved.
|
||||
*
|
||||
* THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
|
||||
* OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
|
||||
*
|
||||
* Permission is hereby granted to use or copy this program
|
||||
* for any purpose, provided the above notices are retained on all copies.
|
||||
* Permission to modify the code and to distribute modified code is granted,
|
||||
* provided the above notices are retained, and a notice that the code was
|
||||
* modified is included with the above copyright notice.
|
||||
*/
|
||||
/*
|
||||
* Support code for Irix (>=6.2) Pthreads. This relies on properties
|
||||
* not guaranteed by the Pthread standard. It may or may not be portable
|
||||
* to other implementations.
|
||||
*
|
||||
* This now also includes an initial attempt at thread support for
|
||||
* HP/UX 11.
|
||||
*
|
||||
* Note that there is a lot of code duplication between linux_threads.c
|
||||
* and hpux_irix_threads.c; any changes made here may need to be reflected
|
||||
* there too.
|
||||
*/
|
||||
|
||||
# if defined(IRIX_THREADS) || defined(HPUX_THREADS)
|
||||
|
||||
# if defined(HPUX_THREADS)
|
||||
# include <sys/semaphore.h>
|
||||
# endif
|
||||
|
||||
# include "gc_priv.h"
|
||||
# include <pthread.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
|
||||
|
||||
void GC_thr_init();
|
||||
|
||||
#if 0
|
||||
void GC_print_sig_mask()
|
||||
{
|
||||
sigset_t blocked;
|
||||
int i;
|
||||
|
||||
if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
|
||||
ABORT("pthread_sigmask");
|
||||
GC_printf0("Blocked: ");
|
||||
for (i = 1; i <= MAXSIG; i++) {
|
||||
if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
|
||||
}
|
||||
GC_printf0("\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* We use the allocation lock to protect thread-related data structures. */
|
||||
|
||||
/* The set of all known threads. We intercept thread creation and */
|
||||
/* joins. We never actually create detached threads. We allocate all */
|
||||
/* new thread stacks ourselves. These allow us to maintain this */
|
||||
/* data structure. */
|
||||
/* Protected by GC_thr_lock. */
|
||||
/* Some of this should be declared volatile, but that's incosnsistent */
|
||||
/* with some library routine declarations. */
|
||||
typedef struct GC_Thread_Rep {
|
||||
struct GC_Thread_Rep * next; /* More recently allocated threads */
|
||||
/* with a given pthread id come */
|
||||
/* first. (All but the first are */
|
||||
/* guaranteed to be dead, but we may */
|
||||
/* not yet have registered the join.) */
|
||||
pthread_t id;
|
||||
word stop;
|
||||
# define NOT_STOPPED 0
|
||||
# define PLEASE_STOP 1
|
||||
# define STOPPED 2
|
||||
word flags;
|
||||
# define FINISHED 1 /* Thread has exited. */
|
||||
# define DETACHED 2 /* Thread is intended to be detached. */
|
||||
# define CLIENT_OWNS_STACK 4
|
||||
/* Stack was supplied by client. */
|
||||
ptr_t stack;
|
||||
ptr_t stack_ptr; /* Valid only when stopped. */
|
||||
/* But must be within stack region at */
|
||||
/* all times. */
|
||||
size_t stack_size; /* 0 for original thread. */
|
||||
void * status; /* Used only to avoid premature */
|
||||
/* reclamation of any data it might */
|
||||
/* reference. */
|
||||
} * GC_thread;
|
||||
|
||||
GC_thread GC_lookup_thread(pthread_t id);
|
||||
|
||||
/*
|
||||
* The only way to suspend threads given the pthread interface is to send
|
||||
* signals. Unfortunately, this means we have to reserve
|
||||
* a signal, and intercept client calls to change the signal mask.
|
||||
*/
|
||||
# define SIG_SUSPEND (SIGRTMIN + 6)
|
||||
|
||||
pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
/* Number of threads stopped so far */
|
||||
pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER;
|
||||
pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
void GC_suspend_handler(int sig)
|
||||
{
|
||||
int dummy;
|
||||
GC_thread me;
|
||||
sigset_t all_sigs;
|
||||
sigset_t old_sigs;
|
||||
int i;
|
||||
|
||||
if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
|
||||
me = GC_lookup_thread(pthread_self());
|
||||
/* The lookup here is safe, since I'm doing this on behalf */
|
||||
/* of a thread which holds the allocation lock in order */
|
||||
/* to stop the world. Thus concurrent modification of the */
|
||||
/* data structure is impossible. */
|
||||
if (PLEASE_STOP != me -> stop) {
|
||||
/* Misdirected signal. */
|
||||
pthread_mutex_unlock(&GC_suspend_lock);
|
||||
return;
|
||||
}
|
||||
pthread_mutex_lock(&GC_suspend_lock);
|
||||
me -> stack_ptr = (ptr_t)(&dummy);
|
||||
me -> stop = STOPPED;
|
||||
pthread_cond_signal(&GC_suspend_ack_cv);
|
||||
pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock);
|
||||
pthread_mutex_unlock(&GC_suspend_lock);
|
||||
/* GC_printf1("Continuing 0x%x\n", pthread_self()); */
|
||||
}
|
||||
|
||||
|
||||
GC_bool GC_thr_initialized = FALSE;
|
||||
|
||||
size_t GC_min_stack_sz;
|
||||
|
||||
size_t GC_page_sz;
|
||||
|
||||
# define N_FREE_LISTS 25
|
||||
ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 };
|
||||
/* GC_stack_free_lists[i] is free list for stacks of */
|
||||
/* size GC_min_stack_sz*2**i. */
|
||||
/* Free lists are linked through first word. */
|
||||
|
||||
/* Return a stack of size at least *stack_size. *stack_size is */
|
||||
/* replaced by the actual stack size. */
|
||||
/* Caller holds allocation lock. */
|
||||
ptr_t GC_stack_alloc(size_t * stack_size)
|
||||
{
|
||||
register size_t requested_sz = *stack_size;
|
||||
register size_t search_sz = GC_min_stack_sz;
|
||||
register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */
|
||||
register ptr_t result;
|
||||
|
||||
while (search_sz < requested_sz) {
|
||||
search_sz *= 2;
|
||||
index++;
|
||||
}
|
||||
if ((result = GC_stack_free_lists[index]) == 0
|
||||
&& (result = GC_stack_free_lists[index+1]) != 0) {
|
||||
/* Try next size up. */
|
||||
search_sz *= 2; index++;
|
||||
}
|
||||
if (result != 0) {
|
||||
GC_stack_free_lists[index] = *(ptr_t *)result;
|
||||
} else {
|
||||
result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_sz);
|
||||
result = (ptr_t)(((word)result + GC_page_sz) & ~(GC_page_sz - 1));
|
||||
/* Protect hottest page to detect overflow. */
|
||||
# ifdef STACK_GROWS_UP
|
||||
/* mprotect(result + search_sz, GC_page_sz, PROT_NONE); */
|
||||
# else
|
||||
/* mprotect(result, GC_page_sz, PROT_NONE); */
|
||||
result += GC_page_sz;
|
||||
# endif
|
||||
}
|
||||
*stack_size = search_sz;
|
||||
return(result);
|
||||
}
|
||||
|
||||
/* Caller holds allocation lock. */
|
||||
void GC_stack_free(ptr_t stack, size_t size)
|
||||
{
|
||||
register int index = 0;
|
||||
register size_t search_sz = GC_min_stack_sz;
|
||||
|
||||
while (search_sz < size) {
|
||||
search_sz *= 2;
|
||||
index++;
|
||||
}
|
||||
if (search_sz != size) ABORT("Bad stack size");
|
||||
*(ptr_t *)stack = GC_stack_free_lists[index];
|
||||
GC_stack_free_lists[index] = stack;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# define THREAD_TABLE_SZ 128 /* Must be power of 2 */
|
||||
volatile GC_thread GC_threads[THREAD_TABLE_SZ];
|
||||
|
||||
/* Add a thread to GC_threads. We assume it wasn't already there. */
|
||||
/* Caller holds allocation lock. */
|
||||
GC_thread GC_new_thread(pthread_t id)
|
||||
{
|
||||
int hv = ((word)id) % THREAD_TABLE_SZ;
|
||||
GC_thread result;
|
||||
static struct GC_Thread_Rep first_thread;
|
||||
static GC_bool first_thread_used = FALSE;
|
||||
|
||||
if (!first_thread_used) {
|
||||
result = &first_thread;
|
||||
first_thread_used = TRUE;
|
||||
/* Dont acquire allocation lock, since we may already hold it. */
|
||||
} else {
|
||||
result = (struct GC_Thread_Rep *)
|
||||
GC_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. */
|
||||
void GC_delete_thread(pthread_t id)
|
||||
{
|
||||
int hv = ((word)id) % THREAD_TABLE_SZ;
|
||||
register GC_thread p = GC_threads[hv];
|
||||
register GC_thread prev = 0;
|
||||
|
||||
while (!pthread_equal(p -> id, id)) {
|
||||
prev = p;
|
||||
p = p -> next;
|
||||
}
|
||||
if (prev == 0) {
|
||||
GC_threads[hv] = p -> next;
|
||||
} else {
|
||||
prev -> next = p -> next;
|
||||
}
|
||||
}
|
||||
|
||||
/* If a thread has been joined, but we have not yet */
|
||||
/* been notified, then there may be more than one thread */
|
||||
/* in the table with the same pthread id. */
|
||||
/* This is OK, but we need a way to delete a specific one. */
|
||||
void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
|
||||
{
|
||||
int hv = ((word)id) % THREAD_TABLE_SZ;
|
||||
register GC_thread p = GC_threads[hv];
|
||||
register GC_thread prev = 0;
|
||||
|
||||
while (p != gc_id) {
|
||||
prev = p;
|
||||
p = p -> next;
|
||||
}
|
||||
if (prev == 0) {
|
||||
GC_threads[hv] = p -> next;
|
||||
} else {
|
||||
prev -> next = p -> next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return a GC_thread corresponding to a given thread_t. */
|
||||
/* Returns 0 if it's not there. */
|
||||
/* Caller holds allocation lock or otherwise inhibits */
|
||||
/* updates. */
|
||||
/* If there is more than one thread with the given id we */
|
||||
/* return the most recent one. */
|
||||
GC_thread GC_lookup_thread(pthread_t id)
|
||||
{
|
||||
int hv = ((word)id) % THREAD_TABLE_SZ;
|
||||
register GC_thread p = GC_threads[hv];
|
||||
|
||||
while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
|
||||
return(p);
|
||||
}
|
||||
|
||||
|
||||
/* Caller holds allocation lock. */
|
||||
void GC_stop_world()
|
||||
{
|
||||
pthread_t my_thread = pthread_self();
|
||||
register int i;
|
||||
register GC_thread p;
|
||||
register int result;
|
||||
struct timespec timeout;
|
||||
|
||||
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||
if (p -> id != my_thread) {
|
||||
if (p -> flags & FINISHED) {
|
||||
p -> stop = STOPPED;
|
||||
continue;
|
||||
}
|
||||
p -> stop = PLEASE_STOP;
|
||||
result = pthread_kill(p -> id, SIG_SUSPEND);
|
||||
/* GC_printf1("Sent signal to 0x%x\n", p -> id); */
|
||||
switch(result) {
|
||||
case ESRCH:
|
||||
/* Not really there anymore. Possible? */
|
||||
p -> stop = STOPPED;
|
||||
break;
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
ABORT("pthread_kill failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&GC_suspend_lock);
|
||||
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||
while (p -> id != my_thread && p -> stop != STOPPED) {
|
||||
clock_gettime(CLOCK_REALTIME, &timeout);
|
||||
timeout.tv_nsec += 50000000; /* 50 msecs */
|
||||
if (timeout.tv_nsec >= 1000000000) {
|
||||
timeout.tv_nsec -= 1000000000;
|
||||
++timeout.tv_sec;
|
||||
}
|
||||
result = pthread_cond_timedwait(&GC_suspend_ack_cv,
|
||||
&GC_suspend_lock,
|
||||
&timeout);
|
||||
if (result == ETIMEDOUT) {
|
||||
/* Signal was lost or misdirected. Try again. */
|
||||
/* Duplicate signals should be benign. */
|
||||
result = pthread_kill(p -> id, SIG_SUSPEND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&GC_suspend_lock);
|
||||
/* GC_printf1("World stopped 0x%x\n", pthread_self()); */
|
||||
}
|
||||
|
||||
/* Caller holds allocation lock. */
|
||||
void GC_start_world()
|
||||
{
|
||||
GC_thread p;
|
||||
unsigned i;
|
||||
|
||||
/* GC_printf0("World starting\n"); */
|
||||
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||
p -> stop = NOT_STOPPED;
|
||||
}
|
||||
}
|
||||
pthread_mutex_lock(&GC_suspend_lock);
|
||||
/* All other threads are at pthread_cond_wait in signal handler. */
|
||||
/* Otherwise we couldn't have acquired the lock. */
|
||||
pthread_mutex_unlock(&GC_suspend_lock);
|
||||
pthread_cond_broadcast(&GC_continue_cv);
|
||||
}
|
||||
|
||||
# ifdef MMAP_STACKS
|
||||
--> not really supported yet.
|
||||
int GC_is_thread_stack(ptr_t addr)
|
||||
{
|
||||
register int i;
|
||||
register GC_thread p;
|
||||
|
||||
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||
if (p -> stack_size != 0) {
|
||||
if (p -> stack <= addr &&
|
||||
addr < p -> stack + p -> stack_size)
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
# endif
|
||||
|
||||
/* We hold allocation lock. We assume the world is stopped. */
|
||||
void GC_push_all_stacks()
|
||||
{
|
||||
register int i;
|
||||
register GC_thread p;
|
||||
register ptr_t sp = GC_approx_sp();
|
||||
register ptr_t hot, cold;
|
||||
pthread_t me = pthread_self();
|
||||
|
||||
if (!GC_thr_initialized) GC_thr_init();
|
||||
/* GC_printf1("Pushing stacks from thread 0x%x\n", me); */
|
||||
for (i = 0; i < THREAD_TABLE_SZ; i++) {
|
||||
for (p = GC_threads[i]; p != 0; p = p -> next) {
|
||||
if (p -> flags & FINISHED) continue;
|
||||
if (pthread_equal(p -> id, me)) {
|
||||
hot = GC_approx_sp();
|
||||
} else {
|
||||
hot = p -> stack_ptr;
|
||||
}
|
||||
if (p -> stack_size != 0) {
|
||||
# ifdef STACK_GROWS_UP
|
||||
cold = p -> stack;
|
||||
# else
|
||||
cold = p -> stack + p -> stack_size;
|
||||
# endif
|
||||
} else {
|
||||
/* The original stack. */
|
||||
cold = GC_stackbottom;
|
||||
}
|
||||
# ifdef STACK_GROWS_UP
|
||||
GC_push_all_stack(cold, hot);
|
||||
# else
|
||||
GC_push_all_stack(hot, cold);
|
||||
# endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* We hold the allocation lock. */
|
||||
void GC_thr_init()
|
||||
{
|
||||
GC_thread t;
|
||||
struct sigaction act;
|
||||
|
||||
if (GC_thr_initialized) return;
|
||||
GC_thr_initialized = TRUE;
|
||||
GC_min_stack_sz = HBLKSIZE;
|
||||
GC_page_sz = sysconf(_SC_PAGESIZE);
|
||||
(void) sigaction(SIG_SUSPEND, 0, &act);
|
||||
if (act.sa_handler != SIG_DFL)
|
||||
ABORT("Previously installed SIG_SUSPEND handler");
|
||||
/* Install handler. */
|
||||
act.sa_handler = GC_suspend_handler;
|
||||
act.sa_flags = SA_RESTART;
|
||||
(void) sigemptyset(&act.sa_mask);
|
||||
if (0 != sigaction(SIG_SUSPEND, &act, 0))
|
||||
ABORT("Failed to install SIG_SUSPEND handler");
|
||||
/* Add the initial thread, so we can stop it. */
|
||||
t = GC_new_thread(pthread_self());
|
||||
t -> stack_size = 0;
|
||||
t -> stack_ptr = (ptr_t)(&t);
|
||||
t -> flags = DETACHED;
|
||||
}
|
||||
|
||||
int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
|
||||
{
|
||||
sigset_t fudged_set;
|
||||
|
||||
if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
|
||||
fudged_set = *set;
|
||||
sigdelset(&fudged_set, SIG_SUSPEND);
|
||||
set = &fudged_set;
|
||||
}
|
||||
return(pthread_sigmask(how, set, oset));
|
||||
}
|
||||
|
||||
struct start_info {
|
||||
void *(*start_routine)(void *);
|
||||
void *arg;
|
||||
word flags;
|
||||
ptr_t stack;
|
||||
size_t stack_size;
|
||||
sem_t registered; /* 1 ==> in our thread table, but */
|
||||
/* parent hasn't yet noticed. */
|
||||
};
|
||||
|
||||
void GC_thread_exit_proc(void *arg)
|
||||
{
|
||||
GC_thread me;
|
||||
|
||||
LOCK();
|
||||
me = GC_lookup_thread(pthread_self());
|
||||
if (me -> flags & DETACHED) {
|
||||
GC_delete_thread(pthread_self());
|
||||
} else {
|
||||
me -> flags |= FINISHED;
|
||||
}
|
||||
UNLOCK();
|
||||
}
|
||||
|
||||
int GC_pthread_join(pthread_t thread, void **retval)
|
||||
{
|
||||
int result;
|
||||
GC_thread thread_gc_id;
|
||||
|
||||
LOCK();
|
||||
thread_gc_id = GC_lookup_thread(thread);
|
||||
/* This is guaranteed to be the intended one, since the thread id */
|
||||
/* cant have been recycled by pthreads. */
|
||||
UNLOCK();
|
||||
result = pthread_join(thread, retval);
|
||||
/* Some versions of the Irix pthreads library can erroneously */
|
||||
/* return EINTR when the call succeeds. */
|
||||
if (EINTR == result) result = 0;
|
||||
LOCK();
|
||||
/* Here the pthread thread id may have been recycled. */
|
||||
GC_delete_gc_thread(thread, thread_gc_id);
|
||||
UNLOCK();
|
||||
return result;
|
||||
}
|
||||
|
||||
void * GC_start_routine(void * arg)
|
||||
{
|
||||
struct start_info * si = arg;
|
||||
void * result;
|
||||
GC_thread me;
|
||||
pthread_t my_pthread;
|
||||
void *(*start)(void *);
|
||||
void *start_arg;
|
||||
|
||||
my_pthread = pthread_self();
|
||||
/* If a GC occurs before the thread is registered, that GC will */
|
||||
/* ignore this thread. That's fine, since it will block trying to */
|
||||
/* acquire the allocation lock, and won't yet hold interesting */
|
||||
/* pointers. */
|
||||
LOCK();
|
||||
/* We register the thread here instead of in the parent, so that */
|
||||
/* we don't need to hold the allocation lock during pthread_create. */
|
||||
/* Holding the allocation lock there would make REDIRECT_MALLOC */
|
||||
/* impossible. It probably still doesn't work, but we're a little */
|
||||
/* closer ... */
|
||||
/* This unfortunately means that we have to be careful the parent */
|
||||
/* doesn't try to do a pthread_join before we're registered. */
|
||||
me = GC_new_thread(my_pthread);
|
||||
me -> flags = si -> flags;
|
||||
me -> stack = si -> stack;
|
||||
me -> stack_size = si -> stack_size;
|
||||
me -> stack_ptr = (ptr_t)si -> stack + si -> stack_size - sizeof(word);
|
||||
UNLOCK();
|
||||
start = si -> start_routine;
|
||||
start_arg = si -> arg;
|
||||
sem_post(&(si -> registered));
|
||||
pthread_cleanup_push(GC_thread_exit_proc, 0);
|
||||
result = (*start)(start_arg);
|
||||
me -> status = result;
|
||||
me -> flags |= FINISHED;
|
||||
pthread_cleanup_pop(1);
|
||||
/* This involves acquiring the lock, ensuring that we can't exit */
|
||||
/* while a collection that thinks we're alive is trying to stop */
|
||||
/* us. */
|
||||
return(result);
|
||||
}
|
||||
|
||||
# ifdef HPUX_THREADS
|
||||
/* pthread_attr_t is not a structure, thus a simple structure copy */
|
||||
/* won't work. */
|
||||
static void copy_attr(pthread_attr_t * pa_ptr,
|
||||
const pthread_attr_t * source) {
|
||||
int tmp;
|
||||
size_t stmp;
|
||||
void * vtmp;
|
||||
struct sched_param sp_tmp;
|
||||
pthread_spu_t ps_tmp;
|
||||
(void) pthread_attr_init(pa_ptr);
|
||||
(void) pthread_attr_getdetachstate(source, &tmp);
|
||||
(void) pthread_attr_setdetachstate(pa_ptr, tmp);
|
||||
(void) pthread_attr_getinheritsched(source, &tmp);
|
||||
(void) pthread_attr_setinheritsched(pa_ptr, tmp);
|
||||
(void) pthread_attr_getschedpolicy(source, &tmp);
|
||||
(void) pthread_attr_setschedpolicy(pa_ptr, tmp);
|
||||
(void) pthread_attr_getstacksize(source, &stmp);
|
||||
(void) pthread_attr_setstacksize(pa_ptr, stmp);
|
||||
(void) pthread_attr_getguardsize(source, &stmp);
|
||||
(void) pthread_attr_setguardsize(pa_ptr, stmp);
|
||||
(void) pthread_attr_getstackaddr(source, &vtmp);
|
||||
(void) pthread_attr_setstackaddr(pa_ptr, vtmp);
|
||||
(void) pthread_attr_getscope(source, &tmp);
|
||||
(void) pthread_attr_setscope(pa_ptr, tmp);
|
||||
(void) pthread_attr_getschedparam(source, &sp_tmp);
|
||||
(void) pthread_attr_setschedparam(pa_ptr, &sp_tmp);
|
||||
(void) pthread_attr_getprocessor_np(source, &ps_tmp, &tmp);
|
||||
(void) pthread_attr_setprocessor_np(pa_ptr, ps_tmp, tmp);
|
||||
}
|
||||
# else
|
||||
# define copy_attr(pa_ptr, source) *(pa_ptr) = *(source)
|
||||
# endif
|
||||
|
||||
int
|
||||
GC_pthread_create(pthread_t *new_thread,
|
||||
const pthread_attr_t *attr,
|
||||
void *(*start_routine)(void *), void *arg)
|
||||
{
|
||||
int result;
|
||||
GC_thread t;
|
||||
void * stack;
|
||||
size_t stacksize;
|
||||
pthread_attr_t new_attr;
|
||||
int detachstate;
|
||||
word my_flags = 0;
|
||||
struct start_info * si = GC_malloc(sizeof(struct start_info));
|
||||
/* This is otherwise saved only in an area mmapped by the thread */
|
||||
/* library, which isn't visible to the collector. */
|
||||
|
||||
if (0 == si) return(ENOMEM);
|
||||
if (0 != sem_init(&(si -> registered), 0, 0)) {
|
||||
ABORT("sem_init failed");
|
||||
}
|
||||
si -> start_routine = start_routine;
|
||||
si -> arg = arg;
|
||||
LOCK();
|
||||
if (!GC_thr_initialized) GC_thr_init();
|
||||
if (NULL == attr) {
|
||||
stack = 0;
|
||||
(void) pthread_attr_init(&new_attr);
|
||||
} else {
|
||||
copy_attr(&new_attr, attr);
|
||||
pthread_attr_getstackaddr(&new_attr, &stack);
|
||||
}
|
||||
pthread_attr_getstacksize(&new_attr, &stacksize);
|
||||
pthread_attr_getdetachstate(&new_attr, &detachstate);
|
||||
if (stacksize < GC_min_stack_sz) ABORT("Stack too small");
|
||||
if (0 == stack) {
|
||||
stack = (void *)GC_stack_alloc(&stacksize);
|
||||
if (0 == stack) {
|
||||
UNLOCK();
|
||||
return(ENOMEM);
|
||||
}
|
||||
pthread_attr_setstackaddr(&new_attr, stack);
|
||||
} else {
|
||||
my_flags |= CLIENT_OWNS_STACK;
|
||||
}
|
||||
if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
|
||||
si -> flags = my_flags;
|
||||
si -> stack = stack;
|
||||
si -> stack_size = stacksize;
|
||||
result = pthread_create(new_thread, &new_attr, GC_start_routine, si);
|
||||
if (0 == new_thread && !(my_flags & CLIENT_OWNS_STACK)) {
|
||||
GC_stack_free(stack, stacksize);
|
||||
}
|
||||
UNLOCK();
|
||||
/* Wait until child has been added to the thread table. */
|
||||
/* This also ensures that we hold onto si until the child is done */
|
||||
/* with it. Thus it doesn't matter whether it is otherwise */
|
||||
/* visible to the collector. */
|
||||
while (0 != sem_wait(&(si -> registered))) {
|
||||
if (errno != EINTR) {
|
||||
GC_printf1("Sem_wait: errno = %ld\n", (unsigned long) errno);
|
||||
ABORT("sem_wait failed");
|
||||
}
|
||||
}
|
||||
sem_destroy(&(si -> registered));
|
||||
pthread_attr_destroy(&new_attr); /* Not a no-op under HPUX */
|
||||
return(result);
|
||||
}
|
||||
|
||||
#ifndef HPUX_THREADS
|
||||
/* For now we use the pthreads locking primitives on HP/UX */
|
||||
|
||||
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
|
||||
|
||||
#ifdef HPUX
|
||||
unsigned long GC_allocate_lock = 1;
|
||||
# define GC_TRY_LOCK() GC_test_and_clear(&GC_allocate_lock)
|
||||
# define GC_LOCK_TAKEN !GC_allocate_lock
|
||||
#else
|
||||
unsigned long GC_allocate_lock = 0;
|
||||
# define GC_TRY_LOCK() !GC_test_and_set(&GC_allocate_lock,1)
|
||||
# define GC_LOCK_TAKEN GC_allocate_lock
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* !HPUX_THREADS */
|
||||
|
||||
# else
|
||||
|
||||
#ifndef LINT
|
||||
int GC_no_Irix_threads;
|
||||
#endif
|
||||
|
||||
# endif /* IRIX_THREADS */
|
||||
|
21
boehm-gc/hpux_test_and_clear.s
Normal file
21
boehm-gc/hpux_test_and_clear.s
Normal file
|
@ -0,0 +1,21 @@
|
|||
.SPACE $PRIVATE$
|
||||
.SUBSPA $DATA$,QUAD=1,ALIGN=8,ACCESS=31
|
||||
.SUBSPA $BSS$,QUAD=1,ALIGN=8,ACCESS=31,ZERO,SORT=82
|
||||
.SPACE $TEXT$
|
||||
.SUBSPA $LIT$,QUAD=0,ALIGN=8,ACCESS=44
|
||||
.SUBSPA $CODE$,QUAD=0,ALIGN=8,ACCESS=44,CODE_ONLY
|
||||
.IMPORT $global$,DATA
|
||||
.IMPORT $$dyncall,MILLICODE
|
||||
.SPACE $TEXT$
|
||||
.SUBSPA $CODE$
|
||||
|
||||
.align 4
|
||||
.EXPORT GC_test_and_clear,ENTRY,PRIV_LEV=3,ARGW0=GR,RTNVAL=GR
|
||||
GC_test_and_clear
|
||||
.PROC
|
||||
.CALLINFO FRAME=0,NO_CALLS
|
||||
.ENTRY
|
||||
ldcw,co (%r26),%r28
|
||||
bv,n 0(%r2)
|
||||
.EXIT
|
||||
.PROCEND
|
56
boehm-gc/include/backptr.h
Normal file
56
boehm-gc/include/backptr.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* This is a simple API to implement pointer back tracing, i.e.
|
||||
* to answer questions such as "who is pointing to this" or
|
||||
* "why is this object being retained by the collector"
|
||||
*
|
||||
* This API assumes that we have an ANSI C compiler.
|
||||
*
|
||||
* Most of these calls yield useful information on only after
|
||||
* a garbage collection. Usually the client will first force
|
||||
* a full collection and then gather information, preferably
|
||||
* before much intervening allocation.
|
||||
*
|
||||
* The implementation of the interface is only about 99.9999%
|
||||
* correct. It is intended to be good enough for profiling,
|
||||
* but is not intended to be used with production code.
|
||||
*
|
||||
* Results are likely to be much more useful if all allocation is
|
||||
* accomplished through the debugging allocators.
|
||||
*
|
||||
* The implementation idea is due to A. Demers.
|
||||
*/
|
||||
|
||||
/* Store information about the object referencing dest in *base_p */
|
||||
/* and *offset_p. */
|
||||
/* If multiple objects or roots point to dest, the one reported */
|
||||
/* will be the last on used by the garbage collector to trace the */
|
||||
/* object. */
|
||||
/* source is root ==> *base_p = address, *offset_p = 0 */
|
||||
/* source is heap object ==> *base_p != 0, *offset_p = offset */
|
||||
/* Returns 1 on success, 0 if source couldn't be determined. */
|
||||
/* Dest can be any address within a heap object. */
|
||||
typedef enum { GC_UNREFERENCED, /* No refence info available. */
|
||||
GC_NO_SPACE, /* Dest not allocated with debug alloc */
|
||||
GC_REFD_FROM_ROOT, /* Referenced directly by root *base_p */
|
||||
GC_REFD_FROM_HEAP, /* Referenced from another heap obj. */
|
||||
GC_FINALIZER_REFD /* Finalizable and hence accessible. */
|
||||
} GC_ref_kind;
|
||||
|
||||
GC_ref_kind GC_get_back_ptr_info(void *dest, void **base_p, size_t *offset_p);
|
||||
|
||||
/* Generate a random heap address. */
|
||||
/* The resulting address is in the heap, but */
|
||||
/* not necessarily inside a valid object. */
|
||||
void * GC_generate_random_heap_address(void);
|
||||
|
||||
/* Generate a random address inside a valid marked heap object. */
|
||||
void * GC_generate_random_valid_address(void);
|
||||
|
||||
/* Force a garbage collection and generate a backtrace from a */
|
||||
/* random heap address. */
|
||||
/* This uses the GC logging mechanism (GC_printf) to produce */
|
||||
/* output. It can often be called from a debugger. The */
|
||||
/* source in dbg_mlc.c also serves as a sample client. */
|
||||
void GC_generate_random_backtrace(void);
|
||||
|
||||
|
26
boehm-gc/include/gc_copy_descr.h
Normal file
26
boehm-gc/include/gc_copy_descr.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 1999 by Silicon Graphics. 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.
|
||||
*/
|
||||
/* Descriptor for allocation request. May be redefined by client. */
|
||||
typedef struct {
|
||||
GC_word bitmap; /* Bitmap describing pointer locations. */
|
||||
/* High order bit correspond to 0th */
|
||||
/* word. 2 lsbs must be 0. */
|
||||
size_t length; /* In bytes, must be multiple of word */
|
||||
/* size. Must be >0, <= 512 */
|
||||
} * GC_copy_descriptor;
|
||||
|
||||
/* The collector accesses descriptors only through these two macros. */
|
||||
#define GC_SIZE_FROM_DESCRIPTOR(d) ((d) -> length)
|
||||
#define GC_BIT_MAP_FROM_DESCRIPTOR(d) ((d) -> bitmap)
|
||||
|
90
boehm-gc/include/gc_nursery.h
Normal file
90
boehm-gc/include/gc_nursery.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
|
||||
/*
|
||||
* Copyright (c) 1999 by Silicon Graphics. 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
* THIS IMPLEMENTATION FOR THIS INTERFACE IS INCOMPLETE.
|
||||
* NONE OF THIS HAS BEEN TESTED. DO NOT USE.
|
||||
*
|
||||
* Comments on the interface are appreciated, especially from
|
||||
* potential users of the interface.
|
||||
*
|
||||
* This is a Bartlett style copying collector for young objects.
|
||||
* We assume for now that all objects allocated through this
|
||||
* mechanism have pointers only in the first BITMAP_BITS words.
|
||||
* (On a 32-bit machine, BITMAP_BITS is 30.)
|
||||
* Objects allocated in this manner should be rarely referenced
|
||||
* by objects not allocated either through this interface, or through
|
||||
* the typed allocation interface.
|
||||
* If this interface is used, we assume that type information provided
|
||||
* through either this or the typed allocation interface is valid
|
||||
* in a stronger sense:
|
||||
*
|
||||
* 1) No pointers are stored in fields not marked as such.
|
||||
* (Otherwise it is only necessary that objects referenced by
|
||||
* fields marked as nonpointers are also reachable via another
|
||||
* path.)
|
||||
* 2) Values stored in pointer fields are either not addresses in
|
||||
* the heap, or they really are pointers. In the latter case, it
|
||||
* is acceptable to move the object they refer to, and to update
|
||||
* the pointer.
|
||||
*
|
||||
* GC_free may not be invoked on objects allocated with GC_copying_malloc.
|
||||
*
|
||||
* No extra space is added to the end of objects allocated through this
|
||||
* interface. If the client needs to maintain pointers past the
|
||||
* end, the size should be explicitly padded.
|
||||
*
|
||||
* We assume that calls to this will usually be compiler generated.
|
||||
* Hence the interface is allowed to be a bit ugly in return for speed.
|
||||
*/
|
||||
|
||||
#include "gc_copy_descr.h"
|
||||
|
||||
/* GC_copy_descr.h must define */
|
||||
/* GC_SIZE_FROM_DESCRIPTOR(descr) and */
|
||||
/* GC_BIT_MAP_FROM_DESCRIPTOR(descr). */
|
||||
/* It may either be the GC supplied version of the header file, or a */
|
||||
/* client specific one that derives the information from a client- */
|
||||
/* specific type descriptor. */
|
||||
|
||||
typedef GC_PTR GC_copy_alloc_state;
|
||||
/* Current allocator state. */
|
||||
/* Multiple allocation states */
|
||||
/* may be used for concurrent */
|
||||
/* allocation, or to enhance */
|
||||
/* locality. */
|
||||
/* Should be treated as opaque. */
|
||||
|
||||
/* Allocate a memory block of size given in the descriptor, and with */
|
||||
/* pointer layout given by the descriptor. The resulting block may not */
|
||||
/* be cleared, and should immediately be initialized by the client. */
|
||||
/* (A concurrent GC may see an uninitialized pointer field. If it */
|
||||
/* points outside the nursery, that's fine. If it points inside, it */
|
||||
/* may retain an object, and be relocated. But that's also fine, since */
|
||||
/* the new value will be immediately overwritten. */
|
||||
/* This variant acquires the allocation lock, and uses a default */
|
||||
/* global allocation state. */
|
||||
GC_PTR GC_copying_malloc(GC_copy_descriptor);
|
||||
|
||||
/* A variant of the above that does no locking on the fast path, */
|
||||
/* and passes an explicit pointer to an allocation state. */
|
||||
/* The allocation state is updated. */
|
||||
/* There will eventually need to be a macro or inline function version */
|
||||
/* of this. */
|
||||
GC_PTR GC_copying_malloc2(GC_copy_descriptor, GC_copy_alloc_state *);
|
||||
|
||||
/* Initialize an allocation state so that it can be used for */
|
||||
/* allocation. This implicitly reserves a small section of the */
|
||||
/* nursery for use with this allocator. */
|
||||
void GC_init_copy_alloc_state(GC_copy_alloc_state *);
|
7
boehm-gc/include/leak_detector.h
Normal file
7
boehm-gc/include/leak_detector.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#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(n)
|
||||
#define CHECK_LEAKS() GC_gcollect()
|
312
boehm-gc/nursery.c
Normal file
312
boehm-gc/nursery.c
Normal file
|
@ -0,0 +1,312 @@
|
|||
/*
|
||||
* Copyright (c) 1999 by Silicon Graphics. 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.
|
||||
*/
|
||||
|
||||
#ifdef NURSERY
|
||||
??? This implementation is incomplete. If you are trying to
|
||||
??? compile this you are doing something wrong.
|
||||
|
||||
#include "nursery.h"
|
||||
|
||||
#define SCAN_STATICS_FOR_NURSERY
|
||||
/* If this is not defined, the collector will not see */
|
||||
/* references from static data areas to the nursery. */
|
||||
|
||||
struct copy_obj {
|
||||
ptr_t forward; /* Forwarding link for copied objects. */
|
||||
GC_copy_descriptor descr; /* Object descriptor */
|
||||
word data[1];
|
||||
}
|
||||
|
||||
ptr_t GC_nursery_start; /* Start of nursery area. */
|
||||
/* Must be NURSERY_BLOCK_SIZE */
|
||||
/* aligned. */
|
||||
ptr_t GC_nursery_end; /* End of nursery area. */
|
||||
unsigned char * GC_nursery_map;
|
||||
/* GC_nursery_map[i] != 0 if an object */
|
||||
/* starts on the ith 64-bit "word" of */
|
||||
/* nursery. This simple structure has */
|
||||
/* the advantage that */
|
||||
/* allocation is cheap. Lookup is */
|
||||
/* cheap for pointers to the head of */
|
||||
/* an object, which should be the */
|
||||
/* usual case. */
|
||||
# define NURSERY_MAP_NOT_START 0 /* Not start of object. */
|
||||
# define NURSERY_MAP_START 1 /* Start of object. */
|
||||
# define NURSERY_MAP_PINNED 2 /* Start of pinned obj. */
|
||||
|
||||
# ifdef ALIGN_DOUBLE
|
||||
# define NURSERY_WORD_SIZE (2 * sizeof(word))
|
||||
# else
|
||||
# define NURSERY_WORD_SIZE sizeof(word)
|
||||
# endif
|
||||
|
||||
# define NURSERY_BLOCK_SIZE (HBLKSIZE/2)
|
||||
/* HBLKSIZE must be a multiple of NURSERY_BLOCK_SIZE */
|
||||
# define NURSERY_SIZE (1024 * NURSERY_BLOCK_SIZE)
|
||||
|
||||
size_t GC_nursery_size = NURSERY_SIZE;
|
||||
/* Must be multiple of NURSERY_BLOCK_SIZE */
|
||||
|
||||
size_t GC_nursery_blocks; /* Number of blocks in the nursery. */
|
||||
|
||||
unsigned GC_next_nursery_block; /* index of next block we will attempt */
|
||||
/* allocate from during this cycle. */
|
||||
/* If it is pinned, we won't actually */
|
||||
/* use it. */
|
||||
|
||||
unsigned short *GC_pinned; /* Number of pinned objects in ith */
|
||||
/* nursery block. */
|
||||
/* GC_pinned[i] != 0 if the ith nursery */
|
||||
/* block is pinned, and thus not used */
|
||||
/* for allocation. */
|
||||
|
||||
GC_copy_alloc_state global_alloc_state = (ptr_t)(-1); /* will overflow. */
|
||||
|
||||
/* Array of known rescuing pointers from the heap to the nursery. */
|
||||
ptr_t ** nursery_rescuers;
|
||||
/* Pointer to one past the last slot in rescuer table */
|
||||
ptr_t ** nursery_rescuers_end;
|
||||
/* Maximum number of known rescuing pointers. */
|
||||
# define MAX_NURSERY_RESCUERS 32*1024
|
||||
/* Add a rescuer to the list */
|
||||
# define ADD_NURSERY_RESCUER(p) \
|
||||
if (nursery_rescuers_end >= nursery_rescuers + MAX_NURSERY_RESCUERS) { \
|
||||
ABORT("Nursery recuers overflow"); /* Fix later !!! */ \
|
||||
} else { \
|
||||
*nursery_rescuers_end++ = p; \
|
||||
}
|
||||
/* Remove rescuer at the given position in the table */
|
||||
# define REMOVE_RESCUER(p) \
|
||||
*p = *--nursery_rescuers_end
|
||||
|
||||
/* Should be called with allocator lock held. */
|
||||
GC_nursery_init() {
|
||||
GC_nursery_start = GET_MEM(GC_nursery_size);
|
||||
GC_nursery_end = GC_nursery_start + GC_nursery_size;
|
||||
GC_next_nursery_block = 0;
|
||||
if (GC_nursery_start < GC_least_plausible_heap_addr) {
|
||||
GC_least_plausible_heap_addr = GC_nursery_start;
|
||||
}
|
||||
if (GC_nursery_end > GC_greatest_plausible_heap_addr) {
|
||||
GC_greatest_plausible_heap_addr = GC_nursery_end;
|
||||
}
|
||||
if (GC_nursery_start & (NURSERY_BLOCK_SIZE-1)) {
|
||||
GC_err_printf1("Nursery area is misaligned!!");
|
||||
/* This should be impossible, since GET_MEM returns HBLKSIZE */
|
||||
/* aligned chunks, and that should be a multiple of */
|
||||
/* NURSERY_BLOCK_SIZE */
|
||||
ABORT("misaligned nursery");
|
||||
}
|
||||
GC_nursery_map = GET_MEM(GC_nursery_size/NURSERY_WORD_SIZE);
|
||||
/* Map is cleared a block at a time when we allocate from the block. */
|
||||
/* BZERO(GC_nursery_map, GC_nursery_size/NURSERY_WORD_SIZE); */
|
||||
GC_nursery_blocks = GC_nursery_size/NURSERY_BLOCK_SIZE;
|
||||
GC_pinned = GC_scratch_alloc(GC_nursery_blocks * sizeof(unsigned short));
|
||||
BZERO(GC_pinned, GC_nursery_blocks);
|
||||
nursery_rescuers = GET_MEM(MAX_NURSERY_RESCUERS * sizeof(ptr_t *));
|
||||
nursery_rescuers_end = nursery_rescuers;
|
||||
if (0 == GC_nursery_start || 0 == GC_nursery_map || 0 == nursery_rescuers)
|
||||
ABORT("Insufficient memory for nursery");
|
||||
}
|
||||
|
||||
#define PIN_OBJ(p) \
|
||||
if (p >= GC_nursery_start && p < GC_nursery_end) { GC_pin_obj_checked(p); }
|
||||
|
||||
/* Pin the object at p, if it's in the nursery. */
|
||||
void GC_pin_obj(ptr_t p) {
|
||||
PIN_OBJ(p);
|
||||
}
|
||||
|
||||
void (*GC_push_proc)(ptr_t) = 0;
|
||||
|
||||
/* Pin the object at p, which is known to be in the nursery. */
|
||||
void GC_pin_obj_checked(ptr_t p) {
|
||||
unsigned offset = p - GC_nursery_start;
|
||||
unsigned word_offset = BYTES_TO_WORDS(offset);
|
||||
unsigned blockno = (current - GC_nursery_start)/NURSERY_BLOCK_SIZE;
|
||||
while (GC_nursery_map[word_offset] == NURSERY_MAP_NOT_START) {
|
||||
--word_offset;
|
||||
}
|
||||
if (GC_nursery_map[word_offset] != NURSERY_MAP_PINNED) {
|
||||
GC_nursery_map[word_offset] = NURSERY_MAP_PINNED;
|
||||
++GC_pinned[blockno];
|
||||
??Push object at GC_nursery_start + WORDS_TO_BYTES(word_offset)
|
||||
??onto mark stack.
|
||||
}
|
||||
}
|
||||
|
||||
void GC_scan_region_for_nursery(ptr_t low, ptr_t high) {
|
||||
# if CPP_WORDSZ/8 != ALIGNMENT
|
||||
--> fix this
|
||||
# endif
|
||||
word * l = (word *)((word)low + ALIGNMENT - 1 & ~(ALIGNMENT - 1));
|
||||
word * h = (word *)((word)high & ~(ALIGNMENT - 1));
|
||||
word * p;
|
||||
for (p = l; p < h; ++p) {
|
||||
PIN_OBJ(p);
|
||||
}
|
||||
}
|
||||
|
||||
/* Invoke GC_scan_region_for_nursery on ranges that are not excluded. */
|
||||
void GC_scan_region_for_nursery_with_exclusions(ptr_t bottom, ptr_t top)
|
||||
{
|
||||
struct exclusion * next;
|
||||
ptr_t excl_start;
|
||||
|
||||
while (bottom < top) {
|
||||
next = GC_next_exclusion(bottom);
|
||||
if (0 == next || (excl_start = next -> e_start) >= top) {
|
||||
GC_scan_region_for_nursery(bottom, top);
|
||||
return;
|
||||
}
|
||||
if (excl_start > bottom)
|
||||
GC_scan_region_for_nursery(bottom, excl_start);
|
||||
bottom = next -> e_end;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void GC_scan_stacks_for_nursery(void) {
|
||||
# ifdef THREADS
|
||||
--> fix this
|
||||
# endif
|
||||
# ifdef STACK_GROWS_DOWN
|
||||
ptr_t stack_low = GC_approx_sp();
|
||||
ptr_t stack_high = GC_stackbottom;
|
||||
# else
|
||||
ptr_t stack_low = GC_stackbottom;
|
||||
ptr_t stack_high = GC_approx_sp();
|
||||
# endif
|
||||
GC_scan_region_for_nursery(stack_low, stack_high);
|
||||
# ifdef IA64
|
||||
GC_scan_region_for_nursery(BACKING_STORE_BASE,
|
||||
(ptr_t) GC_save_regs_ret_val);
|
||||
# endif
|
||||
}
|
||||
|
||||
void GC_scan_roots_for_nursery(void) {
|
||||
/* Scan registers. */
|
||||
/* Direct GC_push_one to call GC_pin_obj instead of marking */
|
||||
/* and pushing objects. */
|
||||
/* This is a bit ugly, but we don't have to touch the */
|
||||
/* platform-dependent code. */
|
||||
|
||||
void (*old_push_proc)(ptr_t) = GC_push_proc;
|
||||
GC_push_proc = GC_pin_obj;
|
||||
GC_push_regs();
|
||||
GC_push_proc = old_push_proc;
|
||||
GC_scan_stacks_for_nursery();
|
||||
# ifdef SCAN_STATICS_FOR_NURSERY
|
||||
# if (defined(DYNAMIC_LOADING) || defined(MSWIN32) || defined(PCR)) \
|
||||
&& !defined(SRC_M3)
|
||||
GC_remove_tmp_roots();
|
||||
GC_register_dynamic_libraries();
|
||||
# endif
|
||||
/* Mark everything in static data areas */
|
||||
for (i = 0; i < n_root_sets; i++) {
|
||||
GC_scan_region_for_nursery_with_exclusions (
|
||||
GC_static_roots[i].r_start,
|
||||
GC_static_roots[i].r_end);
|
||||
}
|
||||
# endif
|
||||
}
|
||||
|
||||
/* Array of known rescuing pointers from the heap to the nursery. */
|
||||
ptr_t ** nursery_rescuers;
|
||||
|
||||
/* Caller holds allocation lock. */
|
||||
void GC_collect_nursery(void) {
|
||||
int i;
|
||||
ptr_t scan_ptr = 0;
|
||||
STOP_WORLD;
|
||||
for (i = 0; i < GC_nursery_blocks; ++i) GC_pinned[i] = 0;
|
||||
GC_scan_roots_for_nursery();
|
||||
/* All objects referenced by roots are now pinned. */
|
||||
/* Their contents are described by */
|
||||
/* mark stack entries. */
|
||||
|
||||
/* Pin blocks corresponding to valid allocation states. */
|
||||
/* that probably happens automagically if the allocation */
|
||||
/* states are kept where we can see them. */
|
||||
/* It will take work if static roots are not scanned. */
|
||||
/* We want to do this both for correctness and to avoid */
|
||||
/* promoting very young objects. */
|
||||
|
||||
/* Somehow capture dirty bits. Update rescuers array to */
|
||||
/* reflect newly valid and invalid references from dirty */
|
||||
/* pages. Other references should remain valid, since the */
|
||||
/* referents should have been pinned. */
|
||||
|
||||
/* Traverse the old object heap. Pin objects in the */
|
||||
/* nursery that are ambiguously referenced, copy those */
|
||||
/* that are unambiguously referenced. */
|
||||
|
||||
/* Traverse objects in mark stack. */
|
||||
/* If referenced object is in pinned block, add contents */
|
||||
/* to mark stack. If referenced object is forwarded, */
|
||||
/* update pointer. Otherwise reallocate the object in the */
|
||||
/* old heap, copy its contents, and then enqueue its */
|
||||
/* contents in the mark stack. */
|
||||
START_WORLD;
|
||||
}
|
||||
|
||||
/* Initialize an allocation state so that it can be used for */
|
||||
/* allocation. This implicitly reserves a small section of the */
|
||||
/* nursery for use with this allocator. */
|
||||
/* Also called to replenish an allocator that has been */
|
||||
/* exhausted. */
|
||||
void GC_init_copy_alloc_state(GC_copy_alloc_state *)
|
||||
unsigned next_block;
|
||||
ptr_t block_addr;
|
||||
LOCK();
|
||||
next_block = GC_next_nursery_block;
|
||||
while(is_pinned[next_block] && next_block < GC_nursery_blocks) {
|
||||
++next_block;
|
||||
}
|
||||
if (next_block < GC_nursery_blocks) {
|
||||
block_addr = GC_nursery_start + NURSERY_BLOCK_SIZE * next_block;
|
||||
GC_next_nursery_block = next_block + 1;
|
||||
BZERO(GC_nursery_map + next_block *
|
||||
(NURSERY_BLOCK_SIZE/NURSERY_WORD_SIZE),
|
||||
NURSERY_BLOCK_SIZE/NURSERY_WORD_SIZE);
|
||||
*GC_copy_alloc_state = block_addr;
|
||||
UNLOCK();
|
||||
} else {
|
||||
GC_collect_nursery();
|
||||
GC_next_nursery_block = 0;
|
||||
UNLOCK();
|
||||
get_new_block(s);
|
||||
}
|
||||
}
|
||||
|
||||
GC_PTR GC_copying_malloc2(GC_copy_descriptor *d, GC_copy_alloc_state *s) {
|
||||
size_t sz = GC_SIZE_FROM_DESCRIPTOR(d);
|
||||
ptrdiff_t offset;
|
||||
ptr_t result = *s;
|
||||
ptr_t new = result + sz;
|
||||
if (new & COPY_BLOCK_MASK <= result & COPY_BLOCK_MASK> {
|
||||
GC_init_copy_alloc_state(s);
|
||||
result = *s;
|
||||
new = result + sz;
|
||||
GC_ASSERT(new & COPY_BLOCK_MASK > result & COPY_BLOCK_MASK>
|
||||
}
|
||||
(struct copy_obj *)result -> descr = d;
|
||||
(struct copy_obj *)result -> forward = 0;
|
||||
offset = (result - GC_nursery_start)/NURSERY_WORD_SIZE;
|
||||
GC_nursery_map[offset] = NURSERY_MAP_NOT_START;
|
||||
}
|
||||
|
||||
GC_PTR GC_copying_malloc(GC_copy_descriptor *d) {
|
||||
}
|
||||
|
||||
#endif /* NURSERY */
|
Loading…
Add table
Reference in a new issue