Milán Major

netscape-revival

1 branch
Code

nspr-/src/prmsgc.c

#include <string.h>
#include <stddef.h>
#include <stdarg.h>
#include <time.h>
#include "prmacros.h"
#include "prclist.h"
#include "prgc.h"
#include "prthread.h"
#include "prglobal.h"
#include "prlog.h"
#include "prtime.h"
#include "prprf.h"
#include "gcint.h"
#include "mdint.h"

#ifdef SW_THREADS
#include "swkern.h"
#else
#include "hwkern.h"
#endif

/*
** Simple mark/sweep garbage collector
*/

PR_LOG_DEFINE(GC);

#ifdef DEBUG
#define GCMETER
#endif
#ifdef DEBUG_jwz
# undef GCMETER
#endif /* 1 */

#ifdef GCMETER
#define METER(x) x
#else
#define METER(x)
#endif


/*
** Make this constant bigger to reduce the amount of recursion during
** garbage collection.
*/
#define MAX_SCAN_Q	100L

#if defined(XP_PC) && !defined(_WIN32)
#define MAX_SEGS            400L
#define MAX_SEGMENT_SIZE    (65536L - 4096L)
#define SEGMENT_SIZE        (65536L - 4096L)
#define MAX_ALLOC_SIZE      (65536L - 4096L)

#else
#define MAX_SEGS            400

#define MAX_SEGMENT_SIZE    (2 * 256 * 1024)
#define SEGMENT_SIZE        (1 * 256 * 1024)
#endif  

static unsigned int segmentSize = SEGMENT_SIZE;

#ifdef GCMETER
uint32 _pr_gcMeter;

#define _GC_METER_STATS         0x01L
#define _GC_METER_GROWTH        0x02L
#define _GC_METER_FREE_LIST     0x04L
#endif

#ifdef DEBUG
static void nop(void) { }
#endif

/************************************************************************/

#define NUM_BINS        64

#ifdef OSF1
#define MIN_ALLOC       32
#define MIN_ALLOC_LOG2  5
#else
#define MIN_ALLOC       16
#define MIN_ALLOC_LOG2  4
#endif

#define BIG_ALLOC       16384L

typedef struct GCFreeChunk {
    struct GCFreeChunk *next;
    struct GCSeg *segment;
    size_t chunkSize;
} GCFreeChunk;

typedef struct GCSegInfo {
    struct GCSegInfo *next;

    char GCPTR *base;
    char GCPTR *limit;
    prword_t GCPTR *hbits;
} GCSegInfo;
    
typedef struct GCSeg {
    char GCPTR *base;
    char GCPTR *limit;
    prword_t GCPTR *hbits;
    GCSegInfo *info;
} GCSeg;

#ifdef GCMETER
typedef struct GCMeter {
    int32 allocBytes;
    int32 zeroBytes;
    int32 wastedBytes;
    int32 numFreeChunks;
} GCMeter;
static GCMeter meter;
#endif

/*
** There is one of these for each segment of GC'able memory.
*/
static GCSeg segs[MAX_SEGS];
static GCSegInfo *freeSegs;
static int nsegs;
static GCFreeChunk *bins[NUM_BINS];

/*
** Scan Q used to avoid deep recursion when scanning live objects for
** heap pointers
*/
typedef struct GCScanQStr {
    prword_t GCPTR *q[MAX_SCAN_Q];
    int queued;
} GCScanQ;

static GCScanQ *pScanQ;

#ifdef GCMETER
int32 _pr_maxScanDepth;
int32 _pr_scanDepth;
#endif

/*
** Keeps track of the number of bytes allocated via the BigAlloc() 
** allocator.  When the number of bytes allocated, exceeds the 
** BIG_ALLOC_GC_SIZE, then a GC will occur before the next allocation
** is done...
*/
#define BIG_ALLOC_GC_SIZE       (4*SEGMENT_SIZE)
static prword_t bigAllocBytes = 0;


/*
** There is one GC header word in front of each GC allocated object.  We
** use it to contain information about the object (what TYPEIX to use for
** scanning it, how big it is, it's mark status, and if it's a root).
*/
#define TYPEIX_BITS	8L
#define WORDS_BITS	22L
#define MAX_CBS		(1L << GC_TYPEIX_BITS)
#define MAX_WORDS	(1L << GC_WORDS_BITS)
#define TYPEIX_SHIFT	24L
#define MAX_TYPEIX	((1L << TYPEIX_BITS) - 1L)
#define TYPEIX_MASK	PR_BITMASK(TYPEIX_BITS)
#define WORDS_SHIFT	2L
#define WORDS_MASK	PR_BITMASK(WORDS_BITS)
#define MARK_BIT	1L
#define FINAL_BIT	2L

#define MAKE_HEADER(_cbix,_words)			  \
    ((prword_t) (((unsigned long)(_cbix) << TYPEIX_SHIFT) \
		 | ((unsigned long)(_words) << WORDS_SHIFT)))

#define GET_TYPEIX(_h) \
    (((uprword_t)(_h) >> TYPEIX_SHIFT) & 0xff)

#define MARK(_sp,_p) \
    (((prword_t GCPTR *)(_p))[0] |= MARK_BIT)
#define IS_MARKED(_sp,_p) \
    (((prword_t GCPTR *)(_p))[0] & MARK_BIT)

#if defined(XP_PC) && !defined(_WIN32)
#define OBJ_BYTES(_h) \
    ( ((size_t) ((_h) & 0x00fffffcL) << (BYTES_PER_WORD_LOG2-2L)) )

#else
#define OBJ_BYTES(_h) \
    (((uprword_t) (_h) & 0x00fffffcL) << (BYTES_PER_WORD_LOG2-2L))

#endif

/************************************************************************/

/*
** Mark the start of an object in a segment. Note that we mark the header
** word (which we always have), not the data word (which we may not have
** for empty objects).
** XXX tune: put subtract of _sp->base into _sp->hbits pointer?
*/
#if !defined(XP_PC) || defined(_WIN32)
#define SET_HBIT(_sp,_ph) \
    SET_BIT((_sp)->hbits, (((prword_t*)(_ph)) - ((prword_t*) (_sp)->base)))

#define CLEAR_HBIT(_sp,_ph) \
    CLEAR_BIT((_sp)->hbits, (((prword_t*)(_ph)) - ((prword_t*) (_sp)->base)))

#define IS_HBIT(_sp,_ph) \
    TEST_BIT((_sp)->hbits, (((prword_t*)(_ph)) - ((prword_t*) (_sp)->base)))
#else

#define SET_HBIT(_sp,_ph) set_hbit(_sp,_ph)

#define CLEAR_HBIT(_sp,_ph) clear_hbit(_sp,_ph)

#define IS_HBIT(_sp,_ph) is_hbit(_sp,_ph)

static void
set_hbit(GCSeg *sp, prword_t GCPTR *p)
{
	unsigned long	distance;
	unsigned long	index;
	unsigned long	mask;

#if 1
        PR_ASSERT( SELECTOROF(p) == SELECTOROF(sp->base) );
        PR_ASSERT( OFFSETOF(p)   >= OFFSETOF(sp->base) );

        distance = (OFFSETOF(p) - OFFSETOF(sp->base)) >> 2;
	index    = distance >> BITS_PER_WORD_LOG2;
	mask	 = 1L << (distance&(BITS_PER_WORD-1));
#else
	distance = ((unsigned long)(((unsigned char *) p) - sp->base)) >> 2L;
	index    = distance >> BITS_PER_WORD_LOG2;
	mask	 = 1L << (distance&(BITS_PER_WORD-1));
#endif
	sp->hbits[index] |= mask;
}

static void
clear_hbit(GCSeg *sp, prword_t GCPTR *p)
{
	unsigned long	distance;
	unsigned long	index;
	unsigned long	mask;

#if 1
        PR_ASSERT( SELECTOROF(p) == SELECTOROF(sp->base) );
        PR_ASSERT( OFFSETOF(p)   >= OFFSETOF(sp->base) );

        distance = (OFFSETOF(p) - OFFSETOF(sp->base)) >> 2;
	index    = distance >> BITS_PER_WORD_LOG2;
	mask	 = 1L << (distance&(BITS_PER_WORD-1));
#else
	distance = ((unsigned long)(((unsigned char *) p) - sp->base)) >> 2L;
	index    = distance >> BITS_PER_WORD_LOG2;
	mask	 = 1L << (distance&(BITS_PER_WORD-1));
#endif
	sp->hbits[index] &= ~mask;
}

static int
is_hbit(GCSeg *sp, prword_t GCPTR *p)
{
	unsigned long	distance;
	unsigned long	index;
	unsigned long	mask;

#if 1
        PR_ASSERT( SELECTOROF(p) == SELECTOROF(sp->base) );
        PR_ASSERT( OFFSETOF(p)   >= OFFSETOF(sp->base) );

        distance = (OFFSETOF(p) - OFFSETOF(sp->base)) >> 2;
	index    = distance >> BITS_PER_WORD_LOG2;
	mask	 = 1L << (distance&(BITS_PER_WORD-1));
#else
	distance = ((unsigned long)(((unsigned char *) p) - sp->base)) >> 2L;
	index    = distance >> BITS_PER_WORD_LOG2;
	mask	 = 1L << (distance&(BITS_PER_WORD-1));
#endif
	return ((sp->hbits[index] & mask) != 0);
}


#endif

/*
** Given a pointer into this segment, back it up until we are at the
** start of the object the pointer points into. Each heap segment has a
** bitmap that has one bit for each word of the objects it contains.  The
** bit's are set for the firstword of an object, and clear for it's other
** words.
*/
#ifdef UNUSED
static prword_t *FindObject(GCSeg *sp, prword_t *p)
{
    prword_t *base;
    
    /* Align p to it's proper boundary before we start fiddling with it */
    p = (prword_t*) ((prword_t)p & ~(BYTES_PER_WORD-1L));

    base = (prword_t *) sp->base;
#if defined(XP_PC) && !defined(_WIN32)
    PR_ASSERT( SELECTOROF(p) == SELECTOROF(base));
#endif
    do {
	if (IS_HBIT(sp, p)) {
	    return (p);
	}
	p--;
    } while ( p >= base );

    /* Heap is corrupted! */
    GCTRACE(GC_TRACE, ("ERROR: The heap is corrupted!!! aborting now!"));
    abort();
}
#endif /* UNUSED */

/************************************************************************/
#ifndef XP_PC
#define OutputDebugString(msg)
#endif 

#if !defined(XP_PC) || defined(_WIN32)
#define IN_SEGMENT(_sp, _p)		     \
    ((((char GCPTR*)(_p)) >= (_sp)->base) && \
     (((char GCPTR*)(_p)) < (_sp)->limit))
#else
#define IN_SEGMENT(_sp, _p)		     \
    ((((prword_t)(_p)) >= ((prword_t)(_sp)->base)) && \
     (((prword_t)(_p)) < ((prword_t)(_sp)->limit)))
#endif

#if defined(XP_PC) && !defined(_WIN32)
static GCSeg *InHeap(void GCPTR *p)
{
    GCSeg *sp, *esp;
    static GCSeg *last = 0;

    if (last && IN_SEGMENT(last, p)) {
	return last;
    }

    sp = segs;
    esp = segs + nsegs;
    for (; sp < esp; sp++) {
	if (IN_SEGMENT(sp, p)) {
	    last = sp;
	    return sp;
	}
    }
    return 0;
}
#endif /* defined(XP_PC) && !defined(_WIN32) */

/*
** Grow the heap by allocating another segment. Fudge the requestedSize
** value to try to pre-account for the HBITS.
*/
static GCSeg* DoGrowHeap(size_t requestedSize, PRBool exactly)
{
    GCSeg *sp;
    GCSegInfo *segInfo;
    GCFreeChunk *cp;
    prword_t nhbytes, nhbits;
    char GCPTR *base;
    prword_t GCPTR *hbits;
    uint32 	 allocSize;
    
    int32 bin;

    if (nsegs == MAX_SEGS) {
	/* No room for more segments */
	return 0;
    }

    segInfo = (GCSegInfo*) malloc(sizeof(GCSegInfo));
#ifdef DEBUG
    {
	char str[256];
	sprintf(str, "[1] Allocated %d bytes at %p\n", sizeof(GCSegInfo), segInfo);
	OutputDebugString(str);
    }
#endif
    if (!segInfo) {
	return 0;
    }

#if defined(XP_PC) && !defined(_WIN32)
    if (requestedSize > segmentSize) {
	free(segInfo);
	return 0;
    }
#endif

    /* Get more memory from the OS */
	allocSize = requestedSize;
	allocSize = (size_t)((allocSize + pr_pageSize - 1L) >> pr_pageShift);
	allocSize <<= pr_pageShift;
	base = _MD_GrowGCHeap(&allocSize);
#ifdef DEBUG
	{
	    char str[256];
	    sprintf(str, "[2] Allocated %d bytes at %p\n", allocSize, base);
	    OutputDebugString(str);
	}
#endif
    if (!base) {
	free(segInfo);
	return 0;
    }

    nhbits = (allocSize + BYTES_PER_WORD - 1L) >> BYTES_PER_WORD_LOG2;
    nhbytes = ((nhbits + BITS_PER_WORD - 1L) >> BITS_PER_WORD_LOG2)
	* sizeof(prword_t);

    /* Get bitmap memory from malloc heap */
#if defined(XP_PC) && !defined(_WIN32)
    PR_ASSERT( nhbytes < MAX_ALLOC_SIZE );
#endif
    hbits = (prword_t GCPTR*) calloc(1, (size_t)nhbytes);
    if (!hbits) {
	/* Loser! */
	free(segInfo);
	_MD_FreeGCSegment(base, allocSize);
	return 0;
    }

    /*
    ** Setup new segment.
    */
    sp = &segs[nsegs++];
    segInfo->base = sp->base = base;
    segInfo->limit = sp->limit = base + allocSize;
    segInfo->hbits = sp->hbits = hbits;
    sp->info = segInfo;

#ifdef GCMETER
    if (_pr_gcMeter & _GC_METER_GROWTH) {
        fprintf(stderr, "[GC: new segment base=%p size=%d]\n",
                sp->base, allocSize);
    }
#endif    

    _pr_gcData.allocMemory += allocSize;
    _pr_gcData.freeMemory  += allocSize;

    if (!exactly) {
        /* Put free memory into a freelist bin */
        bin = allocSize >> MIN_ALLOC_LOG2;
        if (bin >= NUM_BINS) {
            bin = NUM_BINS - 1;
        }
        cp = (GCFreeChunk GCPTR*) base;
        cp->segment = sp;
        cp->chunkSize = allocSize;
        cp->next = bins[bin];
        bins[bin] = cp;
    } else {
        /*
        ** When exactly allocating the entire segment is given over to a
        ** single object to prevent fragmentation
        */
    }

    if (!_pr_gcData.lowSeg) {
	_pr_gcData.lowSeg  = (prword_t*) sp->base;
	_pr_gcData.highSeg = (prword_t*) sp->limit;
    } else {
	if ((prword_t*)sp->base < _pr_gcData.lowSeg) {
	    _pr_gcData.lowSeg = (prword_t*) sp->base;
	}
	if ((prword_t*)sp->limit > _pr_gcData.highSeg) {
	    _pr_gcData.highSeg = (prword_t*) sp->limit;
	}
    }

    /* 
    ** Get rid of the GC pointer in case it shows up in some uninitialized
    ** local stack variable later (while scanning the C stack looking for
    ** roots).
    */ 
    memset(&base, 0, sizeof(base));  /* optimizers beware */
    PR_LOG(GC, warn, ("grow heap: total gc memory now %d",
                      _pr_gcData.allocMemory));
    return sp;
}

static GCSeg *GrowHeapExactly(size_t requestedSize)
{
    GCSeg *sp = DoGrowHeap(requestedSize, PR_TRUE);
    return sp;
}

static PRBool GrowHeap(size_t requestedSize)
{
    void *p = DoGrowHeap(requestedSize, PR_FALSE);
    return p != NULL;
}

/*
** Release a segment when it is entirely free.
*/
static void ShrinkGCHeap(GCSeg *sp)
{
#ifdef GCMETER
    if (_pr_gcMeter & _GC_METER_GROWTH) {
        fprintf(stderr, "[GC: free segment base=%p size=%d]\n",
                sp->base, sp->limit - sp->base);
    }
#endif    

    /*
     * Put segment onto free seginfo list (we can't call free right now
     * because we have the GC lock and all of the other threads are
     * suspended; if one of them has the malloc lock we would deadlock)
     */
    sp->info->next = freeSegs;
    freeSegs = sp->info;
    _pr_gcData.allocMemory -= sp->limit - sp->base;

    /* Squish out disappearing segment from segment table */
    --nsegs;
    if ((sp - segs) != nsegs) {
        *sp = segs[nsegs];
    } else {
        sp->base = 0;
        sp->limit = 0;
        sp->hbits = 0;
	sp->info = 0;
    }

    /* Recalculate the lowSeg and highSeg values */
    _pr_gcData.lowSeg  = (prword_t*) segs[0].base;
    _pr_gcData.highSeg = (prword_t*) segs[0].limit;
    for (sp = segs; sp < &segs[nsegs]; sp++) {
	if ((prword_t*)sp->base < _pr_gcData.lowSeg) {
	    _pr_gcData.lowSeg = (prword_t*) sp->base;
	}
	if ((prword_t*)sp->limit > _pr_gcData.highSeg) {
	    _pr_gcData.highSeg = (prword_t*) sp->limit;
	}
    }
}

static void FreeSegments(void)
{
    GCSegInfo *si;

    while (freeSegs != 0) {
	LOCK_GC();
	si = freeSegs;
	if (si) {
	    freeSegs = si->next;
	}
	UNLOCK_GC();

	if (!si) {
	    break;
	}
	_MD_FreeGCSegment(si->base, si->limit - si->base);
#ifdef DEBUG
	{
	    char str[256];
	    sprintf(str, "[3] Deallocated bytes at %p\n", si->base);
	    OutputDebugString(str);
	}
#endif
	free(si->hbits);
	free(si);
#ifdef DEBUG
	{
	    char str[256];
	    sprintf(str, "[4] Deallocated bytes at %p\n", si);
	    OutputDebugString(str);
	}
#endif
    }
}

/************************************************************************/

void ScanScanQ(GCScanQ *iscan)
{
    prword_t GCPTR *p;
    prword_t GCPTR **pp;
    prword_t GCPTR **epp;
    GCScanQ nextQ, *scan, *next, *temp;
    prword_t h;
    uprword_t tix;
    GCType *gct;

    if (!iscan->queued) return;

    GCTRACE(GC_MARK, ("begin scanQ @ 0x%x (%d)", iscan, iscan->queued));
    scan = iscan;
    next = &nextQ;
    while (scan->queued) {
	GCTRACE(GC_MARK, ("continue scanQ @ 0x%x (%d)", scan, scan->queued));
	/* Set pointer to current scanQ so that pr_liveObject can find it */
	pScanQ = next;
	next->queued = 0;

	/* Now scan the scan Q */
	pp = scan->q;
	epp = &scan->q[scan->queued];
	scan->queued = 0;
	while (pp < epp) {
	    p = *pp++;
	    h = p[0];
	    tix = GET_TYPEIX(h);
	    /*
	    ** If type code is invalid, or if we don't have a handler for
	    ** the type or if the handler doesn't need to scan it's
	    ** objects (for example, the type is used to alloc C
	    ** strings...).
	    */
#ifdef DEBUG
	    if (tix != FREE_MEMORY_TYPEIX) {
		PR_ASSERT(_pr_gcTypes[tix] != 0);
	    }
#endif
	    if (!(gct = _pr_gcTypes[tix]) || (gct->scan == 0)) {
		continue;
	    }

	    /* Scan object ... */
	    (*gct->scan)(p + 1);
	}

	/* Exchange pointers so that we scan next */
	temp = scan;
	scan = next;
	next = temp;
    }

    pScanQ = iscan;
    PR_ASSERT(nextQ.queued == 0);
    PR_ASSERT(iscan->queued == 0);
}

/*
** Called during root finding step to identify "root" pointers into the
** GC heap. First validate if it is a real heap pointer and then mark the
** object being pointed to and add it to the scan Q for eventual
** scanning.
*/
static void PR_CALLBACK ProcessRoot(void **base, int32 count)
{
    GCSeg *last;
    prword_t *p0, *p, h, tix, *low, *high, *segBase;
    GCType *gct;
#ifdef DEBUG
    void **base0 = base;
#endif

    low = _pr_gcData.lowSeg;
    high = _pr_gcData.highSeg;
    last = segs;
    while (--count >= 0) {
        p0 = (prword_t*) *base++;
        /*
        ** XXX:  
        ** Until Win16 maintains lowSeg and highSeg correctly,
        ** (ie. lowSeg=MIN(all segs) and highSeg = MAX(all segs))
        ** Allways scan through the segment list
        */
#if !defined(XP_PC) || defined(_WIN32)
        if (p0 < low) continue;                  /* below gc heap */
        if (p0 >= high) continue;                /* above gc heap */
#endif
        /* NOTE: inline expansion of InHeap */
        /* Find segment */
        if (!IN_SEGMENT(last,p0)) {
            GCSeg *sp = segs;
            GCSeg *esp = segs + nsegs;
            for (; sp < esp; sp++) {
                if (IN_SEGMENT(sp, p0)) {
                    last = sp;
                    goto find_object;
                }
            }
            continue;
        }

      find_object:
        /* NOTE: Inline expansion of FindObject */
        /* Align p to it's proper boundary before we start fiddling with it */
        p = (prword_t*) ((prword_t)p0 & ~(BYTES_PER_WORD-1L));
        segBase = (prword_t *) last->base;
        do {
            if (IS_HBIT(last, p)) {
                goto winner;
            }
            p--;
        } while (p >= segBase);

        /*
        ** We have a pointer into the heap, but it has no header
        ** bit. This means that somehow the very first object in the heap
        ** doesn't have a header. This is impossible so when debugging
        ** lets abort.
        */
#ifdef DEBUG
        PR_Abort();
#endif

      winner:
        h = p[0];
        if ((h & MARK_BIT) == 0) {
#ifdef DEBUG
            GCTRACE(GC_ROOTS,
		    ("root 0x%p (%d) base0=%p off=%d",
		     p, OBJ_BYTES(h), base0, (base-1) - base0));
#endif

            /* Mark the root we just found */
            p[0] = h | MARK_BIT;

            /*
            ** See if object we just found needs scanning. It must have a
            ** gctype and it must have a scan function to be placed on
            ** the scanQ.
            */
            tix = GET_TYPEIX(h);
	    if (!(gct = _pr_gcTypes[tix]) || (gct->scan == 0)) {
		continue;
	    }

            /*
            ** Put a pointer onto the scan Q. We use the scan Q to avoid
            ** deep recursion on the C call stack. Objects are added to
            ** the scan Q until the scan Q fills up. At that point we
            ** make a call to ScanScanQ which proceeds to scan each of
            ** the objects in the Q. This limits the recursion level by a
            ** large amount though the stack frames get larger to hold
            ** the GCScanQ's.
            */
            pScanQ->q[pScanQ->queued++] = p;
            if (pScanQ->queued == MAX_SCAN_Q) {
                METER(_pr_scanDepth++);
                ScanScanQ(pScanQ);
            }
        }
    }
}

/************************************************************************/

/*
** Clear out header bits for the space occupied by a free chunk.
**
** XXX tune
*/
static void ClearHBits(GCSeg *sp, prword_t *p, size_t bytes)
{
    GCTRACE(GC_SWEEP, ("clearing hbits for 0x%x to 0x%x", p,
		       (char*)p + bytes - 1));
    bytes >>= BYTES_PER_WORD_LOG2;
    while (bytes) {
	CLEAR_HBIT(sp, p);
	bytes--;
	p++;
    }
}

/*
** Empty the freelist for each segment. This is done to make sure that
** the root finding step works properly (otherwise, if we had a pointer
** into a free section, we might not find its header word and abort in
** FindObject)
*/
static void EmptyFreelists(void)
{
    GCFreeChunk GCPTR *cp;
    GCFreeChunk GCPTR *next;
    GCSeg *sp;
    prword_t GCPTR *p;
    size_t chunkSize;
    int32 bin;

    /*
    ** Run over the freelist and make all of the free chunks look like
    ** object debris.
    */
    for (bin = 0; bin < NUM_BINS; bin++) {
        cp = bins[bin];
        while (cp) {
            next = cp->next;
            sp = cp->segment;
            chunkSize = cp->chunkSize >> BYTES_PER_WORD_LOG2;
            p = (prword_t GCPTR *) cp;
            PR_ASSERT(chunkSize != 0);
            p[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, chunkSize);
            SET_HBIT(sp, p);
            cp = next;
        }
        bins[bin] = 0;
    }
}

/*
** Sweep a segment, cleaning up all of the debris. Coallese the debris
** into GCFreeChunk's which are added to the freelist bins.
*/
static PRBool SweepSegment(GCSeg *sp)
{
    prword_t h, tix;
    prword_t GCPTR *p;
    prword_t GCPTR *np;
    prword_t GCPTR *limit;
    GCFreeChunk GCPTR *cp;
    size_t bytes, chunkSize;
    int32 totalFree;
    int32 bin, segmentSize;

    /*
    ** Now scan over the segment's memory in memory order, coallescing
    ** all of the debris into a FreeChunk list.
    */
    totalFree = 0;
    segmentSize = sp->limit - sp->base;
    p = (prword_t GCPTR *) sp->base;
    limit = (prword_t GCPTR *) sp->limit;
    PR_ASSERT(segmentSize > 0);
    while (p < limit) {
	chunkSize = 0;
	cp = (GCFreeChunk GCPTR *) p;

	/* Attempt to coallesce any neighboring free objects */
	for (;;) {
	    PR_ASSERT(IS_HBIT(sp, p) != 0);
	    h = p[0];
	    bytes = OBJ_BYTES(h);
	    PR_ASSERT(bytes != 0);
	    np = (prword_t GCPTR *) ((char GCPTR *)p + bytes);
            tix = GET_TYPEIX(h);
	    if ((h & MARK_BIT) && (tix != FREE_MEMORY_TYPEIX)) {
		/* Not a free object. Time to stop looking */
#ifdef DEBUG
		if (tix != FREE_MEMORY_TYPEIX) {
		    PR_ASSERT(_pr_gcTypes[tix] != 0);
		}
#endif
		p[0] = h & ~(MARK_BIT|FINAL_BIT);
		GCTRACE(GC_SWEEP, ("busy 0x%x (%d)", p, bytes));
		break;
	    }
	    GCTRACE(GC_SWEEP, ("free 0x%x (%d)", p, bytes));

	    /* Found a free object */
            if (_pr_gcTypes[tix] && _pr_gcTypes[tix]->free) {
                (*_pr_gcTypes[tix]->free)(p + 1);
            }
	    chunkSize = chunkSize + bytes;
	    if (np == limit) {
		/* Found the end of heap */
		break;
	    }
	    PR_ASSERT(np < limit);
	    p = np;
	}

	if (chunkSize) {
	    GCTRACE(GC_SWEEP, ("free chunk 0x%x to 0x%x (%d)",
			       cp, (char*)cp + chunkSize - 1, chunkSize));
	    if (chunkSize < MIN_ALLOC) {
		/* Lost a tiny fragment until (maybe) next time */
                METER(meter.wastedBytes += chunkSize);
		p = (prword_t GCPTR *) cp;
		chunkSize >>= BYTES_PER_WORD_LOG2;
		PR_ASSERT(chunkSize != 0);
		p[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, chunkSize);
		SET_HBIT(sp, p);
	    } else {
                /* See if the chunk constitutes the entire segment */
                if ((int32)chunkSize == segmentSize) {
                    /* Free up the segment right now */
		    ShrinkGCHeap(sp);
		    return PR_TRUE;
                }

                /* Put free chunk into the appropriate bin */
                bin = chunkSize >> MIN_ALLOC_LOG2;
                if (bin >= NUM_BINS) {
                    bin = NUM_BINS - 1;
                }
                cp->segment = sp;
		cp->chunkSize = chunkSize;
                cp->next = bins[bin];
                bins[bin] = cp;
                METER(meter.numFreeChunks++);

		ClearHBits(sp, (prword_t GCPTR*)cp, chunkSize);
#ifdef DEBUG
		/*
		** XXX I wonder if the sweeper should always initialize
		** memory and then we can take out the zeroing code in
		** AllocMemory? If we do this, then we need to go nuke
		** the memcpy's and memset's buried in java.
		*/
		if (_pr_gcData.flags & GC_DEBUG) {
		    memset(cp+1, 0xf8, chunkSize - sizeof(*cp));
		}
#endif
		totalFree += chunkSize;
	    }
	}

	/* Advance to next object */
	p = np;
    }

    PR_ASSERT(totalFree <= segmentSize);

    _pr_gcData.freeMemory += totalFree;
    _pr_gcData.busyMemory += (sp->limit - sp->base) - totalFree;
    return PR_FALSE;
}

/************************************************************************/

PRCList _pr_pendingFinalQueue;
PRCList _pr_finalQueue;

typedef struct GCFinalStr {
    PRCList links;
    prword_t *object;
} GCFinal;

/*
** Find pointer to GCFinal struct from the list linkaged embedded in it
*/
#define FinalPtr(_qp) \
    ((GCFinal*) ((char*) (_qp) - offsetof(GCFinal,links)))

#ifdef DEBUG
int32 finalNodes = 0;
#endif

/* XXX revist these: we might want to keep a freelist... */
static GCFinal *AllocFinalNode(void)
{
#ifdef DEBUG
    finalNodes++;
#endif
    return calloc(1, sizeof(GCFinal));
}

static void FreeFinalNode(GCFinal *node)
{
    free(node);
#ifdef DEBUG
    finalNodes--;
#endif
}

/*
** Prepare for finalization. At this point in the GC cycle we have
** identified all of the live objects. For each object on the
** pendingFinalizationQueue see if the object is alive or dead. If it's
** dead, resurrect it and remove it from the pendingFinalizationQueue to
** the finalizationQueue (object's only get finalized once).
**
** Once the pendingFinalizationQueue has been processed we can finish the
** GC and free up memory and release the threading lock. After that we
** can invoke the finalization procs for each object that is on the
** finalizationQueue.
*/
static void PrepareFinalize(void)
{
    PRCList *qp;
    GCFinal *fp;
    prword_t h;
    prword_t *p;
    void (*live_object)(void **base, int32 count);

    /* This must be done under the same lock that the finalizer uses */
    PR_ASSERT( GC_IS_LOCKED() );

    /* cache this ptr */
    live_object = _pr_gcData.liveObject;

    /*
     * Pass #1: Identify objects that are to be finalized, set their
     * FINAL_BIT.
     */
    qp = _pr_pendingFinalQueue.next;
    while (qp != &_pr_pendingFinalQueue) {
	fp = FinalPtr(qp);
	qp = qp->next;
	h = fp->object[0];		/* Grab header word */
	if (h & MARK_BIT) {
	    /* Object is already alive */
	    continue;
	}
	PR_ASSERT(_pr_gcTypes[GET_TYPEIX(h)] != 0);
	PR_ASSERT(_pr_gcTypes[GET_TYPEIX(h)]->finalize != 0);
	fp->object[0] |= FINAL_BIT;
	GCTRACE(GC_FINAL, ("moving %p (%d) to finalQueue",
			   fp->object, OBJ_BYTES(h)));
    }

    /*
     * Pass #2: For each object that is going to be finalized, move it to
     * the finalization queue and resurrect it
     */
    qp = _pr_pendingFinalQueue.next;
    while (qp != &_pr_pendingFinalQueue) {
	fp = FinalPtr(qp);
	qp = qp->next;
	h = fp->object[0];		/* Grab header word */
	if ((h & FINAL_BIT) == 0) {
	    continue;
	}

	/* Resurrect the object and any objects it refers to */
        p = &fp->object[1];
	(*live_object)((void**)&p, 1);
	PR_REMOVE_LINK(&fp->links);
	PR_APPEND_LINK(&fp->links, &_pr_finalQueue);
    }
}

/*
** Scan the finalQ, marking each and every object on it live.  This is
** necessary because we might do a GC before objects that are on the
** final queue get finalized. Since there are no other references
** (otherwise they would be on the final queue), we have to scan them.
** This really only does work if we call the GC before the finalizer
** has a chance to do its job.
*/
void _PR_ScanFinalQueue(void *notused)
{
    PRCList *qp;
    GCFinal *fp;
    prword_t *p;
    void (*live_object)(void **base, int32 count);

    live_object = _pr_gcData.liveObject;
    qp = _pr_finalQueue.next;
    while (qp != &_pr_finalQueue) {
	fp = FinalPtr(qp);
	GCTRACE(GC_FINAL, ("marking 0x%x (on final queue)", fp->object));
        p = &fp->object[1];
	(*live_object)((void**)&p, 1);
	qp = qp->next;
    }
}

#ifdef DEBUG
int32
QueueLength(PRCList* queue)
{
    int32 count = 0;
    PRCList* qp = queue->next;
    while (qp != queue) {
	count++;
	qp = qp->next;
    }
    return count;
}
#endif

void FinalizerLoop(void)
{
    GCFinal *fp;
    prword_t GCPTR *p;
    prword_t h, tix;
    GCType *gct;

    LOCK_GC();
    for (;;) {
	PR_Wait(_pr_gcData.lock, LL_MAXINT);

#if defined(_WIN32) && defined(MSWINDBGMALLOC)
	_RPT0(_CRT_WARN, "--finalization---------------------------------------\n");
	_RPT3(_CRT_WARN, "finalQueue %ld pendingFinalQueue %ld nodes %ld\n", 
	      QueueLength(&_pr_finalQueue), QueueLength(&_pr_pendingFinalQueue), finalNodes);
#endif
	GCTRACE(GC_FINAL, ("begin finalization"));
	while (_pr_finalQueue.next != &_pr_finalQueue) {
	    fp = FinalPtr(_pr_finalQueue.next);
	    PR_REMOVE_LINK(&fp->links);
	    p = fp->object;
	    FreeFinalNode(fp);

	    h = p[0];		/* Grab header word */
	    tix = GET_TYPEIX(h);
	    if ((gct = _pr_gcTypes[tix]) == 0) {
		/* Heap is probably corrupted */
		continue;
	    }
	    GCTRACE(GC_FINAL, ("finalize 0x%x (%d)", p, OBJ_BYTES(h)));

	    /*
	    ** Give up the GC lock so that other threads can allocate memory
	    ** while this finalization method is running. Get it back
	    ** afterwards so that the list remains thread safe.
	    */
        UNLOCK_GC();
	    PR_ASSERT(gct->finalize != 0);
	    (*gct->finalize)(p + 1);
        LOCK_GC();
	}
	GCTRACE(GC_FINAL, ("end finalization"));
    }
}

static void NotifyFinalizer(void)
{
    if (!PR_CLIST_IS_EMPTY(&_pr_finalQueue)) {
    PR_ASSERT( GC_IS_LOCKED() );
	PR_Notify(_pr_gcData.lock);
    }
}

void _PR_CreateFinalizer(void)
{
    if (!_pr_gcData.finalizer) {
	_pr_gcData.finalizer = PR_CreateThread("finalizer", 1, 0);
    
    if (_pr_gcData.finalizer == NULL)
	    /* We are doomed if we can't start the finalizer */
        PR_Abort();

	_pr_gcData.finalizer->flags |= _PR_SYSTEM;
	if (PR_Start(_pr_gcData.finalizer, (void (*)(void*,void*))FinalizerLoop,
		     0, 0) < 0) {
	    /* We are doomed if we can't start the finalizer */
	    PR_Abort();
	}
    }
}

PR_PUBLIC_API(void) PR_ForceFinalize()
{
    LOCK_GC();
    while (!PR_CLIST_IS_EMPTY(&_pr_finalQueue)) {
    PR_ASSERT( GC_IS_LOCKED() );
	PR_Notify(_pr_gcData.lock);
    }
    UNLOCK_GC();

    /* XXX I don't know how to make it wait (yet) */
}

/************************************************************************/

/*
** Perform a complete garbage collection
*/
#include "prdump.h"     /* XXX */


static void dogc(void)
{
    RootFinder *rf;
    GCScanQ scanQ;
    GCSeg *sp, *esp;
#ifdef GCMETER
    int64 start, end;
#endif

#ifdef GCMETER
    start = PR_NowMS();
#endif

    /*
    ** Stop all of the other threads. This also promises to capture the
    ** register state of each and every thread
    */
    PR_SingleThread();

#ifdef GCMETER
    /* Reset meter info */
    if (_pr_gcMeter & _GC_METER_STATS) {
        fprintf(stderr,
                "[GCSTATS: zeroed:%d busy:%d, alloced:%d+wasted:%d+free:%d = total:%d]\n",
                meter.zeroBytes, _pr_gcData.busyMemory,
                meter.allocBytes, meter.wastedBytes, _pr_gcData.freeMemory,
                _pr_gcData.allocMemory);
    }        
    memset(&meter, 0, sizeof(meter));
#endif

    PR_LOG(GC, out, ("begin mark phase; busy=%d free=%d total=%d",
                     _pr_gcData.busyMemory, _pr_gcData.freeMemory,
                     _pr_gcData.allocMemory));

    if (_pr_beginGCHook) {
	(*_pr_beginGCHook)(_pr_beginGCHookArg);
    }

    /*
    ** Initialize scanQ to all zero's so that root finder doesn't walk
    ** over it...
    */
    memset(&scanQ, 0, sizeof(scanQ));
    pScanQ = &scanQ;

    /******************************************/
    /* MARK PHASE */

    EmptyFreelists();

    /* Find root's */
    PR_LOG(GC, warn,
           ("begin mark phase; busy=%d free=%d total=%d",
	    _pr_gcData.busyMemory, _pr_gcData.freeMemory,
            _pr_gcData.allocMemory));
    METER(_pr_scanDepth = 0);
    rf = _pr_rootFinders;
    while (rf) {
	GCTRACE(GC_ROOTS, ("finding roots in %s", rf->name));
	(*rf->func)(rf->arg);
	rf = rf->next;
    }
    GCTRACE(GC_ROOTS, ("done finding roots"));

    /* Scan remaining object's that need scanning */
    ScanScanQ(&scanQ);
    PR_ASSERT(pScanQ == &scanQ);
    PR_ASSERT(scanQ.queued == 0);
    METER({
	if (_pr_scanDepth > _pr_maxScanDepth) {
	    _pr_maxScanDepth = _pr_scanDepth;
	}
    });

    /******************************************/
    /* FINALIZATION PHASE */

    METER(_pr_scanDepth = 0);
    PrepareFinalize();

    /* Scan any resurrected objects found during finalization */
    ScanScanQ(&scanQ);
    PR_ASSERT(pScanQ == &scanQ);
    PR_ASSERT(scanQ.queued == 0);
    METER({
	if (_pr_scanDepth > _pr_maxScanDepth) {
	    _pr_maxScanDepth = _pr_scanDepth;
	}
    });

    /******************************************/
    /* SWEEP PHASE */

    /*
    ** Sweep each segment clean. While we are at it, figure out which
    ** segment has the most free space and make that the current segment.
    */
    GCTRACE(GC_SWEEP, ("begin sweep phase"));
    _pr_gcData.freeMemory = 0;
    _pr_gcData.busyMemory = 0;
    sp = segs;
    esp = sp + nsegs;
    while (sp < esp) {
        if (SweepSegment(sp)) {
            /*
            ** Segment is now free and has been replaced with a different
            ** segment object.
            */
            esp--;
            continue;
        }
        sp++;
    }

#ifdef GCMETER
    end = PR_NowMS();
    LL_SUB(end, end, start);
    PR_LOG(GC, out,
	   ("done; busy=%d free=%d time=%lldms",
	    _pr_gcData.busyMemory, _pr_gcData.freeMemory, end));
#ifdef DEBUG_kipp	/* jwz asked that this be removed */
    fprintf(stderr, "[GC done busy=%d free=%d chunks=%d total=%d time=%lldms]\n",
	    _pr_gcData.busyMemory, _pr_gcData.freeMemory, 
            meter.numFreeChunks, _pr_gcData.allocMemory, end);
#endif
    if (_pr_gcMeter & _GC_METER_FREE_LIST) {
        int32 bin;
        fprintf(stderr,
               "Freelist bins: minAlloc=%d numBins=%d maxBin=%d\n",
                MIN_ALLOC, NUM_BINS, (NUM_BINS - 1) * MIN_ALLOC);
        for (bin = 0; bin < NUM_BINS; bin++) {
            GCFreeChunk *cp = bins[bin];
            while (cp != NULL) {
                fprintf(stderr, "%3d: %p %8d\n", bin, cp, cp->chunkSize);
                cp = cp->next;
            }
        }
    }
#endif

    if (_pr_endGCHook) {
	(*_pr_endGCHook)(_pr_endGCHookArg);
    }

    /* clear the running total of the bytes allocated via BigAlloc() */
    bigAllocBytes = 0;

    /* And resume multi-threading */
    PR_MultiThread();

    /* Kick finalizer */
    NotifyFinalizer();
}

PR_PUBLIC_API(void) PR_GC(void)
{
#ifdef HW_THREADS
	LOCK_SCHEDULER();
#endif

    LOCK_GC();
    dogc();
    UNLOCK_GC();

#ifdef HW_THREADS
	UNLOCK_SCHEDULER();
#endif

}

/*******************************************************************************
 * Heap Walker
 ******************************************************************************/

typedef void (*WalkObject_t)(FILE *out, GCType* tp, prword_t *obj,
			     size_t bytes, PRBool detailed);
typedef void (*WalkUnknown_t)(FILE *out, prword_t tix, prword_t *p,
			      size_t bytes, PRBool detailed);
typedef void (*WalkFree_t)(FILE *out, prword_t *p, size_t size, PRBool detailed);
typedef void (*WalkSegment_t)(FILE *out, GCSeg* sp, PRBool detailed);

static void
pr_WalkSegment(FILE* out, GCSeg* sp, PRBool detailed,
	       char* enterMsg, char* exitMsg,
	       WalkObject_t walkObject, WalkUnknown_t walkUnknown, WalkFree_t walkFree)
{
    prword_t *p, *limit;

    p = (prword_t *) sp->base;
    limit = (prword_t *) sp->limit;
    if (enterMsg)
	fprintf(out, enterMsg, p);
    while (p < limit)
    {
	if (IS_HBIT(sp, p)) /* Is this an object header? */
	{
	    prword_t h = p[0];
	    prword_t tix = GET_TYPEIX(h);
	    size_t bytes = OBJ_BYTES(h);
	    prword_t* np = (prword_t*) ((char*)p + bytes);

	    GCType* tp = _pr_gcTypes[tix];
	    if ((0 != tp) && walkObject)
		walkObject(out, tp, p, bytes, detailed);
	    else if (walkUnknown)
		walkUnknown(out, tix, p, bytes, detailed);
	    p = np;
	}
	else
	{
	    /* Must be a freelist item */
	    size_t size = ((GCFreeChunk*)p)->chunkSize;
	    if (walkFree)
		walkFree(out, p, size, detailed);
	    p = (prword_t*)((char*)p + size);
	}
    }
    if (p != limit)
	fprintf(out, "SEGMENT OVERRUN (end should be at 0x%p)\n", limit);
    if (exitMsg)
	fprintf(out, exitMsg, p);
}

static void
pr_WalkSegments(FILE *out, WalkSegment_t walkSegment, PRBool detailed)
{
    GCSeg *sp = segs;
    GCSeg *esp;

    LOCK_GC();
    esp = sp + nsegs;
    while (sp < esp)
    {
	walkSegment(out, sp, detailed);
	sp++;
    }
    fprintf(out, "End of heap\n");
    UNLOCK_GC();
}

/*******************************************************************************
 * Heap Dumper
 ******************************************************************************/

PR_PUBLIC_API(void)
PR_DumpIndent(FILE *out, int indent)
{
    while (--indent >= 0)
	fprintf(out, " ");
}

static void
PR_DumpHexWords(FILE *out, prword_t *p, int nWords, 
		int indent, int nWordsPerLine)
{
    while (nWords > 0)
    {
	int i;

	PR_DumpIndent(out, indent);
	i = nWordsPerLine;
	if (i > nWords)
	    i = nWords;
	nWords -= i;
	while (i--)
	{
	    fprintf(out, "0x%.8X", *p++);
	    if (i)
		fputc(' ', out);
	}
	fputc('\n', out);
    }
}

static void
pr_DumpObject(FILE *out, GCType* tp, prword_t *p, 
	      size_t bytes, PRBool detailed)
{
    fprintf(out, "0x%p: 0x%.6X  ", p, bytes);
    if (tp->dump)
	(*tp->dump)(out, (void*) (p + 1), detailed, 0);
    PR_DumpHexWords(out, p, bytes>>2, 22, 4);
}

static void
pr_DumpUnknown(FILE *out, prword_t tix, prword_t *p, 
	       size_t bytes, PRBool detailed)
{
    fprintf(out, "0x%p: 0x%.6X  ", p, bytes);
    fprintf(out, "UNKNOWN KIND %d\n", tix);
    PR_DumpHexWords(out, p, bytes>>2, 22, 4);
}

static void
pr_DumpFree(FILE *out, prword_t *p, size_t size, PRBool detailed)
{
    fprintf(out, "0x%p: 0x%.6X  FREE\n", p, size);
}

static void
pr_DumpSegment(FILE* out, GCSeg* sp, PRBool detailed)
{
    pr_WalkSegment(out, sp, detailed,
		   "\n   Address: Length\n0x%p: Beginning of segment\n",
		   "0x%p: End of segment\n\n",
		   pr_DumpObject, pr_DumpUnknown, pr_DumpFree);
}

/*
** Dump out the GC heap.
*/
PR_PUBLIC_API(void)
PR_DumpGCHeap(FILE *out, PRBool detailed)
{
    pr_WalkSegments(out, pr_DumpSegment, detailed);
}

PR_PUBLIC_API(void)
PR_DumpMemory(PRBool detailed)
{
    PR_DumpToFile("memory.out", "Dumping memory", PR_DumpGCHeap, detailed);
}

/*******************************************************************************
 * Heap Summary Dumper
 ******************************************************************************/

PRSummaryPrinter summaryPrinter = NULL;
void* summaryPrinterClosure = NULL;

PR_PUBLIC_API(void) 
PR_RegisterSummaryPrinter(PRSummaryPrinter fun, void* closure)
{
    summaryPrinter = fun;
    summaryPrinterClosure = closure;
}

static void
pr_SummarizeObject(FILE *out, GCType* tp, prword_t *p,
		   size_t bytes, PRBool detailed)
{
    if (tp->summarize)
	(*tp->summarize)((void GCPTR*)(p + 1), bytes);
}

static void
pr_DumpSummary(FILE* out, GCSeg* sp, PRBool detailed)
{
    pr_WalkSegment(out, sp, detailed, NULL, NULL,
  		   pr_SummarizeObject, NULL, NULL);
}

PR_PUBLIC_API(void)
PR_DumpGCSummary(FILE *out, PRBool detailed)
{
    if (summaryPrinter) {
	pr_WalkSegments(out, pr_DumpSummary, detailed);
	summaryPrinter(out, summaryPrinterClosure);
    }
#ifdef _WIN32
    {
	extern int32 totalVirtual;
	fprintf(out, "Virtual memory reserve: %ld, in use: %ld (%ld%%)\n",
		GC_VMLIMIT, totalVirtual, (totalVirtual * 100 / GC_VMLIMIT));
    }
#endif
#if 0
    fprintf(out, "\nFinalizable objects:\n");
    {
	PRCList *qp;
	qp = _pr_pendingFinalQueue.next;
	while (qp != &_pr_pendingFinalQueue) {
	    GCFinal* fp = FinalPtr(qp);
	    prword_t h = fp->object[0];		/* Grab header word */
	    prword_t tix = GET_TYPEIX(h);
	    GCType* tp = _pr_gcTypes[tix];
	    size_t bytes = OBJ_BYTES(h);
	    pr_DumpObject(out, tp, fp->object, bytes, PR_FALSE);
	    qp = qp->next;
	}
    }
#endif
}

PR_PUBLIC_API(void)
PR_DumpMemorySummary(void)
{
    PR_DumpToFile("memory.out", "Memory Summary", PR_DumpGCSummary, PR_FALSE);
}

/*******************************************************************************
 * End Of Heap Walker 
 ******************************************************************************/

#if defined(DEBUG) && defined(_WIN32)
static void DumpApplicationHeap(FILE *out, HANDLE heap)
	{
	PROCESS_HEAP_ENTRY entry;
	DWORD err;

	if (!HeapLock(heap))
		OutputDebugString("Can't lock the heap.\n");
	entry.lpData = 0;
	fprintf(out, "   address:       size ovhd region\n");
	while (HeapWalk(heap, &entry))
		{
		WORD flags = entry.wFlags;

		fprintf(out, "0x%.8X: 0x%.8X 0x%.2X 0x%.2X  ", entry.lpData, entry.cbData,
					 entry.cbOverhead, entry.iRegionIndex);
		if (flags & PROCESS_HEAP_REGION)
			fprintf(out, "REGION  committedSize=0x%.8X uncommittedSize=0x%.8X firstBlock=0x%.8X lastBlock=0x%.8X",
					entry.Region.dwCommittedSize, entry.Region.dwUnCommittedSize,
					entry.Region.lpFirstBlock, entry.Region.lpLastBlock);
		else if (flags & PROCESS_HEAP_UNCOMMITTED_RANGE)
			fprintf(out, "UNCOMMITTED");
		else if (flags & PROCESS_HEAP_ENTRY_BUSY)
			{
			if (flags & PROCESS_HEAP_ENTRY_DDESHARE)
				fprintf(out, "DDEShare ");
			if (flags & PROCESS_HEAP_ENTRY_MOVEABLE)
				fprintf(out, "Moveable Block  handle=0x%.8X", entry.Block.hMem);
			else
				fprintf(out, "Block");
			}
		fprintf(out, "\n");
		}
	if ((err = GetLastError()) != ERROR_NO_MORE_ITEMS)
		fprintf(out, "ERROR %d iterating through the heap\n", err);
	if (!HeapUnlock(heap))
		OutputDebugString("Can't unlock the heap.\n");
	}

static void DumpApplicationHeaps(FILE *out)
	{
	HANDLE mainHeap;
	HANDLE heaps[100];
	int32 nHeaps;
	int32 i;

	mainHeap = GetProcessHeap();
	nHeaps = GetProcessHeaps(100, heaps);
	if (nHeaps > 100)
		nHeaps = 0;
	fprintf(out, "%d heaps:\n", nHeaps);
	for (i = 0; i<nHeaps; i++)
		{
		HANDLE heap = heaps[i];

		fprintf(out, "Heap at 0x%.8X", heap);
		if (heap == mainHeap)
			fprintf(out, " (main)");
		fprintf(out, ":\n");
		DumpApplicationHeap(out, heap);
		fprintf(out, "\n");
		}
	fprintf(out, "End of heap dump\n\n");
	}


PR_PUBLIC_API(void) PR_DumpApplicationHeaps(void)
	{
	FILE *out;

	OutputDebugString("Dumping heaps...");
	out = fopen("heaps.out", "a");
	if (!out)
		OutputDebugString("Can't open \"heaps.out\"\n");
	else
		{
		struct tm *newtime;
		time_t aclock;

		time(&aclock);
		newtime = localtime(&aclock);
		fprintf(out, "Heap dump on %s\n", asctime(newtime));	/* Print current time */
		DumpApplicationHeaps(out);
		fprintf(out, "\n\n");
		fclose(out);
		}
	OutputDebugString(" done\n");
	}
#else

PR_PUBLIC_API(void) PR_DumpApplicationHeaps(void)
	{
	}
#endif

/************************************************************************/

/*
** Scan the freelist bins looking for a big enough chunk of memory to
** hold "bytes" worth of allocation. "bytes" already has the
** per-allocation header added to it. Return a pointer to the object with
** its per-allocation header already prepared.
*/
static prword_t *BinAlloc(int cbix, size_t bytes, int dub)
{
    GCFreeChunk *cp, *cpNext;
    GCFreeChunk **cpp;
    GCSeg *sp;
    size_t chunkSize;
    prword_t words;
    prword_t GCPTR *p;
    prword_t GCPTR *np;
    prword_t h;
    int32 bin, newbin;

    /* Compute bin that allocation belongs in */
    bin = bytes >> MIN_ALLOC_LOG2;
    if (bin >= NUM_BINS) {
        bin = NUM_BINS - 1;
    }

    /* XXX Use a bitfield to skip over empty bins */

    /* Search in the bin, and larger bins, for a big enough piece */
    for (; bin < NUM_BINS; bin++) {
        cpp = &bins[bin];
        while ((cp = *cpp) != 0) {
            p = (prword_t GCPTR*) cp;
            sp = cp->segment;
            cpNext = cp->next;
            chunkSize = cp->chunkSize;

#ifdef DEBUG
            if (bin != NUM_BINS - 1) {
                PR_ASSERT(chunkSize >= bytes);
            }
#endif

            /* All memory is double aligned on 64 bit machines... */
#ifndef IS_64
            if (dub && (((prword_t)p & (BYTES_PER_DWORD-1)) == 0)) {
                /*
                ** Need an extra word if double aligning and "p" is
                ** misaligned. Note that we want "p" to NOT be on a
                ** double-word boundary so that the data that follows the one
                ** word object header will be on a double-word boundary!
                */
                if (chunkSize < bytes + BYTES_PER_WORD) {
                    cpp = &cp->next;
                    continue;
                }

                /*
                ** Consume the first word of the chunk with a dummy
                ** unreferenced object.
                */
                p[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, 1);
                SET_HBIT(sp, p);
                p++;
                chunkSize -= BYTES_PER_WORD;
                PR_ASSERT(((prword_t)p & (BYTES_PER_DWORD-1)) != 0);
            } else
#endif
            {
                if (chunkSize < bytes) {
                    cpp = &cp->next;
                    continue;
                }
            }
            

            /* Found a large enough piece */
            np = (prword_t GCPTR*) ((char GCPTR*) p + bytes);
            chunkSize -= bytes;
            if (chunkSize < MIN_ALLOC) {
                /*
                ** Remaining chunk is too small to hold a GCFreeChunk. Just
                ** toss the fragment away, marking it as belonging to the
                ** free object allocator. GC Sweeper will put the pieces back
                ** together.
                */
                if (chunkSize) {
                    words = chunkSize >> BYTES_PER_WORD_LOG2;
                    PR_ASSERT(words != 0);
                    np[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, words);
                    SET_HBIT(sp, np);
                    _pr_gcData.freeMemory -= chunkSize;
                    METER(meter.wastedBytes += chunkSize);
                }
                *cpp = cpNext;
            } else {
                /*
                ** Setup GCFreeChunk data for remaining portion. It might
                ** need to move to a new bin.
                */
                PR_ASSERT(chunkSize != 0);
                newbin = chunkSize >> MIN_ALLOC_LOG2;
                if (newbin >= NUM_BINS - 1) {
                    newbin = NUM_BINS - 1;
                }
                cp = (GCFreeChunk*) np;
                cp->segment = sp;
                cp->chunkSize = chunkSize;
                if (newbin != bin) {
                    *cpp = cpNext;                /* take old cp off old list */
                    cp->next = bins[newbin];      /* put new cp on new list */
                    bins[newbin] = cp;
                } else {
                    cp->next = cpNext;
                    *cpp = (GCFreeChunk GCPTR*) np;
                }
            }
            h = MAKE_HEADER(cbix, (bytes >> BYTES_PER_WORD_LOG2));
            p[0] = h;
            SET_HBIT(sp, p);
            _pr_gcData.freeMemory -= bytes;
            _pr_gcData.busyMemory += bytes;
            return p;
        }
    }
    return 0;
}

/*
** Allocate a piece of memory that is "big" in it's own segment.  Make
** the object consume the entire segment to avoid fragmentation.  When
** the object is no longer referenced, the segment is freed.
*/
static prword_t *BigAlloc(int cbix, size_t bytes, int dub)
{
    GCSeg *sp;
    prword_t *p, h;
    int32 chunkSize;

    /*
    ** If the number of bytes allocated via BigAlloc() since the last GC
    ** exceeds BIG_ALLOC_GC_SIZE then do a GC Now...
    */
    if (bigAllocBytes >= BIG_ALLOC_GC_SIZE) {
        dogc();
    }
    bigAllocBytes += bytes;

    /* Get a segment to hold this allocation */
#ifndef IS_64
    /* XXX this has to be a waste with all the rounding up going on, right? */
    sp = GrowHeapExactly(bytes + (dub ? BYTES_PER_WORD : 0));
#else
    sp = GrowHeapExactly(bytes);
#endif

    if (sp) {
        p = (prword_t*) sp->base;
        chunkSize = sp->limit - sp->base;

        /* All memory is double aligned on 64 bit machines... */
#ifndef IS_64
        if (dub && (((prword_t)p & (BYTES_PER_DWORD-1)) == 0)) {
            /*
            ** Consume the first word of the chunk with a dummy
            ** unreferenced object.
            */
            p[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, 1);
            SET_HBIT(sp, p);
            p++;
            chunkSize -= BYTES_PER_WORD;
            _pr_gcData.freeMemory -= BYTES_PER_WORD;
            _pr_gcData.busyMemory += BYTES_PER_WORD;
            PR_ASSERT(((prword_t)p & (BYTES_PER_DWORD-1)) != 0);
        }
#endif
        /* Consume the *entire* segment with a single allocation */
        h = MAKE_HEADER(cbix, (chunkSize >> BYTES_PER_WORD_LOG2));
        p[0] = h;
        SET_HBIT(sp, p);
        _pr_gcData.freeMemory -= chunkSize;
        _pr_gcData.busyMemory += chunkSize;
	return p;
    }
    return 0;
}

int allocationEnabled = 1;	/* we disable gc allocation during low memory conditions */

PR_PUBLIC_API(void)
PR_EnableAllocation(int yesOrNo)
{
    allocationEnabled = yesOrNo;
}

/*
** Allocate memory from the GC Heap. Performs garbage collections if
** memory gets tight and grows the heap as needed. May return NULL if
** memory cannot be found.
**
** XXX win16: should we use this to do the HUGE allocs for big things?
*/
PR_PUBLIC_API(prword_t GCPTR *)
PR_AllocMemory(prword_t requestedBytes, int tix, int flags)
{
    prword_t bytes, dataBytes;
    prword_t GCPTR *p;
    int tries;
    GCFinal *final;
    prword_t *(*alloc)(int cbix, size_t bytes, int dub);
#if defined(XP_PC) && !defined(_WIN32)
    prword_t shiftVal;
#endif

    if (!allocationEnabled) return NULL;

#ifdef DEBUG
    if (_pr_do_a_dump) {
	int64 pause;

	LL_I2L(pause, 1000000L);
	/*
	** Collect, pause for a second (lets finalizer run), and then GC
	** again.
	*/
	PR_GC();
	PR_Sleep(pause);
	PR_GC();
	PR_DumpGCHeap(_pr_dump_file, PR_TRUE);
	_pr_do_a_dump = 0;
    }
#endif

    PR_ASSERT(_pr_gcTypes[tix] != 0);
    final = 0;
    tries = 0;

    bytes = requestedBytes;
#if defined(XP_PC) && !defined(_WIN32)
    PR_ASSERT( bytes < MAX_ALLOC_SIZE );
#endif
    /*
    ** Align bytes to a multiple of a prword_t, then add in enough space
    ** to hold the header word.
    **
    ** MSVC 1.52 crashed on the ff. code because of the "complex" shifting :-(
    */
#if !defined(XP_PC) || defined(_WIN32) 
    if (flags & PR_ALLOC_DOUBLE) {
	bytes = (bytes + BYTES_PER_DWORD - 1) >> BYTES_PER_DWORD_LOG2;
	bytes <<= BYTES_PER_DWORD_LOG2;
    } else {
	bytes = (bytes + BYTES_PER_WORD - 1) >> BYTES_PER_WORD_LOG2;
	bytes <<= BYTES_PER_WORD_LOG2;
    }

    bytes += sizeof(prword_t);
    bytes = ((bytes + MIN_ALLOC - 1) >> MIN_ALLOC_LOG2) << MIN_ALLOC_LOG2;

#else 
    /* 
    ** For WIN16 the shifts have been broken out into separate statements
    ** to prevent the compiler from crashing...
    */
    if (flags & PR_ALLOC_DOUBLE) {
	bytes += BYTES_PER_DWORD - 1L;
	shiftVal = BYTES_PER_DWORD_LOG2;
    } else {
	bytes += BYTES_PER_WORD - 1L;
	shiftVal = BYTES_PER_WORD_LOG2;
    }
    bytes >>= shiftVal;
    bytes <<= shiftVal;

    bytes += sizeof(prword_t);
    bytes += MIN_ALLOC - 1L;
    shiftVal = MIN_ALLOC_LOG2;
    bytes >>= shiftVal;
    bytes <<= shiftVal;
#endif
    
    dataBytes = bytes - sizeof(prword_t);

    if (_pr_gcTypes[tix]->finalize != 0) {
	/*
	** Allocate a GCFinal struct for this object in advance. Don't put
	** it on the pending list until we have allocated the object
	*/
	final = AllocFinalNode();
	if (!final) {
	    /* XXX THIS IS NOT ACCEPTABLE! */
	    return 0;
	}
    }
    LOCK_GC();

    if ((bytes >= BIG_ALLOC) && (nsegs < MAX_SEGS)) {
	alloc = BigAlloc;
    } else {
	alloc = BinAlloc;
    }
    p = (*alloc)(tix, (size_t)bytes, flags & PR_ALLOC_DOUBLE);
    if (p) {
	goto found;
    }

    /* Run garbage collector */
    GCTRACE(GC_ALLOC, ("force GC: want %d", bytes));
    dogc();
    PR_ASSERT( GC_IS_LOCKED() );

    /* Try again now that gc has run */
    p = (*alloc)(tix, (size_t)bytes, flags & PR_ALLOC_DOUBLE);
    if (p) {
	goto found;
    }

    /*
    ** Last resort: if we can grow the heap, do so now.
    */
    if (_pr_gcData.allocMemory < _pr_gcData.maxMemory) {
        if (!GrowHeap(PR_MAX(bytes,segmentSize))) {
            goto lost;
        }
    }
    p = BinAlloc(tix, (size_t)bytes, flags & PR_ALLOC_DOUBLE);
    if (!p) {
        /* Total lossage */
        goto lost;
    }

  found:
    if (final) {
	GCTRACE(GC_ALLOC, ("alloc 0x%x (%d) final=0x%x", p, bytes, final));
	final->object = p;
	PR_APPEND_LINK(&final->links, &_pr_pendingFinalQueue);
    } else {
	GCTRACE(GC_ALLOC, ("alloc 0x%x (%d)", p, bytes));
    }

    /*
    ** XXX This is a total hack that eliminates duplicate monitor entry
    ** for java. This total hack speeds up memory allocation alot!
    */
    if (flags & PR_ALLOC_ZERO_HANDLE) {
        p[1] = 0;
        p[2] = 0;
    }

    UNLOCK_GC();
    METER(meter.allocBytes += bytes);
    METER(meter.wastedBytes += (bytes - requestedBytes));
    if (flags & PR_ALLOC_CLEAN) {
	memset(p + 1, 0, (size_t)dataBytes);
	METER(meter.zeroBytes += dataBytes);
    }
#ifdef DEBUG
    else if (_pr_gcData.flags & GC_DEBUG) {
	/*
	** Because the caller didn't request a zeroing of memory we
	** should fill it with a pattern that won't trigger the assert in
	** LiveObject.
	*/
	memset(p+1, 0, (size_t)dataBytes);
    }
#endif
    if (freeSegs) {
	FreeSegments();
    }
    return p + 1;

  lost:
    /* Out of memory */
    if (final) {
	FreeFinalNode(final);
    }
    UNLOCK_GC();
    if (freeSegs) {
	FreeSegments();
    }
    return 0;
}

PR_PUBLIC_API(void) PR_InitGC(prword_t flags, prword_t initialHeapSize)
{
    static char firstTime = 1;

    if (!firstTime) return;
    firstTime = 0;

    /* This *MUST* be true */
    PR_ASSERT(sizeof(GCFreeChunk) <= MIN_ALLOC);

#if defined(XP_PC) && !defined(_WIN32)
    PR_ASSERT( initialHeapSize < MAX_ALLOC_SIZE );
#endif

    /* Get memory from MD code */
#ifdef DEBUG
    {
	char *ev = getenv("GC_SEGMENT_SIZE");
	if (ev && ev[0]) {
	    initialHeapSize = atoi(ev);
	    segmentSize = (size_t)initialHeapSize;
	}
	ev = getenv("GC_FLAGS");
	if (ev && ev[0]) {
	    flags |= atoi(ev);
	}
#ifdef GCMETER
        ev = getenv("GC_METER");
        if (ev && ev[0]) {
            _pr_gcMeter = atoi(ev);
        }
#endif
    }
#endif
    _pr_gcData.maxMemory   = MAX_SEGS * segmentSize;
    _pr_gcData.liveObject  = ProcessRoot;
    _pr_gcData.processRoot = ProcessRoot;

#ifdef DEBUG
    _pr_gcData.liveObjectLog  = (void (*)(const char*, ...)) nop;
    _pr_gcData.processRootLog = (void (*)(const char*, ...)) nop;
#endif

    PR_INIT_CLIST(&_pr_pendingFinalQueue);
    PR_INIT_CLIST(&_pr_finalQueue);
    _PR_InitGC(flags);

    /* Create finalizer thread */
    _PR_CreateFinalizer();
    /* Initialize the GC heap */
    GrowHeap((initialHeapSize < segmentSize)
             ? segmentSize
             : initialHeapSize);
}

#if defined(XP_PC) && !defined(_WIN32)
/*
** For WIN16 the GC_IN_HEAP() macro must call the private InHeap function.
** This public wrapper function makes this possible...
*/
PR_PUBLIC_API(PRBool)
PR_GC_In_Heap(void GCPTR *object)
{
	return InHeap( object ) != NULL;	
}
#endif

#ifdef DEBUG
void _PR_DumpSegmentFreelist(GCSeg *sp)
{
#if 0
    GCFreeChunk *cp;
    int32 bin;

    for (bin = 0; bin < NUM_BINS; bin++) {
        cp = bins[bin];
        while (cp) {
            fprintf(stderr, "%08x: %5d -> %08x\n", cp, cp->chunkSize, cp->next);
            cp = cp->next;
        }
    }
#endif
}
#endif