| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
| /* ***** BEGIN LICENSE BLOCK ***** |
| * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
| * |
| * The contents of this file are subject to the Mozilla Public License Version |
| * 1.1 (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * http://www.mozilla.org/MPL/ |
| * |
| * Software distributed under the License is distributed on an "AS IS" basis, |
| * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
| * for the specific language governing rights and limitations under the |
| * License. |
| * |
| * The Original Code is the Netscape Portable Runtime (NSPR). |
| * |
| * The Initial Developer of the Original Code is |
| * Netscape Communications Corporation. |
| * Portions created by the Initial Developer are Copyright (C) 1998-2000 |
| * the Initial Developer. All Rights Reserved. |
| * |
| * Contributor(s): |
| * |
| * Alternatively, the contents of this file may be used under the terms of |
| * either the GNU General Public License Version 2 or later (the "GPL"), or |
| * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| * in which case the provisions of the GPL or the LGPL are applicable instead |
| * of those above. If you wish to allow use of your version of this file only |
| * under the terms of either the GPL or the LGPL, and not to allow others to |
| * use your version of this file under the terms of the MPL, indicate your |
| * decision by deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL or the LGPL. If you do not delete |
| * the provisions above, a recipient may use your version of this file under |
| * the terms of any one of the MPL, the GPL or the LGPL. |
| * |
| * ***** END LICENSE BLOCK ***** */ |
| |
| #include <string.h> |
| #include <stddef.h> |
| #include <stdarg.h> |
| #include <time.h> |
| |
| #ifdef WIN32 |
| #include <windef.h> |
| #include <winbase.h> |
| #endif |
| |
| #include "prclist.h" |
| #include "prbit.h" |
| |
| #include "prtypes.h" |
| #include "prenv.h" |
| #include "prgc.h" |
| #include "prthread.h" |
| #include "prlog.h" |
| #include "prlong.h" |
| #include "prinrval.h" |
| #include "prprf.h" |
| #include "gcint.h" |
| |
| #include "private/pprthred.h" |
| |
| typedef void (*PRFileDumper)(FILE *out, PRBool detailed); |
| |
| PR_EXTERN(void) |
| PR_DumpToFile(char* filename, char* msg, PRFileDumper dump, PRBool detailed); |
| |
| /* |
| ** Mark&sweep garbage collector. Supports objects that require |
| ** finalization, objects that can have a single weak link, and special |
| ** objects that require care during sweeping. |
| */ |
| |
| PRLogModuleInfo *_pr_msgc_lm; |
| PRLogModuleInfo* GC; |
| |
| static PRInt32 _pr_pageShift; |
| static PRInt32 _pr_pageSize; |
| |
| #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 400L |
| #define MAX_SEGMENT_SIZE (2L * 256L * 1024L) |
| #define SEGMENT_SIZE (1L * 256L * 1024L) |
| #define MAX_ALLOC_SIZE (4L * 1024L * 1024L) |
| #endif |
| |
| /* |
| * The highest value that can fit into a signed integer. This |
| * is used to prevent overflow of allocation size in alloc routines. |
| */ |
| |
| #define MAX_INT ((1UL << (PR_BITS_PER_INT - 1)) - 1) |
| |
| /* |
| * On 32-bit machines, only 22 bits are used in the cibx integer to |
| * store size since 8 bits of the integer are used to store type, and |
| * of the remainder, 2 are user defined. Max allocation size = 2^22 -1 |
| */ |
| |
| #define MAX_ALLOC ( (1L << (PR_BYTES_PER_WORD_LOG2 + WORDS_BITS )) -1) |
| |
| /* The minimum percentage of free heap space after a collection. If |
| the amount of free space doesn't meet this criteria then we will |
| attempt to grow the heap */ |
| #define MIN_FREE_THRESHOLD_AFTER_GC 20L |
| |
| static PRInt32 segmentSize = SEGMENT_SIZE; |
| |
| static PRInt32 collectorCleanupNeeded; |
| |
| #ifdef GCMETER |
| PRUint32 _pr_gcMeter; |
| |
| #define _GC_METER_STATS 0x01L |
| #define _GC_METER_GROWTH 0x02L |
| #define _GC_METER_FREE_LIST 0x04L |
| #endif |
| |
| /************************************************************************/ |
| |
| #define LINEAR_BIN_EXPONENT 5 |
| #define NUM_LINEAR_BINS ((PRUint32)1 << LINEAR_BIN_EXPONENT) |
| #define FIRST_LOG_BIN (NUM_LINEAR_BINS - LINEAR_BIN_EXPONENT) |
| |
| /* Each free list bin holds a chunk of memory sized from |
| 2^n to (2^(n+1))-1 inclusive. */ |
| #define NUM_BINS (FIRST_LOG_BIN + 32) |
| |
| /* |
| * Find the bin number for a given size (in bytes). This does not round up as |
| * values from 2^n to (2^(n+1))-1 share the same bin. |
| */ |
| #define InlineBinNumber(_bin,_bytes) \ |
| { \ |
| PRUint32 _t, _n = (PRUint32) _bytes / 4; \ |
| if (_n < NUM_LINEAR_BINS) { \ |
| _bin = _n; \ |
| } else { \ |
| _bin = FIRST_LOG_BIN; \ |
| if ((_t = (_n >> 16)) != 0) { _bin += 16; _n = _t; } \ |
| if ((_t = (_n >> 8)) != 0) { _bin += 8; _n = _t; } \ |
| if ((_t = (_n >> 4)) != 0) { _bin += 4; _n = _t; } \ |
| if ((_t = (_n >> 2)) != 0) { _bin += 2; _n = _t; } \ |
| if ((_n >> 1) != 0) _bin++; \ |
| } \ |
| } |
| |
| #define BIG_ALLOC 16384L |
| |
| #define MIN_FREE_CHUNK_BYTES ((PRInt32)sizeof(GCFreeChunk)) |
| |
| /* Note: fix code in PR_AllocMemory if you change the size of GCFreeChunk |
| so that it zeros the right number of words */ |
| typedef struct GCFreeChunk { |
| struct GCFreeChunk *next; |
| struct GCSeg *segment; |
| PRInt32 chunkSize; |
| } GCFreeChunk; |
| |
| typedef struct GCSegInfo { |
| struct GCSegInfo *next; |
| char *base; |
| char *limit; |
| PRWord *hbits; |
| int fromMalloc; |
| } GCSegInfo; |
| |
| typedef struct GCSeg { |
| char *base; |
| char *limit; |
| PRWord *hbits; |
| GCSegInfo *info; |
| } GCSeg; |
| |
| #ifdef GCMETER |
| typedef struct GCMeter { |
| PRInt32 allocBytes; |
| PRInt32 wastedBytes; |
| PRInt32 numFreeChunks; |
| PRInt32 skippedFreeChunks; |
| } 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 GCSeg* lastInHeap; |
| static int nsegs; |
| |
| static GCFreeChunk *bins[NUM_BINS]; |
| static PRInt32 minBin; |
| static PRInt32 maxBin; |
| |
| /* |
| ** Scan Q used to avoid deep recursion when scanning live objects for |
| ** heap pointers |
| */ |
| typedef struct GCScanQStr { |
| PRWord *q[MAX_SCAN_Q]; |
| int queued; |
| } GCScanQ; |
| |
| static GCScanQ *pScanQ; |
| |
| #ifdef GCMETER |
| PRInt32 _pr_maxScanDepth; |
| PRInt32 _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 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 20L |
| #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 |
| |
| /* Two bits per object header are reserved for the user of the memory |
| system to store information into. */ |
| #define GC_USER_BITS_SHIFT 22L |
| #define GC_USER_BITS 0x00c00000L |
| |
| #define MAKE_HEADER(_cbix,_words) \ |
| ((PRWord) (((unsigned long)(_cbix) << TYPEIX_SHIFT) \ |
| | ((unsigned long)(_words) << WORDS_SHIFT))) |
| |
| #define GET_TYPEIX(_h) \ |
| (((PRUword)(_h) >> TYPEIX_SHIFT) & 0xff) |
| |
| #define MARK(_sp,_p) \ |
| (((PRWord *)(_p))[0] |= MARK_BIT) |
| #define IS_MARKED(_sp,_p) \ |
| (((PRWord *)(_p))[0] & MARK_BIT) |
| #define OBJ_BYTES(_h) \ |
| (((PRInt32) (_h) & 0x003ffffcL) << (PR_BYTES_PER_WORD_LOG2-2L)) |
| |
| #define GC_GET_USER_BITS(_h) (((_h) & GC_USER_BITS) >> GC_USER_BITS_SHIFT) |
| |
| /************************************************************************/ |
| |
| /* |
| ** 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? |
| */ |
| #define SET_HBIT(_sp,_ph) \ |
| SET_BIT((_sp)->hbits, (((PRWord*)(_ph)) - ((PRWord*) (_sp)->base))) |
| |
| #define CLEAR_HBIT(_sp,_ph) \ |
| CLEAR_BIT((_sp)->hbits, (((PRWord*)(_ph)) - ((PRWord*) (_sp)->base))) |
| |
| #define IS_HBIT(_sp,_ph) \ |
| TEST_BIT((_sp)->hbits, (((PRWord*)(_ph)) - ((PRWord*) (_sp)->base))) |
| |
| /* |
| ** 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. |
| */ |
| static PRWord *FindObject(GCSeg *sp, PRWord *p) |
| { |
| PRWord *base; |
| |
| /* Align p to it's proper boundary before we start fiddling with it */ |
| p = (PRWord*) ((PRWord)p & ~(PR_BYTES_PER_WORD-1L)); |
| |
| base = (PRWord *) sp->base; |
| 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(); |
| return NULL; |
| } |
| |
| /************************************************************************/ |
| #if !defined(XP_PC) || defined(XP_OS2) |
| #define OutputDebugString(msg) |
| #endif |
| |
| #define IN_SEGMENT(_sp, _p) \ |
| ((((char *)(_p)) >= (_sp)->base) && \ |
| (((char *)(_p)) < (_sp)->limit)) |
| |
| static GCSeg *InHeap(void *p) |
| { |
| GCSeg *sp, *esp; |
| |
| if (lastInHeap && IN_SEGMENT(lastInHeap, p)) { |
| return lastInHeap; |
| } |
| |
| sp = segs; |
| esp = segs + nsegs; |
| for (; sp < esp; sp++) { |
| if (IN_SEGMENT(sp, p)) { |
| lastInHeap = sp; |
| return sp; |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| ** Grow the heap by allocating another segment. Fudge the requestedSize |
| ** value to try to pre-account for the HBITS. |
| */ |
| static GCSeg* DoGrowHeap(PRInt32 requestedSize, PRBool exactly) |
| { |
| GCSeg *sp; |
| GCSegInfo *segInfo; |
| GCFreeChunk *cp; |
| char *base; |
| PRWord *hbits; |
| PRInt32 nhbytes, nhbits; |
| PRUint32 allocSize; |
| |
| if (nsegs == MAX_SEGS) { |
| /* No room for more segments */ |
| return 0; |
| } |
| |
| segInfo = (GCSegInfo*) PR_MALLOC(sizeof(GCSegInfo)); |
| #ifdef DEBUG |
| { |
| char str[256]; |
| sprintf(str, "[1] Allocated %ld bytes at %p\n", |
| (long) sizeof(GCSegInfo), segInfo); |
| OutputDebugString(str); |
| } |
| #endif |
| if (!segInfo) { |
| return 0; |
| } |
| |
| /* Get more memory from the OS */ |
| if (exactly) { |
| allocSize = requestedSize; |
| base = (char *) PR_MALLOC(requestedSize); |
| } else { |
| allocSize = requestedSize; |
| allocSize = (allocSize + _pr_pageSize - 1L) >> _pr_pageShift; |
| allocSize <<= _pr_pageShift; |
| base = (char*)_MD_GrowGCHeap(&allocSize); |
| } |
| if (!base) { |
| PR_DELETE(segInfo); |
| return 0; |
| } |
| |
| nhbits = (PRInt32)( |
| (allocSize + PR_BYTES_PER_WORD - 1L) >> PR_BYTES_PER_WORD_LOG2); |
| nhbytes = ((nhbits + PR_BITS_PER_WORD - 1L) >> PR_BITS_PER_WORD_LOG2) |
| * sizeof(PRWord); |
| |
| /* Get bitmap memory from malloc heap */ |
| hbits = (PRWord *) PR_CALLOC((PRUint32)nhbytes); |
| if (!hbits) { |
| /* Loser! */ |
| PR_DELETE(segInfo); |
| if (exactly) { |
| PR_DELETE(base); |
| } else { |
| /* XXX do something about this */ |
| /* _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; |
| segInfo->fromMalloc = exactly; |
| memset(base, 0, allocSize); |
| |
| #ifdef GCMETER |
| if (_pr_gcMeter & _GC_METER_GROWTH) { |
| fprintf(stderr, "[GC: new segment base=%p size=%ld]\n", |
| sp->base, (long) allocSize); |
| } |
| #endif |
| |
| _pr_gcData.allocMemory += allocSize; |
| _pr_gcData.freeMemory += allocSize; |
| |
| if (!exactly) { |
| PRInt32 bin; |
| |
| /* Put free memory into a freelist bin */ |
| cp = (GCFreeChunk *) base; |
| cp->segment = sp; |
| cp->chunkSize = allocSize; |
| InlineBinNumber(bin, allocSize) |
| cp->next = bins[bin]; |
| bins[bin] = cp; |
| if (bin < minBin) minBin = bin; |
| if (bin > maxBin) maxBin = bin; |
| } 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*) sp->base; |
| _pr_gcData.highSeg = (PRWord*) sp->limit; |
| } else { |
| if ((PRWord*)sp->base < _pr_gcData.lowSeg) { |
| _pr_gcData.lowSeg = (PRWord*) sp->base; |
| } |
| if ((PRWord*)sp->limit > _pr_gcData.highSeg) { |
| _pr_gcData.highSeg = (PRWord*) 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(_pr_msgc_lm, PR_LOG_WARNING, ("grow heap: total gc memory now %d", |
| _pr_gcData.allocMemory)); |
| |
| return sp; |
| } |
| |
| #ifdef USE_EXTEND_HEAP |
| static PRBool ExtendHeap(PRInt32 requestedSize) { |
| GCSeg* sp; |
| PRUint32 allocSize; |
| PRInt32 oldSize, newSize; |
| PRInt32 newHBits, newHBytes; |
| PRInt32 oldHBits, oldHBytes; |
| PRWord* hbits; |
| GCFreeChunk* cp; |
| PRInt32 bin; |
| |
| /* Can't extend nothing */ |
| if (nsegs == 0) return PR_FALSE; |
| |
| /* Round up requested size to the size of a page */ |
| allocSize = (PRUint32) requestedSize; |
| allocSize = (allocSize + _pr_pageSize - 1L) >> _pr_pageShift; |
| allocSize <<= _pr_pageShift; |
| |
| /* Malloc some memory for the new hbits array */ |
| sp = segs; |
| oldSize = sp->limit - sp->base; |
| newSize = oldSize + allocSize; |
| newHBits = (newSize + PR_BYTES_PER_WORD - 1L) >> PR_BYTES_PER_WORD_LOG2; |
| newHBytes = ((newHBits + PR_BITS_PER_WORD - 1L) >> PR_BITS_PER_WORD_LOG2) |
| * sizeof(PRWord); |
| hbits = (PRWord*) PR_MALLOC(newHBytes); |
| if (0 == hbits) return PR_FALSE; |
| |
| /* Attempt to extend the last segment by the desired amount */ |
| if (_MD_ExtendGCHeap(sp->base, oldSize, newSize)) { |
| oldHBits = (oldSize + PR_BYTES_PER_WORD - 1L) >> PR_BYTES_PER_WORD_LOG2; |
| oldHBytes = ((oldHBits + PR_BITS_PER_WORD - 1L) >> PR_BITS_PER_WORD_LOG2) |
| * sizeof(PRWord); |
| |
| /* Copy hbits from old memory into new memory */ |
| memset(hbits, 0, newHBytes); |
| memcpy(hbits, sp->hbits, oldHBytes); |
| PR_DELETE(sp->hbits); |
| memset(sp->base + oldSize, 0, allocSize); |
| |
| /* Adjust segment state */ |
| sp->limit += allocSize; |
| sp->hbits = hbits; |
| sp->info->limit = sp->limit; |
| sp->info->hbits = hbits; |
| |
| /* Put free memory into a freelist bin */ |
| cp = (GCFreeChunk *) (sp->base + oldSize); |
| cp->segment = sp; |
| cp->chunkSize = allocSize; |
| InlineBinNumber(bin, allocSize) |
| cp->next = bins[bin]; |
| bins[bin] = cp; |
| if (bin < minBin) minBin = bin; |
| if (bin > maxBin) maxBin = bin; |
| |
| /* Prevent a pointer that points to the free memory from showing |
| up on the call stack later on */ |
| memset(&cp, 0, sizeof(cp)); |
| |
| /* Update heap brackets and counters */ |
| if ((PRWord*)sp->limit > _pr_gcData.highSeg) { |
| _pr_gcData.highSeg = (PRWord*) sp->limit; |
| } |
| _pr_gcData.allocMemory += allocSize; |
| _pr_gcData.freeMemory += allocSize; |
| |
| return PR_TRUE; |
| } |
| PR_DELETE(hbits); |
| return PR_FALSE; |
| } |
| #endif /* USE_EXTEND_HEAP */ |
| |
| static GCSeg *GrowHeapExactly(PRInt32 requestedSize) |
| { |
| GCSeg *sp = DoGrowHeap(requestedSize, PR_TRUE); |
| return sp; |
| } |
| |
| static PRBool GrowHeap(PRInt32 requestedSize) |
| { |
| void *p; |
| #ifdef USE_EXTEND_HEAP |
| if (ExtendHeap(requestedSize)) { |
| return PR_TRUE; |
| } |
| #endif |
| p = DoGrowHeap(requestedSize, PR_FALSE); |
| return (p != NULL ? PR_TRUE : PR_FALSE); |
| } |
| |
| /* |
| ** 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=%ld]\n", |
| sp->base, (long) (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; |
| collectorCleanupNeeded = 1; |
| _pr_gcData.allocMemory -= sp->limit - sp->base; |
| if (sp == lastInHeap) lastInHeap = 0; |
| |
| /* 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*) segs[0].base; |
| _pr_gcData.highSeg = (PRWord*) segs[0].limit; |
| for (sp = segs; sp < &segs[nsegs]; sp++) { |
| if ((PRWord*)sp->base < _pr_gcData.lowSeg) { |
| _pr_gcData.lowSeg = (PRWord*) sp->base; |
| } |
| if ((PRWord*)sp->limit > _pr_gcData.highSeg) { |
| _pr_gcData.highSeg = (PRWord*) sp->limit; |
| } |
| } |
| } |
| |
| static void FreeSegments(void) |
| { |
| GCSegInfo *si; |
| |
| while (0 != freeSegs) { |
| LOCK_GC(); |
| si = freeSegs; |
| if (si) { |
| freeSegs = si->next; |
| } |
| UNLOCK_GC(); |
| |
| if (!si) { |
| break; |
| } |
| PR_DELETE(si->base); |
| PR_DELETE(si->hbits); |
| PR_DELETE(si); |
| } |
| } |
| |
| /************************************************************************/ |
| |
| void ScanScanQ(GCScanQ *iscan) |
| { |
| PRWord *p; |
| PRWord **pp; |
| PRWord **epp; |
| GCScanQ nextQ, *scan, *next, *temp; |
| CollectorType *ct; |
| |
| 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_gcData.livePointer |
| * 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++; |
| ct = &_pr_collectorTypes[GET_TYPEIX(p[0])]; |
| PR_ASSERT(0 != ct->gctype.scan); |
| /* Scan object ... */ |
| (*ct->gctype.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 ProcessRootBlock(void **base, PRInt32 count) |
| { |
| GCSeg *sp; |
| PRWord *p0, *p, h, tix, *low, *high, *segBase; |
| CollectorType *ct; |
| #ifdef DEBUG |
| void **base0 = base; |
| #endif |
| |
| low = _pr_gcData.lowSeg; |
| high = _pr_gcData.highSeg; |
| while (--count >= 0) { |
| p0 = (PRWord*) *base++; |
| if (p0 < low) continue; /* below gc heap */ |
| if (p0 >= high) continue; /* above gc heap */ |
| /* NOTE: inline expansion of InHeap */ |
| /* Find segment */ |
| sp = lastInHeap; |
| if (!sp || !IN_SEGMENT(sp,p0)) { |
| GCSeg *esp; |
| sp = segs; |
| esp = segs + nsegs; |
| for (; sp < esp; sp++) { |
| if (IN_SEGMENT(sp, p0)) { |
| lastInHeap = 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*) ((PRWord)p0 & ~(PR_BYTES_PER_WORD-1L)); |
| segBase = (PRWord *) sp->base; |
| do { |
| if (IS_HBIT(sp, 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 scan function to be placed on the scanQ. |
| */ |
| tix = (PRWord)GET_TYPEIX(h); |
| ct = &_pr_collectorTypes[tix]; |
| if (0 == ct->gctype.scan) { |
| 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); |
| } |
| } |
| } |
| } |
| |
| static void PR_CALLBACK ProcessRootPointer(void *ptr) |
| { |
| PRWord *p0, *p, h, tix, *segBase; |
| GCSeg* sp; |
| CollectorType *ct; |
| |
| p0 = (PRWord*) ptr; |
| |
| if (p0 < _pr_gcData.lowSeg) return; /* below gc heap */ |
| if (p0 >= _pr_gcData.highSeg) return; /* above gc heap */ |
| |
| /* NOTE: inline expansion of InHeap */ |
| /* Find segment */ |
| sp = lastInHeap; |
| if (!sp || !IN_SEGMENT(sp,p0)) { |
| GCSeg *esp; |
| sp = segs; |
| esp = segs + nsegs; |
| for (; sp < esp; sp++) { |
| if (IN_SEGMENT(sp, p0)) { |
| lastInHeap = sp; |
| goto find_object; |
| } |
| } |
| return; |
| } |
| |
| find_object: |
| /* NOTE: Inline expansion of FindObject */ |
| /* Align p to it's proper boundary before we start fiddling with it */ |
| p = (PRWord*) ((PRWord)p0 & ~(BYTES_PER_WORD-1L)); |
| segBase = (PRWord *) sp->base; |
| do { |
| if (IS_HBIT(sp, 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)", p, OBJ_BYTES(h))); |
| #endif |
| |
| /* Mark the root we just found */ |
| p[0] = h | MARK_BIT; |
| |
| /* |
| * See if object we just found needs scanning. It must |
| * have a scan function to be placed on the scanQ. |
| */ |
| tix = (PRWord)GET_TYPEIX(h); |
| ct = &_pr_collectorTypes[tix]; |
| if (0 == ct->gctype.scan) { |
| return; |
| } |
| |
| /* |
| ** 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); |
| } |
| } |
| } |
| |
| /************************************************************************/ |
| |
| /* |
| ** 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 *cp; |
| GCFreeChunk *next; |
| GCSeg *sp; |
| PRWord *p; |
| PRInt32 chunkSize; |
| PRInt32 bin; |
| |
| /* |
| ** Run over the freelist and make all of the free chunks look like |
| ** object debris. |
| */ |
| for (bin = 0; bin <= NUM_BINS-1; bin++) { |
| cp = bins[bin]; |
| while (cp) { |
| next = cp->next; |
| sp = cp->segment; |
| chunkSize = cp->chunkSize >> BYTES_PER_WORD_LOG2; |
| p = (PRWord*) cp; |
| PR_ASSERT(chunkSize != 0); |
| p[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, chunkSize); |
| SET_HBIT(sp, p); |
| cp = next; |
| } |
| bins[bin] = 0; |
| } |
| minBin = NUM_BINS - 1; |
| maxBin = 0; |
| } |
| |
| typedef struct GCBlockEnd { |
| PRInt32 check; |
| #ifdef GC_CHECK |
| PRInt32 requestedBytes; |
| #endif |
| #ifdef GC_STATS |
| PRInt32 bin; |
| PRInt64 allocTime; |
| #endif |
| #ifdef GC_TRACEROOTS |
| PRInt32 traceGeneration; |
| #endif |
| } GCBlockEnd; |
| |
| #define PR_BLOCK_END 0xDEADBEEF |
| |
| /************************************************************************/ |
| |
| #ifdef GC_STATS |
| |
| typedef struct GCStat { |
| PRInt32 nallocs; |
| double allocTime; |
| double allocTimeVariance; |
| PRInt32 nfrees; |
| double lifetime; |
| double lifetimeVariance; |
| } GCStat; |
| |
| #define GCSTAT_BINS NUM_BINS |
| |
| GCStat gcstats[GCSTAT_BINS]; |
| |
| #define GCLTFREQ_BINS NUM_BINS |
| |
| PRInt32 gcltfreq[GCSTAT_BINS][GCLTFREQ_BINS]; |
| |
| #include <math.h> |
| |
| static char* |
| pr_GetSizeString(PRUint32 size) |
| { |
| char* sizeStr; |
| if (size < 1024) |
| sizeStr = PR_smprintf("<= %ld", size); |
| else if (size < 1024 * 1024) |
| sizeStr = PR_smprintf("<= %ldk", size / 1024); |
| else |
| sizeStr = PR_smprintf("<= %ldM", size / (1024 * 1024)); |
| return sizeStr; |
| } |
| |
| static void |
| pr_FreeSizeString(char *sizestr) |
| { |
| PR_smprintf_free(sizestr); |
| } |
| |
| |
| static void |
| pr_PrintGCAllocStats(FILE* out) |
| { |
| PRInt32 i, j; |
| _PR_DebugPrint(out, "\n--Allocation-Stats-----------------------------------------------------------"); |
| _PR_DebugPrint(out, "\n--Obj-Size----Count-----Avg-Alloc-Time-----------Avg-Lifetime---------%%Freed-\n"); |
| for (i = 0; i < GCSTAT_BINS; i++) { |
| GCStat stat = gcstats[i]; |
| double allocTimeMean = 0.0, allocTimeVariance = 0.0, lifetimeMean = 0.0, lifetimeVariance = 0.0; |
| PRUint32 maxSize = (1 << i); |
| char* sizeStr; |
| if (stat.nallocs != 0.0) { |
| allocTimeMean = stat.allocTime / stat.nallocs; |
| allocTimeVariance = fabs(stat.allocTimeVariance / stat.nallocs - allocTimeMean * allocTimeMean); |
| } |
| if (stat.nfrees != 0.0) { |
| lifetimeMean = stat.lifetime / stat.nfrees; |
| lifetimeVariance = fabs(stat.lifetimeVariance / stat.nfrees - lifetimeMean * lifetimeMean); |
| } |
| sizeStr = pr_GetSizeString(maxSize); |
| _PR_DebugPrint(out, "%10s %8lu %10.3f +- %10.3f %10.3f +- %10.3f (%2ld%%)\n", |
| sizeStr, stat.nallocs, |
| allocTimeMean, sqrt(allocTimeVariance), |
| lifetimeMean, sqrt(lifetimeVariance), |
| (stat.nallocs ? (stat.nfrees * 100 / stat.nallocs) : 0)); |
| pr_FreeSizeString(sizeStr); |
| } |
| _PR_DebugPrint(out, "--Lifetime-Frequency-Counts----------------------------------------------------\n"); |
| _PR_DebugPrint(out, "size\\cnt"); |
| for (j = 0; j < GCLTFREQ_BINS; j++) { |
| _PR_DebugPrint(out, "\t%lu", j); |
| } |
| _PR_DebugPrint(out, "\n"); |
| for (i = 0; i < GCSTAT_BINS; i++) { |
| PRInt32* freqs = gcltfreq[i]; |
| _PR_DebugPrint(out, "%lu", (1 << i)); |
| for (j = 0; j < GCLTFREQ_BINS; j++) { |
| _PR_DebugPrint(out, "\t%lu", freqs[j]); |
| } |
| _PR_DebugPrint(out, "\n"); |
| } |
| _PR_DebugPrint(out, "-------------------------------------------------------------------------------\n"); |
| } |
| |
| PR_PUBLIC_API(void) |
| PR_PrintGCAllocStats(void) |
| { |
| pr_PrintGCAllocStats(stderr); |
| } |
| |
| #endif /* GC_STATS */ |
| |
| /************************************************************************/ |
| |
| /* |
| ** 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 h, tix; |
| PRWord *p; |
| PRWord *np; |
| PRWord *limit; |
| GCFreeChunk *cp; |
| PRInt32 bytes, chunkSize, segmentSize, totalFree; |
| CollectorType *ct; |
| PRInt32 bin; |
| |
| /* |
| ** 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 *) sp->base; |
| limit = (PRWord *) sp->limit; |
| PR_ASSERT(segmentSize > 0); |
| while (p < limit) { |
| chunkSize = 0; |
| cp = (GCFreeChunk *) 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 *) ((char *)p + bytes); |
| tix = (PRWord)GET_TYPEIX(h); |
| if ((h & MARK_BIT) && (tix != FREE_MEMORY_TYPEIX)) { |
| #ifdef DEBUG |
| if (tix != FREE_MEMORY_TYPEIX) { |
| PR_ASSERT(_pr_collectorTypes[tix].flags != 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 */ |
| #ifdef GC_STATS |
| { |
| PRInt32 userSize = bytes - sizeof(GCBlockEnd); |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + userSize); |
| if (userSize >= 0 && end->check == PR_BLOCK_END) { |
| PRInt64 now = PR_Now(); |
| double nowd, delta; |
| PRInt32 freq; |
| LL_L2D(nowd, now); |
| delta = nowd - end->allocTime; |
| gcstats[end->bin].nfrees++; |
| gcstats[end->bin].lifetime += delta; |
| gcstats[end->bin].lifetimeVariance += delta * delta; |
| |
| InlineBinNumber(freq, delta); |
| gcltfreq[end->bin][freq]++; |
| |
| end->check = 0; |
| } |
| } |
| #endif |
| CLEAR_HBIT(sp, p); |
| ct = &_pr_collectorTypes[tix]; |
| if (0 != ct->gctype.free) { |
| (*ct->gctype.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%p to 0x%p (%d)", |
| cp, (char*)cp + chunkSize - 1, chunkSize)); |
| if (chunkSize < MIN_FREE_CHUNK_BYTES) { |
| /* Lost a tiny fragment until (maybe) next time */ |
| METER(meter.wastedBytes += chunkSize); |
| p = (PRWord *) 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 (chunkSize == segmentSize) { |
| /* Free up the segment right now */ |
| if (sp->info->fromMalloc) { |
| ShrinkGCHeap(sp); |
| return PR_TRUE; |
| } |
| } |
| |
| /* Put free chunk into the appropriate bin */ |
| cp->segment = sp; |
| cp->chunkSize = chunkSize; |
| InlineBinNumber(bin, chunkSize) |
| cp->next = bins[bin]; |
| bins[bin] = cp; |
| if (bin < minBin) minBin = bin; |
| if (bin > maxBin) maxBin = bin; |
| |
| /* Zero swept memory now */ |
| memset(cp+1, 0, chunkSize - sizeof(*cp)); |
| METER(meter.numFreeChunks++); |
| 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; |
| } |
| |
| /************************************************************************/ |
| |
| /* This is a list of all the objects that are finalizable. This is not |
| the list of objects that are awaiting finalization because they |
| have been collected. */ |
| PRCList _pr_finalizeableObjects; |
| |
| /* This is the list of objects that are awaiting finalization because |
| they have been collected. */ |
| PRCList _pr_finalQueue; |
| |
| /* Each object that requires finalization has one of these objects |
| allocated as well. The GCFinal objects are put on the |
| _pr_finalizeableObjects list until the object is collected at which |
| point the GCFinal object is moved to the _pr_finalQueue */ |
| typedef struct GCFinalStr { |
| PRCList links; |
| PRWord *object; |
| } GCFinal; |
| |
| /* Find pointer to GCFinal struct from the list linkaged embedded in it */ |
| #define FinalPtr(_qp) \ |
| ((GCFinal*) ((char*) (_qp) - offsetof(GCFinal,links))) |
| |
| static GCFinal *AllocFinalNode(void) |
| { |
| return PR_NEWZAP(GCFinal); |
| } |
| |
| static void FreeFinalNode(GCFinal *node) |
| { |
| PR_DELETE(node); |
| } |
| |
| /* |
| ** Prepare for finalization. At this point in the GC cycle we have |
| ** identified all of the live objects. For each object on the |
| ** _pr_finalizeableObjects list see if the object is alive or dead. If |
| ** it's dead, resurrect it and move it from the _pr_finalizeableObjects |
| ** list to the _pr_finalQueue (object's only get finalized once). |
| ** |
| ** Once _pr_finalizeableObjects 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 |
| ** _pr_finalQueue. |
| */ |
| static void PrepareFinalize(void) |
| { |
| PRCList *qp; |
| GCFinal *fp; |
| PRWord h; |
| PRWord *p; |
| void (PR_CALLBACK *livePointer)(void *ptr); |
| #ifdef DEBUG |
| CollectorType *ct; |
| #endif |
| |
| /* This must be done under the same lock that the finalizer uses */ |
| PR_ASSERT( GC_IS_LOCKED() ); |
| |
| /* cache this ptr */ |
| livePointer = _pr_gcData.livePointer; |
| |
| /* |
| * Pass #1: Identify objects that are to be finalized, set their |
| * FINAL_BIT. |
| */ |
| qp = _pr_finalizeableObjects.next; |
| while (qp != &_pr_finalizeableObjects) { |
| fp = FinalPtr(qp); |
| qp = qp->next; |
| h = fp->object[0]; /* Grab header word */ |
| if (h & MARK_BIT) { |
| /* Object is already alive */ |
| continue; |
| } |
| |
| #ifdef DEBUG |
| ct = &_pr_collectorTypes[GET_TYPEIX(h)]; |
| PR_ASSERT((0 != ct->flags) && (0 != ct->gctype.finalize)); |
| #endif |
| 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_finalizeableObjects.next; |
| while (qp != &_pr_finalizeableObjects) { |
| 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]; |
| (*livePointer)(p); |
| 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. |
| */ |
| extern void PR_CALLBACK _PR_ScanFinalQueue(void *notused) |
| { |
| PRCList *qp; |
| GCFinal *fp; |
| PRWord *p; |
| void ( PR_CALLBACK *livePointer)(void *ptr); |
| |
| livePointer = _pr_gcData.livePointer; |
| 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]; |
| (*livePointer)(p); |
| qp = qp->next; |
| } |
| } |
| |
| void PR_CALLBACK FinalizerLoop(void* unused) |
| { |
| GCFinal *fp; |
| PRWord *p; |
| PRWord h, tix; |
| CollectorType *ct; |
| |
| LOCK_GC(); |
| for (;;) { |
| p = 0; h = 0; /* don't let the gc find these pointers */ |
| while (PR_CLIST_IS_EMPTY(&_pr_finalQueue)) |
| PR_Wait(_pr_gcData.lock, PR_INTERVAL_NO_TIMEOUT); |
| |
| _GCTRACE(GC_FINAL, ("begin finalization")); |
| while (_pr_finalQueue.next != &_pr_finalQueue) { |
| fp = FinalPtr(_pr_finalQueue.next); |
| PR_REMOVE_LINK(&fp->links); |
| p = fp->object; |
| |
| h = p[0]; /* Grab header word */ |
| tix = (PRWord)GET_TYPEIX(h); |
| ct = &_pr_collectorTypes[tix]; |
| _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(); |
| FreeFinalNode(fp); |
| PR_ASSERT(ct->gctype.finalize != 0); |
| (*ct->gctype.finalize)(p + 1); |
| LOCK_GC(); |
| } |
| _GCTRACE(GC_FINAL, ("end finalization")); |
| PR_Notify(_pr_gcData.lock); |
| } |
| } |
| |
| static void NotifyFinalizer(void) |
| { |
| if (!PR_CLIST_IS_EMPTY(&_pr_finalQueue)) { |
| PR_ASSERT( GC_IS_LOCKED() ); |
| PR_Notify(_pr_gcData.lock); |
| } |
| } |
| |
| void _PR_CreateFinalizer(PRThreadScope scope) |
| { |
| if (!_pr_gcData.finalizer) { |
| _pr_gcData.finalizer = PR_CreateThreadGCAble(PR_SYSTEM_THREAD, |
| FinalizerLoop, 0, |
| PR_PRIORITY_LOW, scope, |
| PR_UNJOINABLE_THREAD, 0); |
| |
| if (_pr_gcData.finalizer == NULL) |
| /* We are doomed if we can't start the finalizer */ |
| PR_Abort(); |
| |
| } |
| } |
| |
| void pr_FinalizeOnExit(void) |
| { |
| #ifdef DEBUG_warren |
| OutputDebugString("### Doing finalize-on-exit pass\n"); |
| #endif |
| PR_ForceFinalize(); |
| #ifdef DEBUG_warren |
| OutputDebugString("### Finalize-on-exit complete. Dumping object left to memory.out\n"); |
| PR_DumpMemorySummary(); |
| PR_DumpMemory(PR_TRUE); |
| #endif |
| } |
| |
| PR_IMPLEMENT(void) PR_ForceFinalize() |
| { |
| LOCK_GC(); |
| NotifyFinalizer(); |
| while (!PR_CLIST_IS_EMPTY(&_pr_finalQueue)) { |
| PR_ASSERT( GC_IS_LOCKED() ); |
| (void) PR_Wait(_pr_gcData.lock, PR_INTERVAL_NO_TIMEOUT); |
| } |
| UNLOCK_GC(); |
| |
| /* XXX I don't know how to make it wait (yet) */ |
| } |
| |
| /************************************************************************/ |
| |
| typedef struct GCWeakStr { |
| PRCList links; |
| PRWord *object; |
| } GCWeak; |
| |
| /* |
| ** Find pointer to GCWeak struct from the list linkaged embedded in it |
| */ |
| #define WeakPtr(_qp) \ |
| ((GCWeak*) ((char*) (_qp) - offsetof(GCWeak,links))) |
| |
| PRCList _pr_weakLinks = PR_INIT_STATIC_CLIST(&_pr_weakLinks); |
| PRCList _pr_freeWeakLinks = PR_INIT_STATIC_CLIST(&_pr_freeWeakLinks); |
| |
| #define WEAK_FREELIST_ISEMPTY() (_pr_freeWeakLinks.next == &_pr_freeWeakLinks) |
| |
| /* |
| * Keep objects referred to by weak free list alive until they can be |
| * freed |
| */ |
| static void PR_CALLBACK ScanWeakFreeList(void *notused) { |
| PRCList *qp = _pr_freeWeakLinks.next; |
| while (qp != &_pr_freeWeakLinks) { |
| GCWeak *wp = WeakPtr(qp); |
| qp = qp->next; |
| ProcessRootPointer(wp->object); |
| } |
| } |
| |
| /* |
| * Empty the list of weak objects. Note that we can't call malloc/free |
| * under the cover of the GC's lock (we might deadlock), so transfer the |
| * list of free objects to a local list under the cover of the lock, then |
| * release the lock and free up the memory. |
| */ |
| static void EmptyWeakFreeList(void) { |
| if (!WEAK_FREELIST_ISEMPTY()) { |
| PRCList *qp, freeLinks; |
| |
| PR_INIT_CLIST(&freeLinks); |
| |
| /* |
| * Transfer list of free weak links from the global list to a |
| * local list. |
| */ |
| LOCK_GC(); |
| qp = _pr_freeWeakLinks.next; |
| while (qp != &_pr_freeWeakLinks) { |
| GCWeak *wp = WeakPtr(qp); |
| qp = qp->next; |
| PR_REMOVE_LINK(&wp->links); |
| PR_APPEND_LINK(&wp->links, &freeLinks); |
| } |
| UNLOCK_GC(); |
| |
| /* Free up storage now */ |
| qp = freeLinks.next; |
| while (qp != &freeLinks) { |
| GCWeak *wp = WeakPtr(qp); |
| qp = qp->next; |
| PR_DELETE(wp); |
| } |
| } |
| } |
| |
| /* |
| * Allocate a new weak node in the weak objects list |
| */ |
| static GCWeak *AllocWeakNode(void) |
| { |
| EmptyWeakFreeList(); |
| return PR_NEWZAP(GCWeak); |
| } |
| |
| static void FreeWeakNode(GCWeak *node) |
| { |
| PR_DELETE(node); |
| } |
| |
| /* |
| * Check the weak links for validity. Note that the list of weak links is |
| * itself weak (otherwise we would keep the objects with weak links in |
| * them alive forever). As we scan the list check the weak link object |
| * itself and if it's not marked then remove it from the weak link list |
| */ |
| static void CheckWeakLinks(void) { |
| PRCList *qp; |
| GCWeak *wp; |
| PRWord *p, h, tix, **weakPtrAddress; |
| CollectorType *ct; |
| PRUint32 offset; |
| |
| qp = _pr_weakLinks.next; |
| while (qp != &_pr_weakLinks) { |
| wp = WeakPtr(qp); |
| qp = qp->next; |
| if ((p = wp->object) != 0) { |
| h = p[0]; /* Grab header word */ |
| if ((h & MARK_BIT) == 0) { |
| /* |
| * The object that has a weak link is no longer being |
| * referenced; remove it from the chain and let it get |
| * swept away by the GC. Transfer it to the list of |
| * free weak links for later freeing. |
| */ |
| PR_REMOVE_LINK(&wp->links); |
| PR_APPEND_LINK(&wp->links, &_pr_freeWeakLinks); |
| collectorCleanupNeeded = 1; |
| continue; |
| } |
| |
| /* Examine a live object that contains weak links */ |
| tix = GET_TYPEIX(h); |
| ct = &_pr_collectorTypes[tix]; |
| PR_ASSERT((ct->flags != 0) && (ct->gctype.getWeakLinkOffset != 0)); |
| if (0 == ct->gctype.getWeakLinkOffset) { |
| /* Heap is probably corrupted */ |
| continue; |
| } |
| |
| /* Get offset into the object of where the weak pointer is */ |
| offset = (*ct->gctype.getWeakLinkOffset)(p + 1); |
| |
| /* Check the weak pointer */ |
| weakPtrAddress = (PRWord**)((char*)(p + 1) + offset); |
| p = *weakPtrAddress; |
| if (p != 0) { |
| h = p[-1]; /* Grab header word for pointed to object */ |
| if (h & MARK_BIT) { |
| /* Object can't be dead */ |
| continue; |
| } |
| /* Break weak link to an object that is about to be swept */ |
| *weakPtrAddress = 0; |
| } |
| } |
| } |
| } |
| |
| /************************************************************************/ |
| |
| /* |
| ** Perform a complete garbage collection |
| */ |
| |
| extern GCLockHook *_pr_GCLockHook; |
| |
| static void dogc(void) |
| { |
| RootFinder *rf; |
| GCLockHook* lhook; |
| |
| GCScanQ scanQ; |
| GCSeg *sp, *esp; |
| PRInt64 start, end, diff; |
| |
| #if defined(GCMETER) || defined(GCTIMINGHOOK) |
| start = PR_Now(); |
| #endif |
| |
| /* |
| ** Stop all of the other threads. This also promises to capture the |
| ** register state of each and every thread |
| */ |
| |
| /* |
| ** Get all the locks that will be need during GC after SuspendAll. We |
| ** cannot make any locking/library calls after SuspendAll. |
| */ |
| if (_pr_GCLockHook) { |
| for (lhook = _pr_GCLockHook->next; lhook != _pr_GCLockHook; |
| lhook = lhook->next) { |
| (*lhook->func)(PR_GCBEGIN, lhook->arg); |
| } |
| } |
| |
| PR_SuspendAll(); |
| |
| #ifdef GCMETER |
| /* Reset meter info */ |
| if (_pr_gcMeter & _GC_METER_STATS) { |
| fprintf(stderr, |
| "[GCSTATS: busy:%ld skipped:%ld, alloced:%ld+wasted:%ld+free:%ld = total:%ld]\n", |
| (long) _pr_gcData.busyMemory, |
| (long) meter.skippedFreeChunks, |
| (long) meter.allocBytes, |
| (long) meter.wastedBytes, |
| (long) _pr_gcData.freeMemory, |
| (long) _pr_gcData.allocMemory); |
| } |
| memset(&meter, 0, sizeof(meter)); |
| #endif |
| |
| PR_LOG(_pr_msgc_lm, PR_LOG_ALWAYS, ("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(_pr_msgc_lm, PR_LOG_WARNING, |
| ("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; |
| } |
| }); |
| pScanQ = 0; |
| |
| /******************************************/ |
| /* 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. |
| */ |
| CheckWeakLinks(); |
| _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++; |
| } |
| |
| #if defined(GCMETER) || defined(GCTIMINGHOOK) |
| end = PR_Now(); |
| #endif |
| #ifdef GCMETER |
| LL_SUB(diff, end, start); |
| PR_LOG(GC, PR_LOG_ALWAYS, |
| ("done; busy=%d free=%d chunks=%d total=%d time=%lldms", |
| _pr_gcData.busyMemory, _pr_gcData.freeMemory, |
| meter.numFreeChunks, _pr_gcData.allocMemory, diff)); |
| if (_pr_gcMeter & _GC_METER_FREE_LIST) { |
| PRIntn bin; |
| fprintf(stderr, "Freelist bins:\n"); |
| for (bin = 0; bin < NUM_BINS; bin++) { |
| GCFreeChunk *cp = bins[bin]; |
| while (cp != NULL) { |
| fprintf(stderr, "%3d: %p %8ld\n", |
| bin, cp, (long) 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_ResumeAll(); |
| |
| if (_pr_GCLockHook) { |
| for (lhook = _pr_GCLockHook->prev; lhook != _pr_GCLockHook; |
| lhook = lhook->prev) { |
| (*lhook->func)(PR_GCEND, lhook->arg); |
| } |
| } |
| |
| /* Kick finalizer */ |
| NotifyFinalizer(); |
| #ifdef GCTIMINGHOOK |
| if (_pr_gcData.gcTimingHook) { |
| PRInt32 time; |
| LL_SUB(diff, end, start); |
| LL_L2I(time, diff); |
| _pr_gcData.gcTimingHook(time); |
| } |
| #endif |
| } |
| |
| PR_IMPLEMENT(void) PR_GC(void) |
| { |
| LOCK_GC(); |
| dogc(); |
| UNLOCK_GC(); |
| |
| EmptyWeakFreeList(); |
| } |
| |
| /******************************************************************************* |
| * Heap Walker |
| ******************************************************************************/ |
| |
| /* |
| ** This is yet another disgusting copy of the body of ProcessRootPointer |
| ** (the other being ProcessRootBlock), but we're not leveraging a single |
| ** function in their cases in interest of performance (avoiding the function |
| ** call). |
| */ |
| static PRInt32 PR_CALLBACK |
| pr_ConservativeWalkPointer(void* ptr, PRWalkFun walkRootPointer, void* data) |
| { |
| PRWord *p0, *p, *segBase; |
| GCSeg* sp; |
| |
| p0 = (PRWord*) ptr; |
| |
| if (p0 < _pr_gcData.lowSeg) return 0; /* below gc heap */ |
| if (p0 >= _pr_gcData.highSeg) return 0; /* above gc heap */ |
| |
| /* NOTE: inline expansion of InHeap */ |
| /* Find segment */ |
| sp = lastInHeap; |
| if (!sp || !IN_SEGMENT(sp,p0)) { |
| GCSeg *esp; |
| sp = segs; |
| esp = segs + nsegs; |
| for (; sp < esp; sp++) { |
| if (IN_SEGMENT(sp, p0)) { |
| lastInHeap = sp; |
| goto find_object; |
| } |
| } |
| return 0; |
| } |
| |
| find_object: |
| /* NOTE: Inline expansion of FindObject */ |
| /* Align p to it's proper boundary before we start fiddling with it */ |
| p = (PRWord*) ((PRWord)p0 & ~(BYTES_PER_WORD-1L)); |
| segBase = (PRWord *) sp->base; |
| do { |
| if (IS_HBIT(sp, 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 |
| return 0; |
| |
| winner: |
| return walkRootPointer(p, data); |
| } |
| |
| static PRInt32 PR_CALLBACK |
| pr_ConservativeWalkBlock(void **base, PRInt32 count, |
| PRWalkFun walkRootPointer, void* data) |
| { |
| PRWord *p0; |
| while (--count >= 0) { |
| PRInt32 status; |
| p0 = (PRWord*) *base++; |
| status = pr_ConservativeWalkPointer(p0, walkRootPointer, data); |
| if (status) return status; |
| } |
| return 0; |
| } |
| |
| /******************************************************************************/ |
| |
| typedef void (*WalkObject_t)(FILE *out, GCType* tp, PRWord *obj, |
| size_t bytes, PRBool detailed); |
| typedef void (*WalkUnknown_t)(FILE *out, GCType* tp, PRWord tix, PRWord *p, |
| size_t bytes, PRBool detailed); |
| typedef void (*WalkFree_t)(FILE *out, PRWord *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 *p, *limit; |
| |
| p = (PRWord *) sp->base; |
| limit = (PRWord *) sp->limit; |
| if (enterMsg) |
| fprintf(out, enterMsg, p); |
| while (p < limit) |
| { |
| if (IS_HBIT(sp, p)) /* Is this an object header? */ |
| { |
| PRWord h = p[0]; |
| PRWord tix = GET_TYPEIX(h); |
| size_t bytes = OBJ_BYTES(h); |
| PRWord* np = (PRWord*) ((char*)p + bytes); |
| |
| GCType* tp = &_pr_collectorTypes[tix].gctype; |
| if ((0 != tp) && walkObject) |
| walkObject(out, tp, p, bytes, detailed); |
| else if (walkUnknown) |
| walkUnknown(out, tp, 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*)((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_IMPLEMENT(void) |
| PR_DumpIndent(FILE *out, int indent) |
| { |
| while (--indent >= 0) |
| fprintf(out, " "); |
| } |
| |
| static void |
| PR_DumpHexWords(FILE *out, PRWord *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%.8lX", (long) *p++); |
| if (i) |
| fputc(' ', out); |
| } |
| fputc('\n', out); |
| } |
| } |
| |
| static void PR_CALLBACK |
| pr_DumpObject(FILE *out, GCType* tp, PRWord *p, |
| size_t bytes, PRBool detailed) |
| { |
| char kindChar = tp->kindChar; |
| fprintf(out, "0x%p: 0x%.6lX %c ", |
| p, (long) bytes, kindChar ? kindChar : '?'); |
| if (tp->dump) |
| (*tp->dump)(out, (void*) (p + 1), detailed, 0); |
| if (detailed) |
| PR_DumpHexWords(out, p, bytes>>2, 22, 4); |
| } |
| |
| static void PR_CALLBACK |
| pr_DumpUnknown(FILE *out, GCType* tp, PRWord tix, PRWord *p, |
| size_t bytes, PRBool detailed) |
| { |
| char kindChar = tp->kindChar; |
| fprintf(out, "0x%p: 0x%.6lX %c ", |
| p, (long) bytes, kindChar ? kindChar : '?'); |
| fprintf(out, "UNKNOWN KIND %ld\n", (long) tix); |
| if (detailed) |
| PR_DumpHexWords(out, p, bytes>>2, 22, 4); |
| } |
| |
| static void PR_CALLBACK |
| pr_DumpFree(FILE *out, PRWord *p, size_t size, PRBool detailed) |
| { |
| fprintf(out, "0x%p: 0x%.6lX - FREE\n", p, (long) size); |
| } |
| |
| static void PR_CALLBACK |
| 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); |
| } |
| |
| static void pr_DumpRoots(FILE *out); |
| |
| /* |
| ** Dump out the GC heap. |
| */ |
| PR_IMPLEMENT(void) |
| PR_DumpGCHeap(FILE *out, PRBool detailed) |
| { |
| fprintf(out, "\n" |
| "The kinds are:\n" |
| " U unscanned block\n" |
| " W weak link block\n" |
| " S scanned block\n" |
| " F scanned and final block\n" |
| " C class record\n" |
| " X context record\n" |
| " - free list item\n" |
| " ? other\n"); |
| LOCK_GC(); |
| pr_WalkSegments(out, pr_DumpSegment, detailed); |
| if (detailed) |
| pr_DumpRoots(out); |
| UNLOCK_GC(); |
| } |
| |
| PR_IMPLEMENT(void) |
| PR_DumpMemory(PRBool detailed) |
| { |
| PR_DumpToFile("memory.out", "Dumping memory", PR_DumpGCHeap, detailed); |
| } |
| |
| /******************************************************************************/ |
| |
| static PRInt32 PR_CALLBACK |
| pr_DumpRootPointer(PRWord* p, void* data) |
| { |
| PRWord h = p[0]; |
| PRWord tix = GET_TYPEIX(h); |
| size_t bytes = OBJ_BYTES(h); |
| |
| GCType* tp = &_pr_collectorTypes[tix].gctype; |
| if (0 != tp) |
| pr_DumpObject(_pr_gcData.dumpOutput, tp, p, bytes, PR_FALSE); |
| else |
| pr_DumpUnknown(_pr_gcData.dumpOutput, tp, tix, p, bytes, PR_FALSE); |
| return 0; |
| } |
| |
| static void PR_CALLBACK |
| pr_ConservativeDumpRootPointer(void* ptr) |
| { |
| (void)pr_ConservativeWalkPointer(ptr, (PRWalkFun) pr_DumpRootPointer, NULL); |
| } |
| |
| static void PR_CALLBACK |
| pr_ConservativeDumpRootBlock(void **base, PRInt32 count) |
| { |
| (void)pr_ConservativeWalkBlock(base, count, (PRWalkFun) pr_DumpRootPointer, NULL); |
| } |
| |
| extern int |
| DumpThreadRoots(PRThread *t, int i, void *notused); |
| |
| static void |
| pr_DumpRoots(FILE *out) |
| { |
| RootFinder *rf; |
| void (*liveBlock)(void **base, PRInt32 count); |
| void (*livePointer)(void *ptr); |
| void (*processRootBlock)(void **base, PRInt32 count); |
| void (*processRootPointer)(void *ptr); |
| |
| LOCK_GC(); |
| |
| liveBlock = _pr_gcData.liveBlock; |
| livePointer = _pr_gcData.livePointer; |
| processRootBlock = _pr_gcData.processRootBlock; |
| processRootPointer = _pr_gcData.processRootPointer; |
| |
| _pr_gcData.liveBlock = pr_ConservativeDumpRootBlock; |
| _pr_gcData.livePointer = pr_ConservativeDumpRootPointer; |
| _pr_gcData.processRootBlock = pr_ConservativeDumpRootBlock; |
| _pr_gcData.processRootPointer = pr_ConservativeDumpRootPointer; |
| _pr_gcData.dumpOutput = out; |
| |
| rf = _pr_rootFinders; |
| while (rf) { |
| fprintf(out, "\n===== Roots for %s\n", rf->name); |
| (*rf->func)(rf->arg); |
| rf = rf->next; |
| } |
| |
| _pr_gcData.liveBlock = liveBlock; |
| _pr_gcData.livePointer = livePointer; |
| _pr_gcData.processRootBlock = processRootBlock; |
| _pr_gcData.processRootPointer = processRootPointer; |
| _pr_gcData.dumpOutput = NULL; |
| |
| UNLOCK_GC(); |
| } |
| |
| /******************************************************************************* |
| * Heap Summary Dumper |
| ******************************************************************************/ |
| |
| PRSummaryPrinter summaryPrinter = NULL; |
| void* summaryPrinterClosure = NULL; |
| |
| PR_IMPLEMENT(void) |
| PR_RegisterSummaryPrinter(PRSummaryPrinter fun, void* closure) |
| { |
| summaryPrinter = fun; |
| summaryPrinterClosure = closure; |
| } |
| |
| static void PR_CALLBACK |
| pr_SummarizeObject(FILE *out, GCType* tp, PRWord *p, |
| size_t bytes, PRBool detailed) |
| { |
| if (tp->summarize) |
| (*tp->summarize)((void GCPTR*)(p + 1), bytes); |
| } |
| |
| static void PR_CALLBACK |
| pr_DumpSummary(FILE* out, GCSeg* sp, PRBool detailed) |
| { |
| pr_WalkSegment(out, sp, detailed, NULL, NULL, |
| pr_SummarizeObject, NULL, NULL); |
| } |
| |
| PR_IMPLEMENT(void) |
| PR_DumpGCSummary(FILE *out, PRBool detailed) |
| { |
| if (summaryPrinter) { |
| pr_WalkSegments(out, pr_DumpSummary, detailed); |
| summaryPrinter(out, summaryPrinterClosure); |
| } |
| #if 0 |
| fprintf(out, "\nFinalizable objects:\n"); |
| { |
| PRCList *qp; |
| qp = _pr_pendingFinalQueue.next; |
| while (qp != &_pr_pendingFinalQueue) { |
| GCFinal* fp = FinalPtr(qp); |
| PRWord h = fp->object[0]; /* Grab header word */ |
| PRWord 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_IMPLEMENT(void) |
| PR_DumpMemorySummary(void) |
| { |
| PR_DumpToFile("memory.out", "Memory Summary", PR_DumpGCSummary, PR_FALSE); |
| } |
| |
| /******************************************************************************* |
| * End Of Heap Walker |
| ******************************************************************************/ |
| |
| #ifdef GC_TRACEROOTS |
| |
| PRInt32 pr_traceGen = 0; |
| |
| static PRBool |
| pr_IsMarked(PRWord* p) |
| { |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| PR_ASSERT(end->check == PR_BLOCK_END); |
| return end->traceGeneration == pr_traceGen; |
| } |
| |
| static void |
| pr_Mark(PRWord* p) |
| { |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| PR_ASSERT(end->check == PR_BLOCK_END); |
| end->traceGeneration = pr_traceGen; |
| } |
| |
| PRWord* pr_traceObj; /* set this in the debugger, then execute PR_TraceRoot() */ |
| |
| static PRInt32 PR_CALLBACK |
| pr_TraceRootObject(void* obj, void* data); |
| |
| static PRInt32 PR_CALLBACK |
| pr_TraceRootPointer(PRWord *p, void* data) |
| { |
| PRInt32 printTrace = 0; |
| PRWord h = p[0]; |
| PRWord tix = GET_TYPEIX(h); |
| GCType* tp = &_pr_collectorTypes[tix].gctype; |
| FILE* out = _pr_gcData.dumpOutput; |
| |
| PR_ASSERT(tp); |
| if (pr_IsMarked(p)) |
| return printTrace; |
| |
| pr_Mark(p); |
| if (p == pr_traceObj) { |
| fprintf(out, "\n### Found path to:\n"); |
| printTrace = 1; |
| } |
| else { |
| if (PR_StackSpaceLeft(PR_GetCurrentThread()) < 512) { |
| fprintf(out, "\n### Path too deep (giving up):\n"); |
| printTrace = 1; |
| } |
| else if (tp->walk) { |
| printTrace = tp->walk((void*)(p + 1), pr_TraceRootObject, data); |
| } |
| /* else there's no way to walk this object, so we |
| haven't found what we're looking for */ |
| } |
| |
| if (printTrace == 1) { |
| PR_ASSERT(tp->dump); |
| fprintf(out, "0x%p: ", p); |
| tp->dump(out, (void*)(p + 1), PR_FALSE, 1); |
| } |
| return printTrace; |
| } |
| |
| static PRInt32 PR_CALLBACK |
| pr_TraceRootObject(void* obj, void* data) |
| { |
| /* This version of pr_TraceRootPointer takes object |
| pointers, instead of gc header pointers. */ |
| return pr_TraceRootPointer((PRWord*)obj - 1, data); |
| } |
| |
| static void PR_CALLBACK |
| pr_ConservativeTraceRootPointer(PRWord *p) |
| { |
| PRInt32 status; |
| ++pr_traceGen; |
| status = pr_ConservativeWalkPointer(p, pr_TraceRootPointer, NULL); |
| if (status) { |
| FILE* out = _pr_gcData.dumpOutput; |
| fprintf(out, "### from root at 0x%p\n\n", p); |
| } |
| } |
| |
| static void PR_CALLBACK |
| pr_ConservativeTraceRootBlock(void **base, PRInt32 count) |
| { |
| PRInt32 status; |
| ++pr_traceGen; |
| status = pr_ConservativeWalkBlock(base, count, pr_TraceRootPointer, NULL); |
| if (status) { |
| FILE* out = _pr_gcData.dumpOutput; |
| fprintf(out, "### from root in range 0x%p + 0x%lx\n\n", |
| base, (long) count); |
| } |
| } |
| |
| static void |
| PR_TraceRoot1(FILE* out, PRBool detailed) |
| { |
| RootFinder *rf; |
| void (*liveBlock)(void **base, PRInt32 count); |
| void (*livePointer)(void *ptr); |
| void (*processRootBlock)(void **base, PRInt32 count); |
| void (*processRootPointer)(void *ptr); |
| |
| LOCK_GC(); |
| |
| liveBlock = _pr_gcData.liveBlock; |
| livePointer = _pr_gcData.livePointer; |
| processRootBlock = _pr_gcData.processRootBlock; |
| processRootPointer = _pr_gcData.processRootPointer; |
| |
| _pr_gcData.liveBlock = pr_ConservativeTraceRootBlock; |
| _pr_gcData.livePointer = pr_ConservativeTraceRootPointer; |
| _pr_gcData.processRootBlock = pr_ConservativeTraceRootBlock; |
| _pr_gcData.processRootPointer = pr_ConservativeTraceRootPointer; |
| _pr_gcData.dumpOutput = out; |
| |
| fprintf(out, "### Looking for paths to 0x%p\n\n", pr_traceObj); |
| |
| rf = _pr_rootFinders; |
| while (rf) { |
| fprintf(out, "\n===== Roots for %s\n", rf->name); |
| (*rf->func)(rf->arg); |
| rf = rf->next; |
| } |
| |
| _pr_gcData.liveBlock = liveBlock; |
| _pr_gcData.livePointer = livePointer; |
| _pr_gcData.processRootBlock = processRootBlock; |
| _pr_gcData.processRootPointer = processRootPointer; |
| _pr_gcData.dumpOutput = NULL; |
| |
| UNLOCK_GC(); |
| } |
| |
| PR_PUBLIC_API(void) |
| PR_TraceRoot() |
| { |
| /* |
| ** How this works: |
| ** Once you find the object you want to trace the roots of, set the |
| ** global variable pr_traceObj to point to it (the header, not the |
| ** java handle), and then call this routine (on Windows, you can set |
| ** a breakpoint at the end of a function that returns void (e.g. dogc) |
| ** and then do a "set next statement" to point to this routine and go. |
| ** This will dump a list of the paths from the roots to the object in |
| ** question to your memory.out file. |
| */ |
| PR_DumpToFile("memory.out", "Tracing Roots", PR_TraceRoot1, PR_FALSE); |
| } |
| |
| #endif /* GC_TRACEROOTS */ |
| |
| /******************************************************************************/ |
| |
| #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"); |
| } |
| #endif |
| |
| #if defined(DEBUG) && defined(WIN32) |
| static void DumpApplicationHeaps(FILE *out) |
| { |
| HANDLE mainHeap; |
| HANDLE heaps[100]; |
| DWORD nHeaps; |
| PRInt32 i; |
| |
| mainHeap = GetProcessHeap(); |
| nHeaps = GetProcessHeaps(100, heaps); |
| if (nHeaps > 100) |
| nHeaps = 0; |
| fprintf(out, "%ld heaps:\n", (long) nHeaps); |
| for (i = 0; i<nHeaps; i++) |
| { |
| HANDLE heap = heaps[i]; |
| |
| fprintf(out, "Heap at 0x%.8lX", (long) heap); |
| if (heap == mainHeap) |
| fprintf(out, " (main)"); |
| fprintf(out, ":\n"); |
| DumpApplicationHeap(out, heap); |
| fprintf(out, "\n"); |
| } |
| fprintf(out, "End of heap dump\n\n"); |
| } |
| #endif |
| |
| #if defined(DEBUG) && defined(WIN32) |
| PR_IMPLEMENT(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_IMPLEMENT(void) PR_DumpApplicationHeaps(void) |
| { |
| fprintf(stderr, "Native heap dumping is currently implemented only for Windows32.\n"); |
| } |
| #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 *BinAlloc(int cbix, PRInt32 bytes, int dub) |
| { |
| GCFreeChunk **cpp, *cp, *cpNext; |
| GCSeg *sp; |
| PRInt32 chunkSize, remainder; |
| PRWord *p, *np; |
| PRInt32 bin, newbin; |
| |
| /* Compute bin that allocation belongs in */ |
| InlineBinNumber(bin,bytes) |
| if (bin < minBin) { |
| bin = minBin; /* Start at first filled bin */ |
| } |
| |
| /* Search in the bin, and larger bins, for a big enough piece */ |
| for (; bin <= NUM_BINS-1; bin++) { |
| cpp = &bins[bin]; |
| while ((cp = *cpp) != 0) { |
| chunkSize = cp->chunkSize; |
| if (chunkSize < bytes) { |
| /* Too small; skip it */ |
| METER(meter.skippedFreeChunks++); |
| cpp = &cp->next; |
| continue; |
| } |
| |
| /* We have found a hunk of memory large enough to use */ |
| p = (PRWord*) cp; |
| sp = cp->segment; |
| cpNext = cp->next; |
| #ifndef IS_64 |
| if (dub && (((PRWord)p & (PR_BYTES_PER_DWORD-1)) == 0)) { |
| /* |
| * We are double aligning the memory and the current free |
| * chunk is aligned on an even boundary. Because header |
| * words are one word long we need to discard the first |
| * word of memory. |
| */ |
| p[0] = MAKE_HEADER(FREE_MEMORY_TYPEIX, 1); |
| SET_HBIT(sp, p); |
| p++; |
| chunkSize -= PR_BYTES_PER_WORD; |
| bytes -= PR_BYTES_PER_WORD; |
| PR_ASSERT(((PRWord)p & (PR_BYTES_PER_DWORD-1)) != 0); |
| _pr_gcData.freeMemory -= PR_BYTES_PER_WORD; |
| _pr_gcData.busyMemory += PR_BYTES_PER_WORD; |
| } |
| #endif |
| np = (PRWord*) ((char*) p + bytes); |
| remainder = chunkSize - bytes; |
| if (remainder >= MIN_FREE_CHUNK_BYTES) { |
| /* The left over memory is large enough to be freed. */ |
| cp = (GCFreeChunk*) np; |
| cp->segment = sp; |
| cp->chunkSize = remainder; |
| InlineBinNumber(newbin, remainder) |
| if (newbin != bin) { |
| *cpp = (GCFreeChunk*) cpNext; /* remove */ |
| cp->next = bins[newbin]; /* insert */ |
| bins[newbin] = cp; |
| if (newbin < minBin) minBin = newbin; |
| if (newbin > maxBin) maxBin = newbin; |
| } else { |
| /* Leave it on the same list */ |
| cp->next = cpNext; |
| *cpp = (GCFreeChunk*) np; |
| } |
| } else { |
| /* |
| * The left over memory is too small to be released. Just |
| * leave it attached to the chunk of memory being |
| * returned. |
| */ |
| *cpp = cpNext; |
| bytes = chunkSize; |
| } |
| p[0] = MAKE_HEADER(cbix, (bytes >> PR_BYTES_PER_WORD_LOG2)); |
| 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 *BigAlloc(int cbix, PRInt32 bytes, int dub) |
| { |
| GCSeg *sp; |
| PRWord *p, h; |
| PRInt32 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 */ |
| sp = GrowHeapExactly(bytes); |
| |
| if (sp) { |
| p = (PRWord*) sp->base; |
| chunkSize = sp->limit - sp->base; |
| |
| /* All memory is double aligned on 64 bit machines... */ |
| #ifndef IS_64 |
| if (dub && (((PRWord)p & (PR_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 -= PR_BYTES_PER_WORD; |
| _pr_gcData.freeMemory -= PR_BYTES_PER_WORD; |
| _pr_gcData.busyMemory += PR_BYTES_PER_WORD; |
| PR_ASSERT(((PRWord)p & (PR_BYTES_PER_DWORD-1)) != 0); |
| } |
| #endif |
| |
| /* Consume the *entire* segment with a single allocation */ |
| h = MAKE_HEADER(cbix, (chunkSize >> PR_BYTES_PER_WORD_LOG2)); |
| p[0] = h; |
| SET_HBIT(sp, p); |
| _pr_gcData.freeMemory -= chunkSize; |
| _pr_gcData.busyMemory += chunkSize; |
| return p; |
| } |
| return 0; |
| } |
| |
| /* we disable gc allocation during low memory conditions */ |
| static PRBool allocationEnabled = PR_TRUE; |
| |
| PR_IMPLEMENT(void) PR_EnableAllocation(PRBool yesOrNo) |
| { |
| allocationEnabled = yesOrNo; |
| } |
| |
| static void CollectorCleanup(void) { |
| while (collectorCleanupNeeded) { |
| LOCK_GC(); |
| collectorCleanupNeeded = 0; |
| UNLOCK_GC(); |
| if (freeSegs) { |
| FreeSegments(); |
| } |
| if (!WEAK_FREELIST_ISEMPTY()) { |
| EmptyWeakFreeList(); |
| } |
| } |
| } |
| |
| /******************************************************************************/ |
| |
| #ifdef GC_CHECK |
| static PRInt32 allocationCount; |
| |
| static void EarthShatteringKaBoom(PRInt32 whichOne) { |
| long* p = 0; |
| *p = 0; |
| } |
| |
| /* Check a segment of heap memory. Verify that the object memory |
| hasn't been overwritten (past the end at least) */ |
| static void CheckSegment(GCSeg* sp) { |
| PRWord h, tix; |
| PRWord *p, *lastp, *np, *limit; |
| |
| lastp = p = (PRWord *) sp->base; |
| limit = (PRWord *) sp->limit; |
| while (p < limit) { |
| if (IS_HBIT(sp, p)) { |
| char *cp, i; |
| GCBlockEnd* end; |
| PRWord bytes, requestedBytes; |
| |
| h = p[0]; |
| tix = GET_TYPEIX(h); |
| bytes = OBJ_BYTES(h); |
| np = (PRWord *) ((char *)p + bytes); |
| if (tix != FREE_MEMORY_TYPEIX) { |
| PRInt32 test; /* msdev get's fooled without this local */ |
| /* A live object is here. The last word in the object will |
| contain the objects requestedSize */ |
| end = (GCBlockEnd*)((char*)(p) + bytes - sizeof(GCBlockEnd)); |
| test = end->check; |
| if (test != PR_BLOCK_END) { |
| PR_ASSERT(test == PR_BLOCK_END); |
| } |
| requestedBytes = end->requestedBytes; |
| if (requestedBytes >= bytes) EarthShatteringKaBoom(0); |
| cp = (char*)(p + 1) + requestedBytes; |
| i = (char) 0xff; |
| while (cp < (char*)end) { |
| if (*cp != i) EarthShatteringKaBoom(1); |
| cp++; |
| i--; |
| } |
| } |
| lastp = p; |
| p = np; |
| } else { |
| /* Must be a freelist item */ |
| GCFreeChunk *cp = (GCFreeChunk*) p; |
| if ((PRInt32)cp->chunkSize < (PRInt32)sizeof(GCFreeChunk)) { |
| EarthShatteringKaBoom(3); |
| } |
| lastp = p; |
| p = (PRWord*) ((char*)p + cp->chunkSize); |
| } |
| } |
| } |
| |
| static void CheckHeap(void) { |
| GCSeg *sp = segs; |
| GCSeg *esp = sp + nsegs; |
| while (sp < esp) { |
| CheckSegment(sp); |
| sp++; |
| } |
| } |
| |
| #endif /* GC_CHECK */ |
| |
| /******************************************************************************/ |
| |
| #ifdef DEBUG |
| long gc_thrash = -1L; |
| #endif |
| |
| /* |
| ** 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. |
| */ |
| PR_IMPLEMENT(PRWord GCPTR *)PR_AllocMemory( |
| PRWord requestedBytes, PRInt32 tix, PRWord flags) |
| { |
| PRWord *p; |
| CollectorType *ct; |
| PRInt32 bytes; |
| GCFinal *final = 0; |
| GCWeak *weak = 0; |
| int dub = flags & PR_ALLOC_DOUBLE; |
| PRInt32 objBytes; |
| #ifdef GC_STATS |
| PRInt64 allocTime, ldelta; |
| #endif |
| |
| if (!allocationEnabled) return NULL; |
| |
| PR_ASSERT(requestedBytes >= 0); |
| PR_ASSERT(_pr_collectorTypes[tix].flags != 0); |
| |
| #ifdef DEBUG |
| if (_pr_do_a_dump) { |
| /* |
| ** Collect, pause for a second (lets finalizer run), and then GC |
| ** again. |
| */ |
| PR_GC(); |
| PR_Sleep(PR_MicrosecondsToInterval(1000000L)); |
| PR_GC(); |
| PR_DumpGCHeap(_pr_dump_file, PR_TRUE); |
| _pr_do_a_dump = 0; |
| } |
| #endif |
| |
| #ifdef GC_STATS |
| allocTime = PR_Now(); |
| #endif |
| bytes = (PRInt32) requestedBytes; |
| |
| /* |
| ** Align bytes to a multiple of a PRWord, then add in enough space |
| ** to hold the header word. |
| ** |
| ** MSVC 1.52 crashed on the ff. code because of the "complex" shifting :-( |
| */ |
| /* Check for possible overflow of bytes before performing add */ |
| if ((MAX_INT - PR_BYTES_PER_WORD) < bytes ) return NULL; |
| bytes = (bytes + PR_BYTES_PER_WORD - 1) >> PR_BYTES_PER_WORD_LOG2; |
| bytes <<= PR_BYTES_PER_WORD_LOG2; |
| /* Check for possible overflow of bytes before performing add */ |
| if ((MAX_INT - sizeof(PRWord)) < bytes ) return NULL; |
| bytes += sizeof(PRWord); |
| /* |
| * Add in an extra word of memory for double-aligned memory. Some |
| * percentage of the time this will waste a word of memory (too |
| * bad). Howver, it makes the allocation logic much simpler and |
| * faster. |
| */ |
| #ifndef IS_64 |
| if (dub) { |
| /* Check for possible overflow of bytes before performing add */ |
| if ((MAX_INT - PR_BYTES_PER_WORD) < bytes ) return NULL; |
| bytes += PR_BYTES_PER_WORD; |
| } |
| #endif |
| |
| #ifdef GC_CHECK |
| if (_pr_gcData.flags & GC_CHECK) { |
| /* Bloat the allocation a bit so that we can lay down |
| a check pattern that we will validate */ |
| /* Check for possible overflow of bytes before performing add */ |
| if ((MAX_INT - PR_BYTES_PER_WORD * 3) < bytes ) return NULL; |
| bytes += PR_BYTES_PER_WORD * 3; |
| } |
| #endif |
| |
| #if defined(GC_CHECK) || defined(GC_STATS) || defined(GC_TRACEROOTS) |
| if ((MAX_INT - sizeof(GCBlockEnd)) < bytes ) return NULL; |
| bytes += sizeof(GCBlockEnd); |
| #endif |
| |
| PR_ASSERT( bytes < MAX_ALLOC_SIZE ); |
| /* |
| ** Java can ask for objects bigger than MAX_ALLOC_SIZE, |
| ** but it won't get them. |
| */ |
| if (bytes >= MAX_ALLOC_SIZE) return NULL; |
| |
| #ifdef DEBUG |
| if (gc_thrash == -1L ? (gc_thrash = (long)PR_GetEnv("GC_THRASH")):gc_thrash) PR_GC(); |
| #endif |
| |
| ct = &_pr_collectorTypes[tix]; |
| if (ct->flags & (_GC_TYPE_FINAL|_GC_TYPE_WEAK)) { |
| if (0 != ct->gctype.finalize) { |
| /* |
| ** 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*/ |
| PR_ASSERT(0); |
| return 0; |
| } |
| } |
| if (0 != ct->gctype.getWeakLinkOffset) { |
| /* |
| ** Allocate a GCWeak struct for this object in advance. Don't put |
| ** it on the weak links list until we have allocated the object |
| */ |
| weak = AllocWeakNode(); |
| if (!weak) { |
| /* XXX THIS IS NOT ACCEPTABLE*/ |
| if (0 != final) { |
| FreeFinalNode(final); |
| } |
| PR_ASSERT(0); |
| return 0; |
| } |
| } |
| } |
| |
| LOCK_GC(); |
| #ifdef GC_CHECK |
| if (_pr_gcData.flags & GC_CHECK) CheckHeap(); |
| allocationCount++; |
| #endif |
| |
| /* Check for overflow of maximum size we can handle */ |
| if (bytes > MAX_ALLOC) goto lost; |
| |
| /* Try default allocation */ |
| p = ((bytes >= BIG_ALLOC) && (nsegs < MAX_SEGS)) ? |
| BigAlloc(tix, bytes, dub) : BinAlloc(tix, bytes, dub); |
| if (0 == p) { |
| #ifdef GC_STATS |
| LL_SUB(ldelta, PR_Now(), allocTime); |
| #endif |
| /* Collect some memory */ |
| _GCTRACE(GC_ALLOC, ("force GC: want %d", bytes)); |
| dogc(); |
| PR_ASSERT( GC_IS_LOCKED() ); |
| |
| /* After a collection we check and see if we should grow the |
| ** heap. We grow the heap when the amount of memory free is less |
| ** than a certain percentage of the heap size. We don't check to |
| ** see if the grow succeeded because our fallback strategy in |
| ** either case is to try one more time to allocate. */ |
| if ((_pr_gcData.allocMemory < _pr_gcData.maxMemory) |
| && ((_pr_gcData.freeMemory < |
| ((_pr_gcData.allocMemory * MIN_FREE_THRESHOLD_AFTER_GC) / 100L)) |
| || (_pr_gcData.freeMemory < bytes))) { |
| GrowHeap(PR_MAX(bytes, segmentSize)); |
| } |
| #ifdef GC_STATS |
| LL_ADD(allocTime, PR_Now(), ldelta); |
| #endif |
| |
| /* Try again */ |
| p = ((bytes >= BIG_ALLOC) && (nsegs < MAX_SEGS)) ? |
| BigAlloc(tix, bytes, dub) : BinAlloc(tix, bytes, dub); |
| if (0 == p) { |
| /* Well that lost big time. Memory must be pretty well fragmented */ |
| if (!GrowHeap(PR_MAX(bytes, segmentSize))) goto lost; |
| p = BinAlloc(tix, bytes, dub); |
| if (0 == p) goto lost; |
| } |
| } |
| |
| /* Zero out the portion of the object memory that was used by |
| the GCFreeChunk structure (skip the first word because it |
| was already overwritten by the gc header word) */ |
| objBytes = OBJ_BYTES(p[0]); |
| if (objBytes > sizeof(PRWord)) p[1] = 0; |
| if (objBytes > sizeof(PRWord)*2) p[2] = 0; |
| |
| if (final) { |
| _GCTRACE(GC_ALLOC, ("alloc 0x%x (%d) final=0x%x", |
| p, bytes, final)); |
| final->object = p; |
| PR_APPEND_LINK(&final->links, &_pr_finalizeableObjects); |
| } else { |
| _GCTRACE(GC_ALLOC, ("alloc 0x%x (%d)", p, bytes)); |
| } |
| if (weak) { |
| weak->object = p; |
| PR_APPEND_LINK(&weak->links, &_pr_weakLinks); |
| } |
| METER(meter.allocBytes += bytes); |
| METER(meter.wastedBytes += (bytes - requestedBytes)); |
| UNLOCK_GC(); |
| |
| if (collectorCleanupNeeded) { |
| CollectorCleanup(); |
| } |
| |
| #if defined(GC_CHECK) || defined(GC_STATS) || defined(GC_TRACEROOTS) |
| { |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| end->check = PR_BLOCK_END; |
| } |
| #endif |
| #ifdef GC_STATS |
| { |
| PRInt64 now = PR_Now(); |
| double delta; |
| PRInt32 bin; |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| |
| end->allocTime = allocTime; |
| LL_SUB(ldelta, now, allocTime); |
| LL_L2D(delta, ldelta); |
| InlineBinNumber(bin, requestedBytes); |
| end->bin = bin; |
| gcstats[bin].nallocs++; |
| gcstats[bin].allocTime += delta; |
| gcstats[bin].allocTimeVariance += delta * delta; |
| } |
| #endif |
| #ifdef GC_CHECK |
| if (_pr_gcData.flags & GC_CHECK) { |
| /* Place a pattern in the memory that was allocated that was not |
| requested. We will check the pattern later. */ |
| char* cp = (char*)(p + 1) + requestedBytes; |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| char i = (char) 0xff; |
| while (cp < (char*)end) { |
| *cp++ = i--; |
| } |
| end->requestedBytes = requestedBytes; |
| CheckHeap(); |
| } |
| #endif |
| return p + 1; |
| |
| lost: |
| /* Out of memory */ |
| UNLOCK_GC(); |
| if (final) { |
| FreeFinalNode(final); |
| } |
| if (weak) { |
| FreeWeakNode(weak); |
| } |
| if (collectorCleanupNeeded) { |
| CollectorCleanup(); |
| } |
| return 0; |
| } |
| |
| /* Shortcut allocator for objects that do not require finalization or |
| are weak objects */ |
| PR_IMPLEMENT(PRWord GCPTR *) |
| PR_AllocSimpleMemory(PRWord requestedBytes, PRInt32 tix) |
| { |
| PRWord *p; |
| PRInt32 bytes; |
| PRInt32 objBytes; |
| #ifdef GC_STATS |
| PRInt64 allocTime, ldelta; |
| #endif |
| |
| if (!allocationEnabled) return NULL; |
| |
| PR_ASSERT(requestedBytes >= 0); |
| PR_ASSERT(_pr_collectorTypes[tix].flags != 0); |
| |
| #ifdef DEBUG |
| if (_pr_do_a_dump) { |
| /* |
| ** Collect, pause for a second (lets finalizer run), and then GC |
| ** again. |
| */ |
| PR_GC(); |
| PR_Sleep(PR_MicrosecondsToInterval(1000000L)); |
| PR_GC(); |
| PR_DumpGCHeap(_pr_dump_file, PR_TRUE); |
| _pr_do_a_dump = 0; |
| } |
| #endif |
| |
| #ifdef GC_STATS |
| allocTime = PR_NowMS(); |
| #endif |
| bytes = (PRInt32) requestedBytes; |
| |
| /* |
| ** Align bytes to a multiple of a PRWord, then add in enough space |
| ** to hold the header word. |
| ** |
| ** MSVC 1.52 crashed on the ff. code because of the "complex" shifting :-( |
| */ |
| bytes = (bytes + PR_BYTES_PER_WORD - 1) >> PR_BYTES_PER_WORD_LOG2; |
| bytes <<= PR_BYTES_PER_WORD_LOG2; |
| bytes += sizeof(PRWord); |
| |
| /* |
| * Add in an extra word of memory for double-aligned memory. Some |
| * percentage of the time this will waste a word of memory (too |
| * bad). Howver, it makes the allocation logic much simpler and |
| * faster. |
| */ |
| #ifndef IS_64 |
| bytes += PR_BYTES_PER_WORD; |
| #endif |
| |
| #ifdef GC_CHECK |
| if (_pr_gcData.flags & GC_CHECK) { |
| /* Bloat the allocation a bit so that we can lay down |
| a check pattern that we will validate */ |
| bytes += PR_BYTES_PER_WORD * 2; |
| } |
| #endif |
| |
| #if defined(GC_CHECK) || defined(GC_STATS) || defined(GC_TRACEROOTS) |
| bytes += sizeof(GCBlockEnd); |
| #endif |
| |
| /* Java can ask for objects bigger than 4M, but it won't get them */ |
| /* |
| * This check was added because there is a fundamental limit of |
| * the size field maintained by the gc code. Going over the 4M |
| * limit caused some bits to roll over into another bit field, |
| * violating the max segment size and causing a bug. |
| */ |
| if (bytes >= MAX_ALLOC_SIZE) { |
| return NULL; |
| } |
| #ifdef DEBUG |
| if (gc_thrash == -1L |
| ? (gc_thrash = (long)PR_GetEnv("GC_THRASH")) |
| : gc_thrash) { |
| PR_GC(); |
| } |
| #endif |
| |
| LOCK_GC(); |
| #ifdef GC_CHECK |
| if (_pr_gcData.flags & GC_CHECK) { |
| CheckHeap(); |
| } |
| allocationCount++; |
| #endif |
| |
| /* Try default allocation */ |
| if ((bytes >= BIG_ALLOC) && (nsegs < MAX_SEGS)) { |
| p = BigAlloc(tix, bytes, 1); |
| } else { |
| p = BinAlloc(tix, bytes, 1); |
| } |
| if (0 == p) { |
| #ifdef GC_STATS |
| LL_SUB(ldelta, PR_Now(), allocTime); |
| #endif |
| /* Collect some memory */ |
| _GCTRACE(GC_ALLOC, ("force GC: want %d", bytes)); |
| dogc(); |
| PR_ASSERT( GC_IS_LOCKED() ); |
| |
| /* After a collection we check and see if we should grow the |
| heap. We grow the heap when the amount of memory free is less |
| than a certain percentage of the heap size. We don't check to |
| see if the grow succeeded because our fallback strategy in |
| either case is to try one more time to allocate. */ |
| if ((_pr_gcData.allocMemory < _pr_gcData.maxMemory) && |
| (_pr_gcData.freeMemory < |
| ((_pr_gcData.allocMemory * MIN_FREE_THRESHOLD_AFTER_GC) / 100L))) { |
| GrowHeap(PR_MAX(bytes, segmentSize)); |
| } |
| #ifdef GC_STATS |
| LL_ADD(allocTime, PR_Now(), ldelta); |
| #endif |
| |
| /* Try one last time */ |
| if ((bytes >= BIG_ALLOC) && (nsegs < MAX_SEGS)) { |
| p = BigAlloc(tix, bytes, 1); |
| } else { |
| p = BinAlloc(tix, bytes, 1); |
| } |
| if (0 == p) { |
| /* Well that lost big time. Memory must be pretty well fragmented */ |
| if (!GrowHeap(PR_MAX(bytes, segmentSize))) { |
| goto lost; |
| } |
| p = BinAlloc(tix, bytes, 1); |
| if (0 == p) goto lost; |
| } |
| } |
| |
| /* Zero out the portion of the object memory that was used by |
| the GCFreeChunk structure (skip the first word because it |
| was already overwritten by the gc header word) */ |
| objBytes = OBJ_BYTES(p[0]); |
| if (objBytes > sizeof(PRWord)) p[1] = 0; |
| if (objBytes > sizeof(PRWord)*2) p[2] = 0; |
| |
| METER(meter.allocBytes += bytes); |
| METER(meter.wastedBytes += (bytes - requestedBytes)); |
| UNLOCK_GC(); |
| |
| if (collectorCleanupNeeded) { |
| CollectorCleanup(); |
| } |
| |
| #if defined(GC_CHECK) || defined(GC_STATS) || defined(GC_TRACEROOTS) |
| { |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| end->check = PR_BLOCK_END; |
| } |
| #endif |
| #ifdef GC_STATS |
| { |
| PRInt64 now = PR_Now(); |
| double delta; |
| PRInt32 bin; |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| |
| end->allocTime = allocTime; |
| LL_SUB(ldelta, now, allocTime); |
| LL_L2D(delta, ldelta); |
| InlineBinNumber(bin, requestedBytes); |
| end->bin = bin; |
| gcstats[bin].nallocs++; |
| gcstats[bin].allocTime += delta; |
| gcstats[bin].allocTimeVariance += delta * delta; |
| } |
| #endif |
| #ifdef GC_CHECK |
| if (_pr_gcData.flags & GC_CHECK) { |
| /* Place a pattern in the memory that was allocated that was not |
| requested. We will check the pattern later. */ |
| char* cp = (char*)(p + 1) + requestedBytes; |
| GCBlockEnd* end = (GCBlockEnd*)((char*)p + OBJ_BYTES(p[0]) - sizeof(GCBlockEnd)); |
| char i = (char) 0xff; |
| while (cp < (char*)end) { |
| *cp++ = i--; |
| } |
| end->requestedBytes = requestedBytes; |
| CheckHeap(); |
| } |
| #endif |
| return p + 1; |
| |
| lost: |
| /* Out of memory */ |
| UNLOCK_GC(); |
| if (collectorCleanupNeeded) { |
| CollectorCleanup(); |
| } |
| return 0; |
| } |
| |
| /************************************************************************/ |
| |
| PR_IMPLEMENT(PRWord) PR_GetObjectHeader(void *ptr) { |
| GCSeg *sp; |
| PRWord *h; |
| |
| if (ptr == 0) return 0; |
| sp = InHeap(ptr); |
| if (sp == 0) return 0; |
| h = (PRWord*)FindObject(sp, (PRWord*)ptr); |
| return GC_GET_USER_BITS(h[0]); |
| } |
| |
| PR_IMPLEMENT(PRWord) PR_SetObjectHeader(void *ptr, PRWord newUserBits) { |
| GCSeg *sp; |
| PRWord *h, rv; |
| |
| if (ptr == 0) return 0; |
| sp = InHeap(ptr); |
| if |