diff options
Diffstat (limited to 'src/third_party/js-1.7/jsgc.c')
-rw-r--r-- | src/third_party/js-1.7/jsgc.c | 3201 |
1 files changed, 3201 insertions, 0 deletions
diff --git a/src/third_party/js-1.7/jsgc.c b/src/third_party/js-1.7/jsgc.c new file mode 100644 index 00000000000..7fae096e0e8 --- /dev/null +++ b/src/third_party/js-1.7/jsgc.c @@ -0,0 +1,3201 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sw=4 et tw=78: + * + * ***** 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 Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of 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 ***** */ + +/* + * JS Mark-and-Sweep Garbage Collector. + * + * This GC allocates fixed-sized things with sizes up to GC_NBYTES_MAX (see + * jsgc.h). It allocates from a special GC arena pool with each arena allocated + * using malloc. It uses an ideally parallel array of flag bytes to hold the + * mark bit, finalizer type index, etc. + * + * XXX swizzle page to freelist for better locality of reference + */ +#include "jsstddef.h" +#include <stdlib.h> /* for free */ +#include <string.h> /* for memset used when DEBUG */ +#include "jstypes.h" +#include "jsutil.h" /* Added by JSIFY */ +#include "jshash.h" /* Added by JSIFY */ +#include "jsapi.h" +#include "jsatom.h" +#include "jsbit.h" +#include "jsclist.h" +#include "jscntxt.h" +#include "jsconfig.h" +#include "jsdbgapi.h" +#include "jsexn.h" +#include "jsfun.h" +#include "jsgc.h" +#include "jsinterp.h" +#include "jsiter.h" +#include "jslock.h" +#include "jsnum.h" +#include "jsobj.h" +#include "jsscope.h" +#include "jsscript.h" +#include "jsstr.h" + +#if JS_HAS_XML_SUPPORT +#include "jsxml.h" +#endif + +/* + * GC arena sizing depends on amortizing arena overhead using a large number + * of things per arena, and on the thing/flags ratio of 8:1 on most platforms. + * + * On 64-bit platforms, we would have half as many things per arena because + * pointers are twice as big, so we double the bytes for things per arena. + * This preserves the 1024 byte flags sub-arena size, which relates to the + * GC_PAGE_SIZE (see below for why). + */ +#if JS_BYTES_PER_WORD == 8 +# define GC_THINGS_SHIFT 14 /* 16KB for things on Alpha, etc. */ +#else +# define GC_THINGS_SHIFT 13 /* 8KB for things on most platforms */ +#endif +#define GC_THINGS_SIZE JS_BIT(GC_THINGS_SHIFT) +#define GC_FLAGS_SIZE (GC_THINGS_SIZE / sizeof(JSGCThing)) + +/* + * A GC arena contains one flag byte for each thing in its heap, and supports + * O(1) lookup of a flag given its thing's address. + * + * To implement this, we take advantage of the thing/flags numerology: given + * the 8K bytes worth of GC-things, there are 1K flag bytes. Within each 9K + * allocation for things+flags there are always 8 consecutive 1K-pages each + * aligned on 1K boundary. We use these pages to allocate things and the + * remaining 1K of space before and after the aligned pages to store flags. + * If we are really lucky and things+flags starts on a 1K boundary, then + * flags would consist of a single 1K chunk that comes after 8K of things. + * Otherwise there are 2 chunks of flags, one before and one after things. + * + * To be able to find the flag byte for a particular thing, we put a + * JSGCPageInfo record at the beginning of each 1K-aligned page to hold that + * page's offset from the beginning of things+flags allocation and we allocate + * things after this record. Thus for each thing |thing_address & ~1023| + * gives the address of a JSGCPageInfo record from which we read page_offset. + * Due to page alignment + * (page_offset & ~1023) + (thing_address & 1023) + * gives thing_offset from the beginning of 8K paged things. We then divide + * thing_offset by sizeof(JSGCThing) to get thing_index. + * + * Now |page_address - page_offset| is things+flags arena_address and + * (page_offset & 1023) is the offset of the first page from the start of + * things+flags area. Thus if + * thing_index < (page_offset & 1023) + * then + * allocation_start_address + thing_index < address_of_the_first_page + * and we use + * allocation_start_address + thing_index + * as the address to store thing's flags. If + * thing_index >= (page_offset & 1023), + * then we use the chunk of flags that comes after the pages with things + * and calculate the address for the flag byte as + * address_of_the_first_page + 8K + (thing_index - (page_offset & 1023)) + * which is just + * allocation_start_address + thing_index + 8K. + * + * When we allocate things with size equal to sizeof(JSGCThing), the overhead + * of this scheme for 32 bit platforms is (8+8*(8+1))/(8+9K) or 0.87% + * (assuming 4 bytes for each JSGCArena header, and 8 bytes for each + * JSGCThing and JSGCPageInfo). When thing_size > 8, the scheme wastes the + * flag byte for each extra 8 bytes beyond sizeof(JSGCThing) in thing_size + * and the overhead is close to 1/8 or 12.5%. + * FIXME: How can we avoid this overhead? + * + * Here's some ASCII art showing an arena: + * + * split or the first 1-K aligned address. + * | + * V + * +--+-------+-------+-------+-------+-------+-------+-------+-------+-----+ + * |fB| tp0 | tp1 | tp2 | tp3 | tp4 | tp5 | tp6 | tp7 | fA | + * +--+-------+-------+-------+-------+-------+-------+-------+-------+-----+ + * ^ ^ + * tI ---------+ | + * tJ -------------------------------------------+ + * + * - fB are the "before split" flags, fA are the "after split" flags + * - tp0-tp7 are the 8 thing pages + * - thing tI points into tp1, whose flags are below the split, in fB + * - thing tJ points into tp5, clearly above the split + * + * In general, one of the thing pages will have some of its things' flags on + * the low side of the split, and the rest of its things' flags on the high + * side. All the other pages have flags only below or only above. + * + * (If we need to implement card-marking for an incremental GC write barrier, + * we can replace word-sized offsetInArena in JSGCPageInfo by pair of + * uint8 card_mark and uint16 offsetInArena fields as the offset can not exceed + * GC_THINGS_SIZE. This would gives an extremely efficient write barrier: + * when mutating an object obj, just store a 1 byte at + * (uint8 *) ((jsuword)obj & ~1023) on 32-bit platforms.) + */ +#define GC_PAGE_SHIFT 10 +#define GC_PAGE_MASK ((jsuword) JS_BITMASK(GC_PAGE_SHIFT)) +#define GC_PAGE_SIZE JS_BIT(GC_PAGE_SHIFT) +#define GC_PAGE_COUNT (1 << (GC_THINGS_SHIFT - GC_PAGE_SHIFT)) + +typedef struct JSGCPageInfo { + jsuword offsetInArena; /* offset from the arena start */ + jsuword unscannedBitmap; /* bitset for fast search of marked + but not yet scanned GC things */ +} JSGCPageInfo; + +struct JSGCArena { + JSGCArenaList *list; /* allocation list for the arena */ + JSGCArena *prev; /* link field for allocation list */ + JSGCArena *prevUnscanned; /* link field for the list of arenas + with marked but not yet scanned + things */ + jsuword unscannedPages; /* bitset for fast search of pages + with marked but not yet scanned + things */ + uint8 base[1]; /* things+flags allocation area */ +}; + +#define GC_ARENA_SIZE \ + (offsetof(JSGCArena, base) + GC_THINGS_SIZE + GC_FLAGS_SIZE) + +#define FIRST_THING_PAGE(a) \ + (((jsuword)(a)->base + GC_FLAGS_SIZE - 1) & ~GC_PAGE_MASK) + +#define PAGE_TO_ARENA(pi) \ + ((JSGCArena *)((jsuword)(pi) - (pi)->offsetInArena \ + - offsetof(JSGCArena, base))) + +#define PAGE_INDEX(pi) \ + ((size_t)((pi)->offsetInArena >> GC_PAGE_SHIFT)) + +#define THING_TO_PAGE(thing) \ + ((JSGCPageInfo *)((jsuword)(thing) & ~GC_PAGE_MASK)) + +/* + * Given a thing size n, return the size of the gap from the page start before + * the first thing. We know that any n not a power of two packs from + * the end of the page leaving at least enough room for one JSGCPageInfo, but + * not for another thing, at the front of the page (JS_ASSERTs below insist + * on this). + * + * This works because all allocations are a multiple of sizeof(JSGCThing) == + * sizeof(JSGCPageInfo) in size. + */ +#define PAGE_THING_GAP(n) (((n) & ((n) - 1)) ? (GC_PAGE_SIZE % (n)) : (n)) + +#ifdef JS_THREADSAFE +/* + * The maximum number of things to put to the local free list by taking + * several things from the global free list or from the tail of the last + * allocated arena to amortize the cost of rt->gcLock. + * + * We use number 8 based on benchmarks from bug 312238. + */ +#define MAX_THREAD_LOCAL_THINGS 8 + +#endif + +JS_STATIC_ASSERT(sizeof(JSGCThing) == sizeof(JSGCPageInfo)); +JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(JSObject)); +JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(JSString)); +JS_STATIC_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble)); +JS_STATIC_ASSERT(GC_FLAGS_SIZE >= GC_PAGE_SIZE); +JS_STATIC_ASSERT(sizeof(JSStackHeader) >= 2 * sizeof(jsval)); + +/* + * JSPtrTable capacity growth descriptor. The table grows by powers of two + * starting from capacity JSPtrTableInfo.minCapacity, but switching to linear + * growth when capacity reaches JSPtrTableInfo.linearGrowthThreshold. + */ +typedef struct JSPtrTableInfo { + uint16 minCapacity; + uint16 linearGrowthThreshold; +} JSPtrTableInfo; + +#define GC_ITERATOR_TABLE_MIN 4 +#define GC_ITERATOR_TABLE_LINEAR 1024 + +static const JSPtrTableInfo iteratorTableInfo = { + GC_ITERATOR_TABLE_MIN, + GC_ITERATOR_TABLE_LINEAR +}; + +/* Calculate table capacity based on the current value of JSPtrTable.count. */ +static size_t +PtrTableCapacity(size_t count, const JSPtrTableInfo *info) +{ + size_t linear, log, capacity; + + linear = info->linearGrowthThreshold; + JS_ASSERT(info->minCapacity <= linear); + + if (count == 0) { + capacity = 0; + } else if (count < linear) { + log = JS_CEILING_LOG2W(count); + JS_ASSERT(log != JS_BITS_PER_WORD); + capacity = (size_t)1 << log; + if (capacity < info->minCapacity) + capacity = info->minCapacity; + } else { + capacity = JS_ROUNDUP(count, linear); + } + + JS_ASSERT(capacity >= count); + return capacity; +} + +static void +FreePtrTable(JSPtrTable *table, const JSPtrTableInfo *info) +{ + if (table->array) { + JS_ASSERT(table->count > 0); + free(table->array); + table->array = NULL; + table->count = 0; + } + JS_ASSERT(table->count == 0); +} + +static JSBool +AddToPtrTable(JSContext *cx, JSPtrTable *table, const JSPtrTableInfo *info, + void *ptr) +{ + size_t count, capacity; + void **array; + + count = table->count; + capacity = PtrTableCapacity(count, info); + + if (count == capacity) { + if (capacity < info->minCapacity) { + JS_ASSERT(capacity == 0); + JS_ASSERT(!table->array); + capacity = info->minCapacity; + } else { + /* + * Simplify the overflow detection assuming pointer is bigger + * than byte. + */ + JS_STATIC_ASSERT(2 <= sizeof table->array[0]); + capacity = (capacity < info->linearGrowthThreshold) + ? 2 * capacity + : capacity + info->linearGrowthThreshold; + if (capacity > (size_t)-1 / sizeof table->array[0]) + goto bad; + } + array = (void **) realloc(table->array, + capacity * sizeof table->array[0]); + if (!array) + goto bad; +#ifdef DEBUG + memset(array + count, JS_FREE_PATTERN, + (capacity - count) * sizeof table->array[0]); +#endif + table->array = array; + } + + table->array[count] = ptr; + table->count = count + 1; + + return JS_TRUE; + + bad: + JS_ReportOutOfMemory(cx); + return JS_FALSE; +} + +static void +ShrinkPtrTable(JSPtrTable *table, const JSPtrTableInfo *info, + size_t newCount) +{ + size_t oldCapacity, capacity; + void **array; + + JS_ASSERT(newCount <= table->count); + if (newCount == table->count) + return; + + oldCapacity = PtrTableCapacity(table->count, info); + table->count = newCount; + capacity = PtrTableCapacity(newCount, info); + + if (oldCapacity != capacity) { + array = table->array; + JS_ASSERT(array); + if (capacity == 0) { + free(array); + table->array = NULL; + return; + } + array = (void **) realloc(array, capacity * sizeof array[0]); + if (array) + table->array = array; + } +#ifdef DEBUG + memset(table->array + newCount, JS_FREE_PATTERN, + (capacity - newCount) * sizeof table->array[0]); +#endif +} + +#ifdef JS_GCMETER +# define METER(x) x +#else +# define METER(x) ((void) 0) +#endif + +static JSBool +NewGCArena(JSRuntime *rt, JSGCArenaList *arenaList) +{ + JSGCArena *a; + jsuword offset; + JSGCPageInfo *pi; + uint32 *bytesptr; + + /* Check if we are allowed and can allocate a new arena. */ + if (rt->gcBytes >= rt->gcMaxBytes) + return JS_FALSE; + a = (JSGCArena *)malloc(GC_ARENA_SIZE); + if (!a) + return JS_FALSE; + + /* Initialize the JSGCPageInfo records at the start of every thing page. */ + offset = (GC_PAGE_SIZE - ((jsuword)a->base & GC_PAGE_MASK)) & GC_PAGE_MASK; + JS_ASSERT((jsuword)a->base + offset == FIRST_THING_PAGE(a)); + do { + pi = (JSGCPageInfo *) (a->base + offset); + pi->offsetInArena = offset; + pi->unscannedBitmap = 0; + offset += GC_PAGE_SIZE; + } while (offset < GC_THINGS_SIZE); + + METER(++arenaList->stats.narenas); + METER(arenaList->stats.maxarenas + = JS_MAX(arenaList->stats.maxarenas, arenaList->stats.narenas)); + + a->list = arenaList; + a->prev = arenaList->last; + a->prevUnscanned = NULL; + a->unscannedPages = 0; + arenaList->last = a; + arenaList->lastLimit = 0; + + bytesptr = (arenaList == &rt->gcArenaList[0]) + ? &rt->gcBytes + : &rt->gcPrivateBytes; + *bytesptr += GC_ARENA_SIZE; + + return JS_TRUE; +} + +static void +DestroyGCArena(JSRuntime *rt, JSGCArenaList *arenaList, JSGCArena **ap) +{ + JSGCArena *a; + uint32 *bytesptr; + + a = *ap; + JS_ASSERT(a); + bytesptr = (arenaList == &rt->gcArenaList[0]) + ? &rt->gcBytes + : &rt->gcPrivateBytes; + JS_ASSERT(*bytesptr >= GC_ARENA_SIZE); + *bytesptr -= GC_ARENA_SIZE; + METER(rt->gcStats.afree++); + METER(--arenaList->stats.narenas); + if (a == arenaList->last) + arenaList->lastLimit = (uint16)(a->prev ? GC_THINGS_SIZE : 0); + *ap = a->prev; + +#ifdef DEBUG + memset(a, JS_FREE_PATTERN, GC_ARENA_SIZE); +#endif + free(a); +} + +static void +InitGCArenaLists(JSRuntime *rt) +{ + uintN i, thingSize; + JSGCArenaList *arenaList; + + for (i = 0; i < GC_NUM_FREELISTS; i++) { + arenaList = &rt->gcArenaList[i]; + thingSize = GC_FREELIST_NBYTES(i); + JS_ASSERT((size_t)(uint16)thingSize == thingSize); + arenaList->last = NULL; + arenaList->lastLimit = 0; + arenaList->thingSize = (uint16)thingSize; + arenaList->freeList = NULL; + METER(memset(&arenaList->stats, 0, sizeof arenaList->stats)); + } +} + +static void +FinishGCArenaLists(JSRuntime *rt) +{ + uintN i; + JSGCArenaList *arenaList; + + for (i = 0; i < GC_NUM_FREELISTS; i++) { + arenaList = &rt->gcArenaList[i]; + while (arenaList->last) + DestroyGCArena(rt, arenaList, &arenaList->last); + arenaList->freeList = NULL; + } +} + +uint8 * +js_GetGCThingFlags(void *thing) +{ + JSGCPageInfo *pi; + jsuword offsetInArena, thingIndex; + + pi = THING_TO_PAGE(thing); + offsetInArena = pi->offsetInArena; + JS_ASSERT(offsetInArena < GC_THINGS_SIZE); + thingIndex = ((offsetInArena & ~GC_PAGE_MASK) | + ((jsuword)thing & GC_PAGE_MASK)) / sizeof(JSGCThing); + JS_ASSERT(thingIndex < GC_PAGE_SIZE); + if (thingIndex >= (offsetInArena & GC_PAGE_MASK)) + thingIndex += GC_THINGS_SIZE; + return (uint8 *)pi - offsetInArena + thingIndex; +} + +JSRuntime* +js_GetGCStringRuntime(JSString *str) +{ + JSGCPageInfo *pi; + JSGCArenaList *list; + + pi = THING_TO_PAGE(str); + list = PAGE_TO_ARENA(pi)->list; + + JS_ASSERT(list->thingSize == sizeof(JSGCThing)); + JS_ASSERT(GC_FREELIST_INDEX(sizeof(JSGCThing)) == 0); + + return (JSRuntime *)((uint8 *)list - offsetof(JSRuntime, gcArenaList)); +} + +JSBool +js_IsAboutToBeFinalized(JSContext *cx, void *thing) +{ + uint8 flags = *js_GetGCThingFlags(thing); + + return !(flags & (GCF_MARK | GCF_LOCK | GCF_FINAL)); +} + +typedef void (*GCFinalizeOp)(JSContext *cx, JSGCThing *thing); + +#ifndef DEBUG +# define js_FinalizeDouble NULL +#endif + +#if !JS_HAS_XML_SUPPORT +# define js_FinalizeXMLNamespace NULL +# define js_FinalizeXMLQName NULL +# define js_FinalizeXML NULL +#endif + +static GCFinalizeOp gc_finalizers[GCX_NTYPES] = { + (GCFinalizeOp) js_FinalizeObject, /* GCX_OBJECT */ + (GCFinalizeOp) js_FinalizeString, /* GCX_STRING */ + (GCFinalizeOp) js_FinalizeDouble, /* GCX_DOUBLE */ + (GCFinalizeOp) js_FinalizeString, /* GCX_MUTABLE_STRING */ + NULL, /* GCX_PRIVATE */ + (GCFinalizeOp) js_FinalizeXMLNamespace, /* GCX_NAMESPACE */ + (GCFinalizeOp) js_FinalizeXMLQName, /* GCX_QNAME */ + (GCFinalizeOp) js_FinalizeXML, /* GCX_XML */ + NULL, /* GCX_EXTERNAL_STRING */ + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL +}; + +#ifdef GC_MARK_DEBUG +static const char newborn_external_string[] = "newborn external string"; + +static const char *gc_typenames[GCX_NTYPES] = { + "newborn object", + "newborn string", + "newborn double", + "newborn mutable string", + "newborn private", + "newborn Namespace", + "newborn QName", + "newborn XML", + newborn_external_string, + newborn_external_string, + newborn_external_string, + newborn_external_string, + newborn_external_string, + newborn_external_string, + newborn_external_string, + newborn_external_string +}; +#endif + +intN +js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop, + JSStringFinalizeOp newop) +{ + uintN i; + + for (i = GCX_EXTERNAL_STRING; i < GCX_NTYPES; i++) { + if (gc_finalizers[i] == (GCFinalizeOp) oldop) { + gc_finalizers[i] = (GCFinalizeOp) newop; + return (intN) i; + } + } + return -1; +} + +/* This is compatible with JSDHashEntryStub. */ +typedef struct JSGCRootHashEntry { + JSDHashEntryHdr hdr; + void *root; + const char *name; +} JSGCRootHashEntry; + +/* Initial size of the gcRootsHash table (SWAG, small enough to amortize). */ +#define GC_ROOTS_SIZE 256 +#define GC_FINALIZE_LEN 1024 + +JSBool +js_InitGC(JSRuntime *rt, uint32 maxbytes) +{ + InitGCArenaLists(rt); + if (!JS_DHashTableInit(&rt->gcRootsHash, JS_DHashGetStubOps(), NULL, + sizeof(JSGCRootHashEntry), GC_ROOTS_SIZE)) { + rt->gcRootsHash.ops = NULL; + return JS_FALSE; + } + rt->gcLocksHash = NULL; /* create lazily */ + + /* + * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes + * for default backward API compatibility. + */ + rt->gcMaxBytes = rt->gcMaxMallocBytes = maxbytes; + + return JS_TRUE; +} + +#ifdef JS_GCMETER +JS_FRIEND_API(void) +js_DumpGCStats(JSRuntime *rt, FILE *fp) +{ + uintN i; + size_t totalThings, totalMaxThings, totalBytes; + + fprintf(fp, "\nGC allocation statistics:\n"); + +#define UL(x) ((unsigned long)(x)) +#define ULSTAT(x) UL(rt->gcStats.x) + totalThings = 0; + totalMaxThings = 0; + totalBytes = 0; + for (i = 0; i < GC_NUM_FREELISTS; i++) { + JSGCArenaList *list = &rt->gcArenaList[i]; + JSGCArenaStats *stats = &list->stats; + if (stats->maxarenas == 0) { + fprintf(fp, "ARENA LIST %u (thing size %lu): NEVER USED\n", + i, UL(GC_FREELIST_NBYTES(i))); + continue; + } + fprintf(fp, "ARENA LIST %u (thing size %lu):\n", + i, UL(GC_FREELIST_NBYTES(i))); + fprintf(fp, " arenas: %lu\n", UL(stats->narenas)); + fprintf(fp, " max arenas: %lu\n", UL(stats->maxarenas)); + fprintf(fp, " things: %lu\n", UL(stats->nthings)); + fprintf(fp, " max things: %lu\n", UL(stats->maxthings)); + fprintf(fp, " free list: %lu\n", UL(stats->freelen)); + fprintf(fp, " free list density: %.1f%%\n", + stats->narenas == 0 + ? 0.0 + : (100.0 * list->thingSize * (jsdouble)stats->freelen / + (GC_THINGS_SIZE * (jsdouble)stats->narenas))); + fprintf(fp, " average free list density: %.1f%%\n", + stats->totalarenas == 0 + ? 0.0 + : (100.0 * list->thingSize * (jsdouble)stats->totalfreelen / + (GC_THINGS_SIZE * (jsdouble)stats->totalarenas))); + fprintf(fp, " recycles: %lu\n", UL(stats->recycle)); + fprintf(fp, " recycle/alloc ratio: %.2f\n", + (jsdouble)stats->recycle / + (jsdouble)(stats->totalnew - stats->recycle)); + totalThings += stats->nthings; + totalMaxThings += stats->maxthings; + totalBytes += GC_FREELIST_NBYTES(i) * stats->nthings; + } + fprintf(fp, "TOTAL STATS:\n"); + fprintf(fp, " public bytes allocated: %lu\n", UL(rt->gcBytes)); + fprintf(fp, " private bytes allocated: %lu\n", UL(rt->gcPrivateBytes)); + fprintf(fp, " alloc attempts: %lu\n", ULSTAT(alloc)); +#ifdef JS_THREADSAFE + fprintf(fp, " alloc without locks: %1u\n", ULSTAT(localalloc)); +#endif + fprintf(fp, " total GC things: %lu\n", UL(totalThings)); + fprintf(fp, " max total GC things: %lu\n", UL(totalMaxThings)); + fprintf(fp, " GC things size: %lu\n", UL(totalBytes)); + fprintf(fp, "allocation retries after GC: %lu\n", ULSTAT(retry)); + fprintf(fp, " allocation failures: %lu\n", ULSTAT(fail)); + fprintf(fp, " things born locked: %lu\n", ULSTAT(lockborn)); + fprintf(fp, " valid lock calls: %lu\n", ULSTAT(lock)); + fprintf(fp, " valid unlock calls: %lu\n", ULSTAT(unlock)); + fprintf(fp, " mark recursion depth: %lu\n", ULSTAT(depth)); + fprintf(fp, " maximum mark recursion: %lu\n", ULSTAT(maxdepth)); + fprintf(fp, " mark C recursion depth: %lu\n", ULSTAT(cdepth)); + fprintf(fp, " maximum mark C recursion: %lu\n", ULSTAT(maxcdepth)); + fprintf(fp, " delayed scan bag adds: %lu\n", ULSTAT(unscanned)); +#ifdef DEBUG + fprintf(fp, " max delayed scan bag size: %lu\n", ULSTAT(maxunscanned)); +#endif + fprintf(fp, " maximum GC nesting level: %lu\n", ULSTAT(maxlevel)); + fprintf(fp, "potentially useful GC calls: %lu\n", ULSTAT(poke)); + fprintf(fp, " useless GC calls: %lu\n", ULSTAT(nopoke)); + fprintf(fp, " thing arenas freed so far: %lu\n", ULSTAT(afree)); + fprintf(fp, " stack segments scanned: %lu\n", ULSTAT(stackseg)); + fprintf(fp, "stack segment slots scanned: %lu\n", ULSTAT(segslots)); + fprintf(fp, "reachable closeable objects: %lu\n", ULSTAT(nclose)); + fprintf(fp, " max reachable closeable: %lu\n", ULSTAT(maxnclose)); + fprintf(fp, " scheduled close hooks: %lu\n", ULSTAT(closelater)); + fprintf(fp, " max scheduled close hooks: %lu\n", ULSTAT(maxcloselater)); +#undef UL +#undef US + +#ifdef JS_ARENAMETER + JS_DumpArenaStats(fp); +#endif +} +#endif + +#ifdef DEBUG +static void +CheckLeakedRoots(JSRuntime *rt); +#endif + +void +js_FinishGC(JSRuntime *rt) +{ +#ifdef JS_ARENAMETER + JS_DumpArenaStats(stdout); +#endif +#ifdef JS_GCMETER + js_DumpGCStats(rt, stdout); +#endif + + FreePtrTable(&rt->gcIteratorTable, &iteratorTableInfo); +#if JS_HAS_GENERATORS + rt->gcCloseState.reachableList = NULL; + METER(rt->gcStats.nclose = 0); + rt->gcCloseState.todoQueue = NULL; +#endif + FinishGCArenaLists(rt); + + if (rt->gcRootsHash.ops) { +#ifdef DEBUG + CheckLeakedRoots(rt); +#endif + JS_DHashTableFinish(&rt->gcRootsHash); + rt->gcRootsHash.ops = NULL; + } + if (rt->gcLocksHash) { + JS_DHashTableDestroy(rt->gcLocksHash); + rt->gcLocksHash = NULL; + } +} + +JSBool +js_AddRoot(JSContext *cx, void *rp, const char *name) +{ + JSBool ok = js_AddRootRT(cx->runtime, rp, name); + if (!ok) + JS_ReportOutOfMemory(cx); + return ok; +} + +JSBool +js_AddRootRT(JSRuntime *rt, void *rp, const char *name) +{ + JSBool ok; + JSGCRootHashEntry *rhe; + + /* + * Due to the long-standing, but now removed, use of rt->gcLock across the + * bulk of js_GC, API users have come to depend on JS_AddRoot etc. locking + * properly with a racing GC, without calling JS_AddRoot from a request. + * We have to preserve API compatibility here, now that we avoid holding + * rt->gcLock across the mark phase (including the root hashtable mark). + * + * If the GC is running and we're called on another thread, wait for this + * GC activation to finish. We can safely wait here (in the case where we + * are called within a request on another thread's context) without fear + * of deadlock because the GC doesn't set rt->gcRunning until after it has + * waited for all active requests to end. + */ + JS_LOCK_GC(rt); +#ifdef JS_THREADSAFE + JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0); + if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) { + do { + JS_AWAIT_GC_DONE(rt); + } while (rt->gcLevel > 0); + } +#endif + rhe = (JSGCRootHashEntry *) JS_DHashTableOperate(&rt->gcRootsHash, rp, + JS_DHASH_ADD); + if (rhe) { + rhe->root = rp; + rhe->name = name; + ok = JS_TRUE; + } else { + ok = JS_FALSE; + } + JS_UNLOCK_GC(rt); + return ok; +} + +JSBool +js_RemoveRoot(JSRuntime *rt, void *rp) +{ + /* + * Due to the JS_RemoveRootRT API, we may be called outside of a request. + * Same synchronization drill as above in js_AddRoot. + */ + JS_LOCK_GC(rt); +#ifdef JS_THREADSAFE + JS_ASSERT(!rt->gcRunning || rt->gcLevel > 0); + if (rt->gcRunning && rt->gcThread->id != js_CurrentThreadId()) { + do { + JS_AWAIT_GC_DONE(rt); + } while (rt->gcLevel > 0); + } +#endif + (void) JS_DHashTableOperate(&rt->gcRootsHash, rp, JS_DHASH_REMOVE); + rt->gcPoke = JS_TRUE; + JS_UNLOCK_GC(rt); + return JS_TRUE; +} + +#ifdef DEBUG + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_root_printer(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 i, void *arg) +{ + uint32 *leakedroots = (uint32 *)arg; + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + + (*leakedroots)++; + fprintf(stderr, + "JS engine warning: leaking GC root \'%s\' at %p\n", + rhe->name ? (char *)rhe->name : "", rhe->root); + + return JS_DHASH_NEXT; +} + +static void +CheckLeakedRoots(JSRuntime *rt) +{ + uint32 leakedroots = 0; + + /* Warn (but don't assert) debug builds of any remaining roots. */ + JS_DHashTableEnumerate(&rt->gcRootsHash, js_root_printer, + &leakedroots); + if (leakedroots > 0) { + if (leakedroots == 1) { + fprintf(stderr, +"JS engine warning: 1 GC root remains after destroying the JSRuntime.\n" +" This root may point to freed memory. Objects reachable\n" +" through it have not been finalized.\n"); + } else { + fprintf(stderr, +"JS engine warning: %lu GC roots remain after destroying the JSRuntime.\n" +" These roots may point to freed memory. Objects reachable\n" +" through them have not been finalized.\n", + (unsigned long) leakedroots); + } + } +} + +typedef struct NamedRootDumpArgs { + void (*dump)(const char *name, void *rp, void *data); + void *data; +} NamedRootDumpArgs; + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_named_root_dumper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, + void *arg) +{ + NamedRootDumpArgs *args = (NamedRootDumpArgs *) arg; + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + + if (rhe->name) + args->dump(rhe->name, rhe->root, args->data); + return JS_DHASH_NEXT; +} + +void +js_DumpNamedRoots(JSRuntime *rt, + void (*dump)(const char *name, void *rp, void *data), + void *data) +{ + NamedRootDumpArgs args; + + args.dump = dump; + args.data = data; + JS_DHashTableEnumerate(&rt->gcRootsHash, js_named_root_dumper, &args); +} + +#endif /* DEBUG */ + +typedef struct GCRootMapArgs { + JSGCRootMapFun map; + void *data; +} GCRootMapArgs; + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +js_gcroot_mapper(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 number, + void *arg) +{ + GCRootMapArgs *args = (GCRootMapArgs *) arg; + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + intN mapflags; + JSDHashOperator op; + + mapflags = args->map(rhe->root, rhe->name, args->data); + +#if JS_MAP_GCROOT_NEXT == JS_DHASH_NEXT && \ + JS_MAP_GCROOT_STOP == JS_DHASH_STOP && \ + JS_MAP_GCROOT_REMOVE == JS_DHASH_REMOVE + op = (JSDHashOperator)mapflags; +#else + op = JS_DHASH_NEXT; + if (mapflags & JS_MAP_GCROOT_STOP) + op |= JS_DHASH_STOP; + if (mapflags & JS_MAP_GCROOT_REMOVE) + op |= JS_DHASH_REMOVE; +#endif + + return op; +} + +uint32 +js_MapGCRoots(JSRuntime *rt, JSGCRootMapFun map, void *data) +{ + GCRootMapArgs args; + uint32 rv; + + args.map = map; + args.data = data; + JS_LOCK_GC(rt); + rv = JS_DHashTableEnumerate(&rt->gcRootsHash, js_gcroot_mapper, &args); + JS_UNLOCK_GC(rt); + return rv; +} + +JSBool +js_RegisterCloseableIterator(JSContext *cx, JSObject *obj) +{ + JSRuntime *rt; + JSBool ok; + + rt = cx->runtime; + JS_ASSERT(!rt->gcRunning); + + JS_LOCK_GC(rt); + ok = AddToPtrTable(cx, &rt->gcIteratorTable, &iteratorTableInfo, obj); + JS_UNLOCK_GC(rt); + return ok; +} + +static void +CloseIteratorStates(JSContext *cx) +{ + JSRuntime *rt; + size_t count, newCount, i; + void **array; + JSObject *obj; + + rt = cx->runtime; + count = rt->gcIteratorTable.count; + array = rt->gcIteratorTable.array; + + newCount = 0; + for (i = 0; i != count; ++i) { + obj = (JSObject *)array[i]; + if (js_IsAboutToBeFinalized(cx, obj)) + js_CloseIteratorState(cx, obj); + else + array[newCount++] = obj; + } + ShrinkPtrTable(&rt->gcIteratorTable, &iteratorTableInfo, newCount); +} + +#if JS_HAS_GENERATORS + +void +js_RegisterGenerator(JSContext *cx, JSGenerator *gen) +{ + JSRuntime *rt; + + rt = cx->runtime; + JS_ASSERT(!rt->gcRunning); + JS_ASSERT(rt->state != JSRTS_LANDING); + JS_ASSERT(gen->state == JSGEN_NEWBORN); + + JS_LOCK_GC(rt); + gen->next = rt->gcCloseState.reachableList; + rt->gcCloseState.reachableList = gen; + METER(rt->gcStats.nclose++); + METER(rt->gcStats.maxnclose = JS_MAX(rt->gcStats.maxnclose, + rt->gcStats.nclose)); + JS_UNLOCK_GC(rt); +} + +/* + * We do not run close hooks when the parent scope of the generator instance + * becomes unreachable to prevent denial-of-service and resource leakage from + * misbehaved generators. + * + * Called from the GC. + */ +static JSBool +CanScheduleCloseHook(JSGenerator *gen) +{ + JSObject *parent; + JSBool canSchedule; + + /* Avoid OBJ_GET_PARENT overhead as we are in GC. */ + parent = JSVAL_TO_OBJECT(gen->obj->slots[JSSLOT_PARENT]); + canSchedule = *js_GetGCThingFlags(parent) & GCF_MARK; +#ifdef DEBUG_igor + if (!canSchedule) { + fprintf(stderr, "GEN: Kill without schedule, gen=%p parent=%p\n", + (void *)gen, (void *)parent); + } +#endif + return canSchedule; +} + +/* + * Check if we should delay execution of the close hook. + * + * Called outside GC or any locks. + * + * XXX The current implementation is a hack that embeds the knowledge of the + * browser embedding pending the resolution of bug 352788. In the browser we + * must not close any generators that came from a page that is currently in + * the browser history. We detect that using the fact in the browser the scope + * is history if scope->outerObject->innerObject != scope. + */ +static JSBool +ShouldDeferCloseHook(JSContext *cx, JSGenerator *gen, JSBool *defer) +{ + JSObject *parent, *obj; + JSClass *clasp; + JSExtendedClass *xclasp; + + /* + * This is called outside any locks, so use thread-safe macros to access + * parent and classes. + */ + *defer = JS_FALSE; + parent = OBJ_GET_PARENT(cx, gen->obj); + clasp = OBJ_GET_CLASS(cx, parent); + if (clasp->flags & JSCLASS_IS_EXTENDED) { + xclasp = (JSExtendedClass *)clasp; + if (xclasp->outerObject) { + obj = xclasp->outerObject(cx, parent); + if (!obj) + return JS_FALSE; + OBJ_TO_INNER_OBJECT(cx, obj); + if (!obj) + return JS_FALSE; + *defer = obj != parent; + } + } +#ifdef DEBUG_igor + if (*defer) { + fprintf(stderr, "GEN: deferring, gen=%p parent=%p\n", + (void *)gen, (void *)parent); + } +#endif + return JS_TRUE; +} + +/* + * Find all unreachable generators and move them to the todo queue from + * rt->gcCloseState.reachableList to execute thier close hooks after the GC + * cycle completes. To ensure liveness during the sweep phase we mark all + * generators we are going to close later. + */ +static void +FindAndMarkObjectsToClose(JSContext *cx, JSGCInvocationKind gckind, + JSGenerator **todoQueueTail) +{ + JSRuntime *rt; + JSGenerator *todo, **genp, *gen; + + rt = cx->runtime; + todo = NULL; + genp = &rt->gcCloseState.reachableList; + while ((gen = *genp) != NULL) { + if (*js_GetGCThingFlags(gen->obj) & GCF_MARK) { + genp = &gen->next; + } else { + /* Generator must not be executing when it becomes unreachable. */ + JS_ASSERT(gen->state == JSGEN_NEWBORN || + gen->state == JSGEN_OPEN || + gen->state == JSGEN_CLOSED); + + *genp = gen->next; + if (gen->state == JSGEN_OPEN && + js_FindFinallyHandler(gen->frame.script, gen->frame.pc) && + CanScheduleCloseHook(gen)) { + /* + * Generator yielded inside a try with a finally block. + * Schedule it for closing. + * + * We keep generators that yielded outside try-with-finally + * with gen->state == JSGEN_OPEN. The finalizer must deal with + * open generators as we may skip the close hooks, see below. + */ + gen->next = NULL; + *todoQueueTail = gen; + todoQueueTail = &gen->next; + if (!todo) + todo = gen; + METER(JS_ASSERT(rt->gcStats.nclose)); + METER(rt->gcStats.nclose--); + METER(rt->gcStats.closelater++); + METER(rt->gcStats.maxcloselater + = JS_MAX(rt->gcStats.maxcloselater, + rt->gcStats.closelater)); + } + } + } + + if (gckind == GC_LAST_CONTEXT) { + /* + * Remove scheduled hooks on shutdown as it is too late to run them: + * we do not allow execution of arbitrary scripts at this point. + */ + rt->gcCloseState.todoQueue = NULL; + } else { + /* + * Mark just-found unreachable generators *after* we scan the global + * list to prevent a generator that refers to other unreachable + * generators from keeping them on gcCloseState.reachableList. + */ + for (gen = todo; gen; gen = gen->next) + GC_MARK(cx, gen->obj, "newly scheduled generator"); + } +} + +/* + * Mark unreachable generators already scheduled to close and return the tail + * pointer to JSGCCloseState.todoQueue. + */ +static JSGenerator ** +MarkScheduledGenerators(JSContext *cx) +{ + JSRuntime *rt; + JSGenerator **genp, *gen; + + rt = cx->runtime; + genp = &rt->gcCloseState.todoQueue; + while ((gen = *genp) != NULL) { + if (CanScheduleCloseHook(gen)) { + GC_MARK(cx, gen->obj, "scheduled generator"); + genp = &gen->next; + } else { + /* Discard the generator from the list if its schedule is over. */ + *genp = gen->next; + METER(JS_ASSERT(rt->gcStats.closelater > 0)); + METER(rt->gcStats.closelater--); + } + } + return genp; +} + +#ifdef JS_THREADSAFE +# define GC_RUNNING_CLOSE_HOOKS_PTR(cx) \ + (&(cx)->thread->gcRunningCloseHooks) +#else +# define GC_RUNNING_CLOSE_HOOKS_PTR(cx) \ + (&(cx)->runtime->gcCloseState.runningCloseHook) +#endif + +typedef struct JSTempCloseList { + JSTempValueRooter tvr; + JSGenerator *head; +} JSTempCloseList; + +JS_STATIC_DLL_CALLBACK(void) +mark_temp_close_list(JSContext *cx, JSTempValueRooter *tvr) +{ + JSTempCloseList *list = (JSTempCloseList *)tvr; + JSGenerator *gen; + + for (gen = list->head; gen; gen = gen->next) + GC_MARK(cx, gen->obj, "temp list generator"); +} + +#define JS_PUSH_TEMP_CLOSE_LIST(cx, tempList) \ + JS_PUSH_TEMP_ROOT_MARKER(cx, mark_temp_close_list, &(tempList)->tvr) + +#define JS_POP_TEMP_CLOSE_LIST(cx, tempList) \ + JS_BEGIN_MACRO \ + JS_ASSERT((tempList)->tvr.u.marker == mark_temp_close_list); \ + JS_POP_TEMP_ROOT(cx, &(tempList)->tvr); \ + JS_END_MACRO + +JSBool +js_RunCloseHooks(JSContext *cx) +{ + JSRuntime *rt; + JSTempCloseList tempList; + JSStackFrame *fp; + JSGenerator **genp, *gen; + JSBool ok, defer; +#if JS_GCMETER + uint32 deferCount; +#endif + + rt = cx->runtime; + + /* + * It is OK to access todoQueue outside the lock here. When many threads + * update the todo list, accessing some older value of todoQueue in the + * worst case just delays the excution of close hooks. + */ + if (!rt->gcCloseState.todoQueue) + return JS_TRUE; + + /* + * To prevent an infinite loop when a close hook creats more objects with + * close hooks and then triggers GC we ignore recursive invocations of + * js_RunCloseHooks and limit number of hooks to execute to the initial + * size of the list. + */ + if (*GC_RUNNING_CLOSE_HOOKS_PTR(cx)) + return JS_TRUE; + + *GC_RUNNING_CLOSE_HOOKS_PTR(cx) = JS_TRUE; + + JS_LOCK_GC(rt); + tempList.head = rt->gcCloseState.todoQueue; + JS_PUSH_TEMP_CLOSE_LIST(cx, &tempList); + rt->gcCloseState.todoQueue = NULL; + METER(rt->gcStats.closelater = 0); + rt->gcPoke = JS_TRUE; + JS_UNLOCK_GC(rt); + + /* + * Set aside cx->fp since we do not want a close hook using caller or + * other means to backtrace into whatever stack might be active when + * running the hook. We store the current frame on the dormant list to + * protect against GC that the hook can trigger. + */ + fp = cx->fp; + if (fp) { + JS_ASSERT(!fp->dormantNext); + fp->dormantNext = cx->dormantFrameChain; + cx->dormantFrameChain = fp; + } + cx->fp = NULL; + + genp = &tempList.head; + ok = JS_TRUE; + while ((gen = *genp) != NULL) { + ok = ShouldDeferCloseHook(cx, gen, &defer); + if (!ok) { + /* Quit ASAP discarding the hook. */ + *genp = gen->next; + break; + } + if (defer) { + genp = &gen->next; + METER(deferCount++); + continue; + } + ok = js_CloseGeneratorObject(cx, gen); + + /* + * Unlink the generator after closing it to make sure it always stays + * rooted through tempList. + */ + *genp = gen->next; + + if (cx->throwing) { + /* + * Report the exception thrown by the close hook and continue to + * execute the rest of the hooks. + */ + if (!js_ReportUncaughtException(cx)) + JS_ClearPendingException(cx); + ok = JS_TRUE; + } else if (!ok) { + /* + * Assume this is a stop signal from the branch callback or + * other quit ASAP condition. Break execution until the next + * invocation of js_RunCloseHooks. + */ + break; + } + } + + cx->fp = fp; + if (fp) { + JS_ASSERT(cx->dormantFrameChain == fp); + cx->dormantFrameChain = fp->dormantNext; + fp->dormantNext = NULL; + } + + if (tempList.head) { + /* + * Some close hooks were not yet executed, put them back into the + * scheduled list. + */ + while ((gen = *genp) != NULL) { + genp = &gen->next; + METER(deferCount++); + } + + /* Now genp is a pointer to the tail of tempList. */ + JS_LOCK_GC(rt); + *genp = rt->gcCloseState.todoQueue; + rt->gcCloseState.todoQueue = tempList.head; + METER(rt->gcStats.closelater += deferCount); + METER(rt->gcStats.maxcloselater + = JS_MAX(rt->gcStats.maxcloselater, rt->gcStats.closelater)); + JS_UNLOCK_GC(rt); + } + + JS_POP_TEMP_CLOSE_LIST(cx, &tempList); + *GC_RUNNING_CLOSE_HOOKS_PTR(cx) = JS_FALSE; + + return ok; +} + +#endif /* JS_HAS_GENERATORS */ + +#if defined(DEBUG_brendan) || defined(DEBUG_timeless) +#define DEBUG_gchist +#endif + +#ifdef DEBUG_gchist +#define NGCHIST 64 + +static struct GCHist { + JSBool lastDitch; + JSGCThing *freeList; +} gchist[NGCHIST]; + +unsigned gchpos; +#endif + +void * +js_NewGCThing(JSContext *cx, uintN flags, size_t nbytes) +{ + JSRuntime *rt; + uintN flindex; + JSBool doGC; + JSGCThing *thing; + uint8 *flagp, *firstPage; + JSGCArenaList *arenaList; + jsuword offset; + JSGCArena *a; + JSLocalRootStack *lrs; +#ifdef JS_THREADSAFE + JSBool gcLocked; + uintN localMallocBytes; + JSGCThing **flbase, **lastptr; + JSGCThing *tmpthing; + uint8 *tmpflagp; + uintN maxFreeThings; /* max to take from the global free list */ + METER(size_t nfree); +#endif + + rt = cx->runtime; + METER(rt->gcStats.alloc++); /* this is not thread-safe */ + nbytes = JS_ROUNDUP(nbytes, sizeof(JSGCThing)); + flindex = GC_FREELIST_INDEX(nbytes); + +#ifdef JS_THREADSAFE + gcLocked = JS_FALSE; + JS_ASSERT(cx->thread); + flbase = cx->thread->gcFreeLists; + JS_ASSERT(flbase); + thing = flbase[flindex]; + localMallocBytes = cx->thread->gcMallocBytes; + if (thing && rt->gcMaxMallocBytes - rt->gcMallocBytes > localMallocBytes) { + flagp = thing->flagp; + flbase[flindex] = thing->next; + METER(rt->gcStats.localalloc++); /* this is not thread-safe */ + goto success; + } + + JS_LOCK_GC(rt); + gcLocked = JS_TRUE; + + /* Transfer thread-local counter to global one. */ + if (localMallocBytes != 0) { + cx->thread->gcMallocBytes = 0; + if (rt->gcMaxMallocBytes - rt->gcMallocBytes < localMallocBytes) + rt->gcMallocBytes = rt->gcMaxMallocBytes; + else + rt->gcMallocBytes += localMallocBytes; + } +#endif + JS_ASSERT(!rt->gcRunning); + if (rt->gcRunning) { + METER(rt->gcStats.finalfail++); + JS_UNLOCK_GC(rt); + return NULL; + } + +#ifdef TOO_MUCH_GC +#ifdef WAY_TOO_MUCH_GC + rt->gcPoke = JS_TRUE; +#endif + doGC = JS_TRUE; +#else + doGC = (rt->gcMallocBytes >= rt->gcMaxMallocBytes); +#endif + + arenaList = &rt->gcArenaList[flindex]; + for (;;) { + if (doGC) { + /* + * Keep rt->gcLock across the call into js_GC so we don't starve + * and lose to racing threads who deplete the heap just after + * js_GC has replenished it (or has synchronized with a racing + * GC that collected a bunch of garbage). This unfair scheduling + * can happen on certain operating systems. For the gory details, + * see bug 162779 at https://bugzilla.mozilla.org/. + */ + js_GC(cx, GC_LAST_DITCH); + METER(rt->gcStats.retry++); + } + + /* Try to get thing from the free list. */ + thing = arenaList->freeList; + if (thing) { + arenaList->freeList = thing->next; + flagp = thing->flagp; + JS_ASSERT(*flagp & GCF_FINAL); + METER(arenaList->stats.freelen--); + METER(arenaList->stats.recycle++); + +#ifdef JS_THREADSAFE + /* + * Refill the local free list by taking several things from the + * global free list unless we are still at rt->gcMaxMallocBytes + * barrier or the free list is already populated. The former + * happens when GC is canceled due to !gcCallback(cx, JSGC_BEGIN) + * or no gcPoke. The latter is caused via allocating new things + * in gcCallback(cx, JSGC_END). + */ + if (rt->gcMallocBytes >= rt->gcMaxMallocBytes || flbase[flindex]) + break; + tmpthing = arenaList->freeList; + if (tmpthing) { + maxFreeThings = MAX_THREAD_LOCAL_THINGS; + do { + if (!tmpthing->next) + break; + tmpthing = tmpthing->next; + } while (--maxFreeThings != 0); + + flbase[flindex] = arenaList->freeList; + arenaList->freeList = tmpthing->next; + tmpthing->next = NULL; + } +#endif + break; + } + + /* Allocate from the tail of last arena or from new arena if we can. */ + if ((arenaList->last && arenaList->lastLimit != GC_THINGS_SIZE) || + NewGCArena(rt, arenaList)) { + + offset = arenaList->lastLimit; + if ((offset & GC_PAGE_MASK) == 0) { + /* + * Skip JSGCPageInfo record located at GC_PAGE_SIZE boundary. + */ + offset += PAGE_THING_GAP(nbytes); + } + JS_ASSERT(offset + nbytes <= GC_THINGS_SIZE); + arenaList->lastLimit = (uint16)(offset + nbytes); + a = arenaList->last; + firstPage = (uint8 *)FIRST_THING_PAGE(a); + thing = (JSGCThing *)(firstPage + offset); + flagp = a->base + offset / sizeof(JSGCThing); + if (flagp >= firstPage) + flagp += GC_THINGS_SIZE; + METER(++arenaList->stats.nthings); + METER(arenaList->stats.maxthings = + JS_MAX(arenaList->stats.nthings, + arenaList->stats.maxthings)); + +#ifdef JS_THREADSAFE + /* + * Refill the local free list by taking free things from the last + * arena. Prefer to order free things by ascending address in the + * (unscientific) hope of better cache locality. + */ + if (rt->gcMallocBytes >= rt->gcMaxMallocBytes || flbase[flindex]) + break; + METER(nfree = 0); + lastptr = &flbase[flindex]; + maxFreeThings = MAX_THREAD_LOCAL_THINGS; + for (offset = arenaList->lastLimit; + offset != GC_THINGS_SIZE && maxFreeThings-- != 0; + offset += nbytes) { + if ((offset & GC_PAGE_MASK) == 0) + offset += PAGE_THING_GAP(nbytes); + JS_ASSERT(offset + nbytes <= GC_THINGS_SIZE); + tmpflagp = a->base + offset / sizeof(JSGCThing); + if (tmpflagp >= firstPage) + tmpflagp += GC_THINGS_SIZE; + + tmpthing = (JSGCThing *)(firstPage + offset); + tmpthing->flagp = tmpflagp; + *tmpflagp = GCF_FINAL; /* signifying that thing is free */ + + *lastptr = tmpthing; + lastptr = &tmpthing->next; + METER(++nfree); + } + arenaList->lastLimit = offset; + *lastptr = NULL; + METER(arenaList->stats.freelen += nfree); +#endif + break; + } + + /* Consider doing a "last ditch" GC unless already tried. */ + if (doGC) + goto fail; + rt->gcPoke = JS_TRUE; + doGC = JS_TRUE; + } + + /* We successfully allocated the thing. */ +#ifdef JS_THREADSAFE + success: +#endif + lrs = cx->localRootStack; + if (lrs) { + /* + * If we're in a local root scope, don't set newborn[type] at all, to + * avoid entraining garbage from it for an unbounded amount of time + * on this context. A caller will leave the local root scope and pop + * this reference, allowing thing to be GC'd if it has no other refs. + * See JS_EnterLocalRootScope and related APIs. + */ + if (js_PushLocalRoot(cx, lrs, (jsval) thing) < 0) { + /* + * When we fail for a thing allocated through the tail of the last + * arena, thing's flag byte is not initialized. So to prevent GC + * accessing the uninitialized flags during the finalization, we + * always mark the thing as final. See bug 337407. + */ + *flagp = GCF_FINAL; + goto fail; + } + } else { + /* + * No local root scope, so we're stuck with the old, fragile model of + * depending on a pigeon-hole newborn per type per context. + */ + cx->weakRoots.newborn[flags & GCF_TYPEMASK] = thing; + } + + /* We can't fail now, so update flags and rt->gc{,Private}Bytes. */ + *flagp = (uint8)flags; + + /* + * Clear thing before unlocking in case a GC run is about to scan it, + * finding it via newborn[]. + */ + thing->next = NULL; + thing->flagp = NULL; +#ifdef DEBUG_gchist + gchist[gchpos].lastDitch = doGC; + gchist[gchpos].freeList = rt->gcArenaList[flindex].freeList; + if (++gchpos == NGCHIST) + gchpos = 0; +#endif + METER(if (flags & GCF_LOCK) rt->gcStats.lockborn++); + METER(++rt->gcArenaList[flindex].stats.totalnew); +#ifdef JS_THREADSAFE + if (gcLocked) + JS_UNLOCK_GC(rt); +#endif + return thing; + +fail: +#ifdef JS_THREADSAFE + if (gcLocked) + JS_UNLOCK_GC(rt); +#endif + METER(rt->gcStats.fail++); + JS_ReportOutOfMemory(cx); + return NULL; +} + +JSBool +js_LockGCThing(JSContext *cx, void *thing) +{ + JSBool ok = js_LockGCThingRT(cx->runtime, thing); + if (!ok) + JS_ReportOutOfMemory(cx); + return ok; +} + +/* + * Deep GC-things can't be locked just by setting the GCF_LOCK bit, because + * their descendants must be marked by the GC. To find them during the mark + * phase, they are added to rt->gcLocksHash, which is created lazily. + * + * NB: we depend on the order of GC-thing type indexes here! + */ +#define GC_TYPE_IS_STRING(t) ((t) == GCX_STRING || \ + (t) >= GCX_EXTERNAL_STRING) +#define GC_TYPE_IS_XML(t) ((unsigned)((t) - GCX_NAMESPACE) <= \ + (unsigned)(GCX_XML - GCX_NAMESPACE)) +#define GC_TYPE_IS_DEEP(t) ((t) == GCX_OBJECT || GC_TYPE_IS_XML(t)) + +#define IS_DEEP_STRING(t,o) (GC_TYPE_IS_STRING(t) && \ + JSSTRING_IS_DEPENDENT((JSString *)(o))) + +#define GC_THING_IS_DEEP(t,o) (GC_TYPE_IS_DEEP(t) || IS_DEEP_STRING(t, o)) + +/* This is compatible with JSDHashEntryStub. */ +typedef struct JSGCLockHashEntry { + JSDHashEntryHdr hdr; + const JSGCThing *thing; + uint32 count; +} JSGCLockHashEntry; + +JSBool +js_LockGCThingRT(JSRuntime *rt, void *thing) +{ + JSBool ok, deep; + uint8 *flagp; + uintN flags, lock, type; + JSGCLockHashEntry *lhe; + + ok = JS_TRUE; + if (!thing) + return ok; + + flagp = js_GetGCThingFlags(thing); + + JS_LOCK_GC(rt); + flags = *flagp; + lock = (flags & GCF_LOCK); + type = (flags & GCF_TYPEMASK); + deep = GC_THING_IS_DEEP(type, thing); + + /* + * Avoid adding a rt->gcLocksHash entry for shallow things until someone + * nests a lock -- then start such an entry with a count of 2, not 1. + */ + if (lock || deep) { + if (!rt->gcLocksHash) { + rt->gcLocksHash = + JS_NewDHashTable(JS_DHashGetStubOps(), NULL, + sizeof(JSGCLockHashEntry), + GC_ROOTS_SIZE); + if (!rt->gcLocksHash) { + ok = JS_FALSE; + goto done; + } + } else if (lock == 0) { +#ifdef DEBUG + JSDHashEntryHdr *hdr = + JS_DHashTableOperate(rt->gcLocksHash, thing, + JS_DHASH_LOOKUP); + JS_ASSERT(JS_DHASH_ENTRY_IS_FREE(hdr)); +#endif + } + + lhe = (JSGCLockHashEntry *) + JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_ADD); + if (!lhe) { + ok = JS_FALSE; + goto done; + } + if (!lhe->thing) { + lhe->thing = thing; + lhe->count = deep ? 1 : 2; + } else { + JS_ASSERT(lhe->count >= 1); + lhe->count++; + } + } + + *flagp = (uint8)(flags | GCF_LOCK); + METER(rt->gcStats.lock++); + ok = JS_TRUE; +done: + JS_UNLOCK_GC(rt); + return ok; +} + +JSBool +js_UnlockGCThingRT(JSRuntime *rt, void *thing) +{ + uint8 *flagp, flags; + JSGCLockHashEntry *lhe; + + if (!thing) + return JS_TRUE; + + flagp = js_GetGCThingFlags(thing); + JS_LOCK_GC(rt); + flags = *flagp; + + if (flags & GCF_LOCK) { + if (!rt->gcLocksHash || + (lhe = (JSGCLockHashEntry *) + JS_DHashTableOperate(rt->gcLocksHash, thing, + JS_DHASH_LOOKUP), + JS_DHASH_ENTRY_IS_FREE(&lhe->hdr))) { + /* Shallow GC-thing with an implicit lock count of 1. */ + JS_ASSERT(!GC_THING_IS_DEEP(flags & GCF_TYPEMASK, thing)); + } else { + /* Basis or nested unlock of a deep thing, or nested of shallow. */ + if (--lhe->count != 0) + goto out; + JS_DHashTableOperate(rt->gcLocksHash, thing, JS_DHASH_REMOVE); + } + *flagp = (uint8)(flags & ~GCF_LOCK); + } + + rt->gcPoke = JS_TRUE; +out: + METER(rt->gcStats.unlock++); + JS_UNLOCK_GC(rt); + return JS_TRUE; +} + +#ifdef GC_MARK_DEBUG + +#include <stdio.h> +#include "jsprf.h" + +typedef struct GCMarkNode GCMarkNode; + +struct GCMarkNode { + void *thing; + const char *name; + GCMarkNode *next; + GCMarkNode *prev; +}; + +JS_FRIEND_DATA(FILE *) js_DumpGCHeap; +JS_EXPORT_DATA(void *) js_LiveThingToFind; + +#ifdef HAVE_XPCONNECT +#include "dump_xpc.h" +#endif + +static void +GetObjSlotName(JSScope *scope, JSObject *obj, uint32 slot, char *buf, + size_t bufsize) +{ + jsval nval; + JSScopeProperty *sprop; + JSClass *clasp; + uint32 key; + const char *slotname; + + if (!scope) { + JS_snprintf(buf, bufsize, "**UNKNOWN OBJECT MAP ENTRY**"); + return; + } + + sprop = SCOPE_LAST_PROP(scope); + while (sprop && sprop->slot != slot) + sprop = sprop->parent; + + if (!sprop) { + switch (slot) { + case JSSLOT_PROTO: + JS_snprintf(buf, bufsize, "__proto__"); + break; + case JSSLOT_PARENT: + JS_snprintf(buf, bufsize, "__parent__"); + break; + default: + slotname = NULL; + clasp = LOCKED_OBJ_GET_CLASS(obj); + if (clasp->flags & JSCLASS_IS_GLOBAL) { + key = slot - JSSLOT_START(clasp); +#define JS_PROTO(name,code,init) \ + if ((code) == key) { slotname = js_##name##_str; goto found; } +#include "jsproto.tbl" +#undef JS_PROTO + } + found: + if (slotname) + JS_snprintf(buf, bufsize, "CLASS_OBJECT(%s)", slotname); + else + JS_snprintf(buf, bufsize, "**UNKNOWN SLOT %ld**", (long)slot); + break; + } + } else { + nval = ID_TO_VALUE(sprop->id); + if (JSVAL_IS_INT(nval)) { + JS_snprintf(buf, bufsize, "%ld", (long)JSVAL_TO_INT(nval)); + } else if (JSVAL_IS_STRING(nval)) { + JS_snprintf(buf, bufsize, "%s", + JS_GetStringBytes(JSVAL_TO_STRING(nval))); + } else { + JS_snprintf(buf, bufsize, "**FINALIZED ATOM KEY**"); + } + } +} + +static const char * +gc_object_class_name(void* thing) +{ + uint8 *flagp = js_GetGCThingFlags(thing); + const char *className = ""; + static char depbuf[32]; + + switch (*flagp & GCF_TYPEMASK) { + case GCX_OBJECT: { + JSObject *obj = (JSObject *)thing; + JSClass *clasp = JSVAL_TO_PRIVATE(obj->slots[JSSLOT_CLASS]); + className = clasp->name; +#ifdef HAVE_XPCONNECT + if (clasp->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS) { + jsval privateValue = obj->slots[JSSLOT_PRIVATE]; + + JS_ASSERT(clasp->flags & JSCLASS_HAS_PRIVATE); + if (!JSVAL_IS_VOID(privateValue)) { + void *privateThing = JSVAL_TO_PRIVATE(privateValue); + const char *xpcClassName = GetXPCObjectClassName(privateThing); + + if (xpcClassName) + className = xpcClassName; + } + } +#endif + break; + } + + case GCX_STRING: + case GCX_MUTABLE_STRING: { + JSString *str = (JSString *)thing; + if (JSSTRING_IS_DEPENDENT(str)) { + JS_snprintf(depbuf, sizeof depbuf, "start:%u, length:%u", + JSSTRDEP_START(str), JSSTRDEP_LENGTH(str)); + className = depbuf; + } else { + className = "string"; + } + break; + } + + case GCX_DOUBLE: + className = "double"; + break; + } + + return className; +} + +static void +gc_dump_thing(JSContext *cx, JSGCThing *thing, FILE *fp) +{ + GCMarkNode *prev = (GCMarkNode *)cx->gcCurrentMarkNode; + GCMarkNode *next = NULL; + char *path = NULL; + + while (prev) { + next = prev; + prev = prev->prev; + } + while (next) { + uint8 nextFlags = *js_GetGCThingFlags(next->thing); + if ((nextFlags & GCF_TYPEMASK) == GCX_OBJECT) { + path = JS_sprintf_append(path, "%s(%s @ 0x%08p).", + next->name, + gc_object_class_name(next->thing), + (JSObject*)next->thing); + } else { + path = JS_sprintf_append(path, "%s(%s).", + next->name, + gc_object_class_name(next->thing)); + } + next = next->next; + } + if (!path) + return; + + fprintf(fp, "%08lx ", (long)thing); + switch (*js_GetGCThingFlags(thing) & GCF_TYPEMASK) { + case GCX_OBJECT: + { + JSObject *obj = (JSObject *)thing; + jsval privateValue = obj->slots[JSSLOT_PRIVATE]; + void *privateThing = JSVAL_IS_VOID(privateValue) + ? NULL + : JSVAL_TO_PRIVATE(privateValue); + const char *className = gc_object_class_name(thing); + fprintf(fp, "object %8p %s", privateThing, className); + break; + } +#if JS_HAS_XML_SUPPORT + case GCX_NAMESPACE: + { + JSXMLNamespace *ns = (JSXMLNamespace *)thing; + fprintf(fp, "namespace %s:%s", + JS_GetStringBytes(ns->prefix), JS_GetStringBytes(ns->uri)); + break; + } + case GCX_QNAME: + { + JSXMLQName *qn = (JSXMLQName *)thing; + fprintf(fp, "qname %s(%s):%s", + JS_GetStringBytes(qn->prefix), JS_GetStringBytes(qn->uri), + JS_GetStringBytes(qn->localName)); + break; + } + case GCX_XML: + { + extern const char *js_xml_class_str[]; + JSXML *xml = (JSXML *)thing; + fprintf(fp, "xml %8p %s", xml, js_xml_class_str[xml->xml_class]); + break; + } +#endif + case GCX_DOUBLE: + fprintf(fp, "double %g", *(jsdouble *)thing); + break; + case GCX_PRIVATE: + fprintf(fp, "private %8p", (void *)thing); + break; + default: + fprintf(fp, "string %s", JS_GetStringBytes((JSString *)thing)); + break; + } + fprintf(fp, " via %s\n", path); + free(path); +} + +void +js_MarkNamedGCThing(JSContext *cx, void *thing, const char *name) +{ + GCMarkNode markNode; + + if (!thing) + return; + + markNode.thing = thing; + markNode.name = name; + markNode.next = NULL; + markNode.prev = (GCMarkNode *)cx->gcCurrentMarkNode; + if (markNode.prev) + markNode.prev->next = &markNode; + cx->gcCurrentMarkNode = &markNode; + + if (thing == js_LiveThingToFind) { + /* + * Dump js_LiveThingToFind each time we reach it during the marking + * phase of GC to print all live references to the thing. + */ + gc_dump_thing(cx, thing, stderr); + } + + js_MarkGCThing(cx, thing); + + if (markNode.prev) + markNode.prev->next = NULL; + cx->gcCurrentMarkNode = markNode.prev; +} + +#endif /* !GC_MARK_DEBUG */ + +static void +gc_mark_atom_key_thing(void *thing, void *arg) +{ + JSContext *cx = (JSContext *) arg; + + GC_MARK(cx, thing, "atom"); +} + +void +js_MarkAtom(JSContext *cx, JSAtom *atom) +{ + jsval key; + + if (atom->flags & ATOM_MARK) + return; + atom->flags |= ATOM_MARK; + key = ATOM_KEY(atom); + if (JSVAL_IS_GCTHING(key)) { +#ifdef GC_MARK_DEBUG + char name[32]; + + if (JSVAL_IS_STRING(key)) { + JS_snprintf(name, sizeof name, "'%s'", + JS_GetStringBytes(JSVAL_TO_STRING(key))); + } else { + JS_snprintf(name, sizeof name, "<%x>", key); + } +#endif + GC_MARK(cx, JSVAL_TO_GCTHING(key), name); + } + if (atom->flags & ATOM_HIDDEN) + js_MarkAtom(cx, atom->entry.value); +} + +static void +AddThingToUnscannedBag(JSRuntime *rt, void *thing, uint8 *flagp); + +static void +MarkGCThingChildren(JSContext *cx, void *thing, uint8 *flagp, + JSBool shouldCheckRecursion) +{ + JSRuntime *rt; + JSObject *obj; + jsval v, *vp, *end; + void *next_thing; + uint8 *next_flagp; + JSString *str; +#ifdef JS_GCMETER + uint32 tailCallNesting; +#endif +#ifdef GC_MARK_DEBUG + JSScope *scope; + char name[32]; +#endif + + /* + * With JS_GC_ASSUME_LOW_C_STACK defined the mark phase of GC always + * uses the non-recursive code that otherwise would be called only on + * a low C stack condition. + */ +#ifdef JS_GC_ASSUME_LOW_C_STACK +# define RECURSION_TOO_DEEP() shouldCheckRecursion +#else + int stackDummy; +# define RECURSION_TOO_DEEP() (shouldCheckRecursion && \ + !JS_CHECK_STACK_SIZE(cx, stackDummy)) +#endif + + rt = cx->runtime; + METER(tailCallNesting = 0); + METER(if (++rt->gcStats.cdepth > rt->gcStats.maxcdepth) + rt->gcStats.maxcdepth = rt->gcStats.cdepth); + +#ifndef GC_MARK_DEBUG + start: +#endif + JS_ASSERT(flagp); + JS_ASSERT(*flagp & GCF_MARK); /* the caller must already mark the thing */ + METER(if (++rt->gcStats.depth > rt->gcStats.maxdepth) + rt->gcStats.maxdepth = rt->gcStats.depth); +#ifdef GC_MARK_DEBUG + if (js_DumpGCHeap) + gc_dump_thing(cx, thing, js_DumpGCHeap); +#endif + + switch (*flagp & GCF_TYPEMASK) { + case GCX_OBJECT: + if (RECURSION_TOO_DEEP()) + goto add_to_unscanned_bag; + /* If obj->slots is null, obj must be a newborn. */ + obj = (JSObject *) thing; + vp = obj->slots; + if (!vp) + break; + + /* Mark slots if they are small enough to be GC-allocated. */ + if ((vp[-1] + 1) * sizeof(jsval) <= GC_NBYTES_MAX) + GC_MARK(cx, vp - 1, "slots"); + + /* Set up local variables to loop over unmarked things. */ + end = vp + ((obj->map->ops->mark) + ? obj->map->ops->mark(cx, obj, NULL) + : JS_MIN(obj->map->freeslot, obj->map->nslots)); + thing = NULL; + flagp = NULL; +#ifdef GC_MARK_DEBUG + scope = OBJ_IS_NATIVE(obj) ? OBJ_SCOPE(obj) : NULL; +#endif + for (; vp != end; ++vp) { + v = *vp; + if (!JSVAL_IS_GCTHING(v) || v == JSVAL_NULL) + continue; + next_thing = JSVAL_TO_GCTHING(v); + if (next_thing == thing) + continue; + next_flagp = js_GetGCThingFlags(next_thing); + if (*next_flagp & GCF_MARK) + continue; + JS_ASSERT(*next_flagp != GCF_FINAL); + if (thing) { +#ifdef GC_MARK_DEBUG + GC_MARK(cx, thing, name); +#else + *flagp |= GCF_MARK; + MarkGCThingChildren(cx, thing, flagp, JS_TRUE); +#endif + if (*next_flagp & GCF_MARK) { + /* + * This happens when recursive MarkGCThingChildren marks + * the thing with flags referred by *next_flagp. + */ + thing = NULL; + continue; + } + } +#ifdef GC_MARK_DEBUG + GetObjSlotName(scope, obj, vp - obj->slots, name, sizeof name); +#endif + thing = next_thing; + flagp = next_flagp; + } + if (thing) { + /* + * thing came from the last unmarked GC-thing slot and we + * can optimize tail recursion. + * + * Since we already know that there is enough C stack space, + * we clear shouldCheckRecursion to avoid extra checking in + * RECURSION_TOO_DEEP. + */ + shouldCheckRecursion = JS_FALSE; + goto on_tail_recursion; + } + break; + +#ifdef DEBUG + case GCX_STRING: + str = (JSString *)thing; + JS_ASSERT(!JSSTRING_IS_DEPENDENT(str)); + break; +#endif + + case GCX_MUTABLE_STRING: + str = (JSString *)thing; + if (!JSSTRING_IS_DEPENDENT(str)) + break; + thing = JSSTRDEP_BASE(str); + flagp = js_GetGCThingFlags(thing); + if (*flagp & GCF_MARK) + break; +#ifdef GC_MARK_DEBUG + strcpy(name, "base"); +#endif + /* Fallthrough to code to deal with the tail recursion. */ + + on_tail_recursion: +#ifdef GC_MARK_DEBUG + /* + * Do not eliminate C recursion when debugging to allow + * js_MarkNamedGCThing to build a full dump of live GC + * things. + */ + GC_MARK(cx, thing, name); + break; +#else + /* Eliminate tail recursion for the last unmarked child. */ + JS_ASSERT(*flagp != GCF_FINAL); + METER(++tailCallNesting); + *flagp |= GCF_MARK; + goto start; +#endif + +#if JS_HAS_XML_SUPPORT + case GCX_NAMESPACE: + if (RECURSION_TOO_DEEP()) + goto add_to_unscanned_bag; + js_MarkXMLNamespace(cx, (JSXMLNamespace *)thing); + break; + + case GCX_QNAME: + if (RECURSION_TOO_DEEP()) + goto add_to_unscanned_bag; + js_MarkXMLQName(cx, (JSXMLQName *)thing); + break; + + case GCX_XML: + if (RECURSION_TOO_DEEP()) + goto add_to_unscanned_bag; + js_MarkXML(cx, (JSXML *)thing); + break; +#endif + add_to_unscanned_bag: + AddThingToUnscannedBag(cx->runtime, thing, flagp); + break; + } + +#undef RECURSION_TOO_DEEP + + METER(rt->gcStats.depth -= 1 + tailCallNesting); + METER(rt->gcStats.cdepth--); +} + +/* + * Avoid using PAGE_THING_GAP inside this macro to optimize the + * thingsPerUnscannedChunk calculation when thingSize is a power of two. + */ +#define GET_GAP_AND_CHUNK_SPAN(thingSize, thingsPerUnscannedChunk, pageGap) \ + JS_BEGIN_MACRO \ + if (0 == ((thingSize) & ((thingSize) - 1))) { \ + pageGap = (thingSize); \ + thingsPerUnscannedChunk = ((GC_PAGE_SIZE / (thingSize)) \ + + JS_BITS_PER_WORD - 1) \ + >> JS_BITS_PER_WORD_LOG2; \ + } else { \ + pageGap = GC_PAGE_SIZE % (thingSize); \ + thingsPerUnscannedChunk = JS_HOWMANY(GC_PAGE_SIZE / (thingSize), \ + JS_BITS_PER_WORD); \ + } \ + JS_END_MACRO + +static void +AddThingToUnscannedBag(JSRuntime *rt, void *thing, uint8 *flagp) +{ + JSGCPageInfo *pi; + JSGCArena *arena; + size_t thingSize; + size_t thingsPerUnscannedChunk; + size_t pageGap; + size_t chunkIndex; + jsuword bit; + + /* Things from delayed scanning bag are marked as GCF_MARK | GCF_FINAL. */ + JS_ASSERT((*flagp & (GCF_MARK | GCF_FINAL)) == GCF_MARK); + *flagp |= GCF_FINAL; + + METER(rt->gcStats.unscanned++); +#ifdef DEBUG + ++rt->gcUnscannedBagSize; + METER(if (rt->gcUnscannedBagSize > rt->gcStats.maxunscanned) + rt->gcStats.maxunscanned = rt->gcUnscannedBagSize); +#endif + + pi = THING_TO_PAGE(thing); + arena = PAGE_TO_ARENA(pi); + thingSize = arena->list->thingSize; + GET_GAP_AND_CHUNK_SPAN(thingSize, thingsPerUnscannedChunk, pageGap); + chunkIndex = (((jsuword)thing & GC_PAGE_MASK) - pageGap) / + (thingSize * thingsPerUnscannedChunk); + JS_ASSERT(chunkIndex < JS_BITS_PER_WORD); + bit = (jsuword)1 << chunkIndex; + if (pi->unscannedBitmap != 0) { + JS_ASSERT(rt->gcUnscannedArenaStackTop); + if (thingsPerUnscannedChunk != 1) { + if (pi->unscannedBitmap & bit) { + /* Chunk already contains things to scan later. */ + return; + } + } else { + /* + * The chunk must not contain things to scan later if there is + * only one thing per chunk. + */ + JS_ASSERT(!(pi->unscannedBitmap & bit)); + } + pi->unscannedBitmap |= bit; + JS_ASSERT(arena->unscannedPages & ((size_t)1 << PAGE_INDEX(pi))); + } else { + /* + * The thing is the first unscanned thing in the page, set the bit + * corresponding to this page arena->unscannedPages. + */ + pi->unscannedBitmap = bit; + JS_ASSERT(PAGE_INDEX(pi) < JS_BITS_PER_WORD); + bit = (jsuword)1 << PAGE_INDEX(pi); + JS_ASSERT(!(arena->unscannedPages & bit)); + if (arena->unscannedPages != 0) { + arena->unscannedPages |= bit; + JS_ASSERT(arena->prevUnscanned); + JS_ASSERT(rt->gcUnscannedArenaStackTop); + } else { + /* + * The thing is the first unscanned thing in the whole arena, push + * the arena on the stack of unscanned arenas unless the arena + * has already been pushed. We detect that through prevUnscanned + * field which is NULL only for not yet pushed arenas. To ensure + * that prevUnscanned != NULL even when the stack contains one + * element, we make prevUnscanned for the arena at the bottom + * to point to itself. + * + * See comments in ScanDelayedChildren. + */ + arena->unscannedPages = bit; + if (!arena->prevUnscanned) { + if (!rt->gcUnscannedArenaStackTop) { + /* Stack was empty, mark the arena as bottom element. */ + arena->prevUnscanned = arena; + } else { + JS_ASSERT(rt->gcUnscannedArenaStackTop->prevUnscanned); + arena->prevUnscanned = rt->gcUnscannedArenaStackTop; + } + rt->gcUnscannedArenaStackTop = arena; + } + } + } + JS_ASSERT(rt->gcUnscannedArenaStackTop); +} + +static void +ScanDelayedChildren(JSContext *cx) +{ + JSRuntime *rt; + JSGCArena *arena; + size_t thingSize; + size_t thingsPerUnscannedChunk; + size_t pageGap; + size_t pageIndex; + JSGCPageInfo *pi; + size_t chunkIndex; + size_t thingOffset, thingLimit; + JSGCThing *thing; + uint8 *flagp; + JSGCArena *prevArena; + + rt = cx->runtime; + arena = rt->gcUnscannedArenaStackTop; + if (!arena) { + JS_ASSERT(rt->gcUnscannedBagSize == 0); + return; + } + + init_size: + thingSize = arena->list->thingSize; + GET_GAP_AND_CHUNK_SPAN(thingSize, thingsPerUnscannedChunk, pageGap); + for (;;) { + /* + * The following assert verifies that the current arena belongs to + * the unscan stack since AddThingToUnscannedBag ensures that even + * for stack's bottom prevUnscanned != NULL but rather points to self. + */ + JS_ASSERT(arena->prevUnscanned); + JS_ASSERT(rt->gcUnscannedArenaStackTop->prevUnscanned); + while (arena->unscannedPages != 0) { + pageIndex = JS_FLOOR_LOG2W(arena->unscannedPages); + JS_ASSERT(pageIndex < GC_PAGE_COUNT); + pi = (JSGCPageInfo *)(FIRST_THING_PAGE(arena) + + pageIndex * GC_PAGE_SIZE); + JS_ASSERT(pi->unscannedBitmap); + chunkIndex = JS_FLOOR_LOG2W(pi->unscannedBitmap); + pi->unscannedBitmap &= ~((jsuword)1 << chunkIndex); + if (pi->unscannedBitmap == 0) + arena->unscannedPages &= ~((jsuword)1 << pageIndex); + thingOffset = (pageGap + + chunkIndex * thingsPerUnscannedChunk * thingSize); + JS_ASSERT(thingOffset >= sizeof(JSGCPageInfo)); + thingLimit = thingOffset + thingsPerUnscannedChunk * thingSize; + if (thingsPerUnscannedChunk != 1) { + /* + * thingLimit can go beyond the last allocated thing for the + * last chunk as the real limit can be inside the chunk. + */ + if (arena->list->last == arena && + arena->list->lastLimit < (pageIndex * GC_PAGE_SIZE + + thingLimit)) { + thingLimit = (arena->list->lastLimit - + pageIndex * GC_PAGE_SIZE); + } else if (thingLimit > GC_PAGE_SIZE) { + thingLimit = GC_PAGE_SIZE; + } + JS_ASSERT(thingLimit > thingOffset); + } + JS_ASSERT(arena->list->last != arena || + arena->list->lastLimit >= (pageIndex * GC_PAGE_SIZE + + thingLimit)); + JS_ASSERT(thingLimit <= GC_PAGE_SIZE); + + for (; thingOffset != thingLimit; thingOffset += thingSize) { + /* + * XXX: inline js_GetGCThingFlags() to use already available + * pi. + */ + thing = (void *)((jsuword)pi + thingOffset); + flagp = js_GetGCThingFlags(thing); + if (thingsPerUnscannedChunk != 1) { + /* + * Skip free or already scanned things that share the chunk + * with unscanned ones. + */ + if ((*flagp & (GCF_MARK|GCF_FINAL)) != (GCF_MARK|GCF_FINAL)) + continue; + } + JS_ASSERT((*flagp & (GCF_MARK|GCF_FINAL)) + == (GCF_MARK|GCF_FINAL)); + *flagp &= ~GCF_FINAL; +#ifdef DEBUG + JS_ASSERT(rt->gcUnscannedBagSize != 0); + --rt->gcUnscannedBagSize; + + /* + * Check that GC thing type is consistent with the type of + * things that can be put to the unscanned bag. + */ + switch (*flagp & GCF_TYPEMASK) { + case GCX_OBJECT: +# if JS_HAS_XML_SUPPORT + case GCX_NAMESPACE: + case GCX_QNAME: + case GCX_XML: +# endif + break; + default: + JS_ASSERT(0); + } +#endif + MarkGCThingChildren(cx, thing, flagp, JS_FALSE); + } + } + /* + * We finished scanning of the arena but we can only pop it from + * the stack if the arena is the stack's top. + * + * When MarkGCThingChildren from the above calls + * AddThingToUnscannedBag and the latter pushes new arenas to the + * stack, we have to skip popping of this arena until it becomes + * the top of the stack again. + */ + if (arena == rt->gcUnscannedArenaStackTop) { + prevArena = arena->prevUnscanned; + arena->prevUnscanned = NULL; + if (arena == prevArena) { + /* + * prevUnscanned points to itself and we reached the bottom + * of the stack. + */ + break; + } + rt->gcUnscannedArenaStackTop = arena = prevArena; + } else { + arena = rt->gcUnscannedArenaStackTop; + } + if (arena->list->thingSize != thingSize) + goto init_size; + } + JS_ASSERT(rt->gcUnscannedArenaStackTop); + JS_ASSERT(!rt->gcUnscannedArenaStackTop->prevUnscanned); + rt->gcUnscannedArenaStackTop = NULL; + JS_ASSERT(rt->gcUnscannedBagSize == 0); +} + +void +js_MarkGCThing(JSContext *cx, void *thing) +{ + uint8 *flagp; + + if (!thing) + return; + + flagp = js_GetGCThingFlags(thing); + JS_ASSERT(*flagp != GCF_FINAL); + if (*flagp & GCF_MARK) + return; + *flagp |= GCF_MARK; + + if (!cx->insideGCMarkCallback) { + MarkGCThingChildren(cx, thing, flagp, JS_TRUE); + } else { + /* + * For API compatibility we allow for the callback to assume that + * after it calls js_MarkGCThing for the last time, the callback + * can start to finalize its own objects that are only referenced + * by unmarked GC things. + * + * Since we do not know which call from inside the callback is the + * last, we ensure that the unscanned bag is always empty when we + * return to the callback and all marked things are scanned. + * + * As an optimization we do not check for the stack size here and + * pass JS_FALSE as the last argument to MarkGCThingChildren. + * Otherwise with low C stack the thing would be pushed to the bag + * just to be feed to MarkGCThingChildren from inside + * ScanDelayedChildren. + */ + cx->insideGCMarkCallback = JS_FALSE; + MarkGCThingChildren(cx, thing, flagp, JS_FALSE); + ScanDelayedChildren(cx); + cx->insideGCMarkCallback = JS_TRUE; + } +} + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +gc_root_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg) +{ + JSGCRootHashEntry *rhe = (JSGCRootHashEntry *)hdr; + jsval *rp = (jsval *)rhe->root; + jsval v = *rp; + + /* Ignore null object and scalar values. */ + if (!JSVAL_IS_NULL(v) && JSVAL_IS_GCTHING(v)) { + JSContext *cx = (JSContext *)arg; +#ifdef DEBUG + JSBool root_points_to_gcArenaList = JS_FALSE; + jsuword thing = (jsuword) JSVAL_TO_GCTHING(v); + uintN i; + JSGCArenaList *arenaList; + JSGCArena *a; + size_t limit; + + for (i = 0; i < GC_NUM_FREELISTS; i++) { + arenaList = &cx->runtime->gcArenaList[i]; + limit = arenaList->lastLimit; + for (a = arenaList->last; a; a = a->prev) { + if (thing - FIRST_THING_PAGE(a) < limit) { + root_points_to_gcArenaList = JS_TRUE; + break; + } + limit = GC_THINGS_SIZE; + } + } + if (!root_points_to_gcArenaList && rhe->name) { + fprintf(stderr, +"JS API usage error: the address passed to JS_AddNamedRoot currently holds an\n" +"invalid jsval. This is usually caused by a missing call to JS_RemoveRoot.\n" +"The root's name is \"%s\".\n", + rhe->name); + } + JS_ASSERT(root_points_to_gcArenaList); +#endif + + GC_MARK(cx, JSVAL_TO_GCTHING(v), rhe->name ? rhe->name : "root"); + } + return JS_DHASH_NEXT; +} + +JS_STATIC_DLL_CALLBACK(JSDHashOperator) +gc_lock_marker(JSDHashTable *table, JSDHashEntryHdr *hdr, uint32 num, void *arg) +{ + JSGCLockHashEntry *lhe = (JSGCLockHashEntry *)hdr; + void *thing = (void *)lhe->thing; + JSContext *cx = (JSContext *)arg; + + GC_MARK(cx, thing, "locked object"); + return JS_DHASH_NEXT; +} + +#define GC_MARK_JSVALS(cx, len, vec, name) \ + JS_BEGIN_MACRO \ + jsval _v, *_vp, *_end; \ + \ + for (_vp = vec, _end = _vp + len; _vp < _end; _vp++) { \ + _v = *_vp; \ + if (JSVAL_IS_GCTHING(_v)) \ + GC_MARK(cx, JSVAL_TO_GCTHING(_v), name); \ + } \ + JS_END_MACRO + +void +js_MarkStackFrame(JSContext *cx, JSStackFrame *fp) +{ + uintN depth, nslots; + + if (fp->callobj) + GC_MARK(cx, fp->callobj, "call object"); + if (fp->argsobj) + GC_MARK(cx, fp->argsobj, "arguments object"); + if (fp->varobj) + GC_MARK(cx, fp->varobj, "variables object"); + if (fp->script) { + js_MarkScript(cx, fp->script); + if (fp->spbase) { + /* + * Don't mark what has not been pushed yet, or what has been + * popped already. + */ + depth = fp->script->depth; + nslots = (JS_UPTRDIFF(fp->sp, fp->spbase) + < depth * sizeof(jsval)) + ? (uintN)(fp->sp - fp->spbase) + : depth; + GC_MARK_JSVALS(cx, nslots, fp->spbase, "operand"); + } + } + + /* Allow for primitive this parameter due to JSFUN_THISP_* flags. */ + JS_ASSERT(JSVAL_IS_OBJECT((jsval)fp->thisp) || + (fp->fun && JSFUN_THISP_FLAGS(fp->fun->flags))); + if (JSVAL_IS_GCTHING((jsval)fp->thisp)) + GC_MARK(cx, JSVAL_TO_GCTHING((jsval)fp->thisp), "this"); + + /* + * Mark fp->argv, even though in the common case it will be marked via our + * caller's frame, or via a JSStackHeader if fp was pushed by an external + * invocation. + * + * The hard case is when there is not enough contiguous space in the stack + * arena for actual, missing formal, and local root (JSFunctionSpec.extra) + * slots. In this case, fp->argv points to new space in a new arena, and + * marking the caller's operand stack, or an external caller's allocated + * stack tracked by a JSStackHeader, will not mark all the values stored + * and addressable via fp->argv. + * + * So in summary, solely for the hard case of moving argv due to missing + * formals and extra roots, we must mark actuals, missing formals, and any + * local roots arrayed at fp->argv here. + * + * It would be good to avoid redundant marking of the same reference, in + * the case where fp->argv does point into caller-allocated space tracked + * by fp->down->spbase or cx->stackHeaders. This would allow callbacks + * such as the forthcoming rt->gcThingCallback (bug 333078) to compute JS + * reference counts. So this comment deserves a FIXME bug to cite. + */ + if (fp->argv) { + nslots = fp->argc; + if (fp->fun) { + if (fp->fun->nargs > nslots) + nslots = fp->fun->nargs; + if (!FUN_INTERPRETED(fp->fun)) + nslots += fp->fun->u.n.extra; + } + GC_MARK_JSVALS(cx, nslots + 2, fp->argv - 2, "arg"); + } + if (JSVAL_IS_GCTHING(fp->rval)) + GC_MARK(cx, JSVAL_TO_GCTHING(fp->rval), "rval"); + if (fp->vars) + GC_MARK_JSVALS(cx, fp->nvars, fp->vars, "var"); + GC_MARK(cx, fp->scopeChain, "scope chain"); + if (fp->sharpArray) + GC_MARK(cx, fp->sharpArray, "sharp array"); + + if (fp->xmlNamespace) + GC_MARK(cx, fp->xmlNamespace, "xmlNamespace"); +} + +static void +MarkWeakRoots(JSContext *cx, JSWeakRoots *wr) +{ + uintN i; + void *thing; + + for (i = 0; i < GCX_NTYPES; i++) + GC_MARK(cx, wr->newborn[i], gc_typenames[i]); + if (wr->lastAtom) + GC_MARK_ATOM(cx, wr->lastAtom); + if (JSVAL_IS_GCTHING(wr->lastInternalResult)) { + thing = JSVAL_TO_GCTHING(wr->lastInternalResult); + if (thing) + GC_MARK(cx, thing, "lastInternalResult"); + } +} + +/* + * When gckind is GC_LAST_DITCH, it indicates a call from js_NewGCThing with + * rt->gcLock already held and when the lock should be kept on return. + */ +void +js_GC(JSContext *cx, JSGCInvocationKind gckind) +{ + JSRuntime *rt; + JSBool keepAtoms; + uintN i, type; + JSContext *iter, *acx; +#if JS_HAS_GENERATORS + JSGenerator **genTodoTail; +#endif + JSStackFrame *fp, *chain; + JSStackHeader *sh; + JSTempValueRooter *tvr; + size_t nbytes, limit, offset; + JSGCArena *a, **ap; + uint8 flags, *flagp, *firstPage; + JSGCThing *thing, *freeList; + JSGCArenaList *arenaList; + GCFinalizeOp finalizer; + JSBool allClear; +#ifdef JS_THREADSAFE + uint32 requestDebit; +#endif + + rt = cx->runtime; +#ifdef JS_THREADSAFE + /* Avoid deadlock. */ + JS_ASSERT(!JS_IS_RUNTIME_LOCKED(rt)); +#endif + + if (gckind == GC_LAST_DITCH) { + /* The last ditch GC preserves all atoms and weak roots. */ + keepAtoms = JS_TRUE; + } else { + JS_CLEAR_WEAK_ROOTS(&cx->weakRoots); + rt->gcPoke = JS_TRUE; + + /* Keep atoms when a suspended compile is running on another context. */ + keepAtoms = (rt->gcKeepAtoms != 0); + } + + /* + * Don't collect garbage if the runtime isn't up, and cx is not the last + * context in the runtime. The last context must force a GC, and nothing + * should suppress that final collection or there may be shutdown leaks, + * or runtime bloat until the next context is created. + */ + if (rt->state != JSRTS_UP && gckind != GC_LAST_CONTEXT) + return; + + restart_after_callback: + /* + * Let the API user decide to defer a GC if it wants to (unless this + * is the last context). Invoke the callback regardless. + */ + if (rt->gcCallback && + !rt->gcCallback(cx, JSGC_BEGIN) && + gckind != GC_LAST_CONTEXT) { + return; + } + + /* Lock out other GC allocator and collector invocations. */ + if (gckind != GC_LAST_DITCH) + JS_LOCK_GC(rt); + + /* Do nothing if no mutator has executed since the last GC. */ + if (!rt->gcPoke) { + METER(rt->gcStats.nopoke++); + if (gckind != GC_LAST_DITCH) + JS_UNLOCK_GC(rt); + return; + } + METER(rt->gcStats.poke++); + rt->gcPoke = JS_FALSE; + +#ifdef JS_THREADSAFE + JS_ASSERT(cx->thread->id == js_CurrentThreadId()); + + /* Bump gcLevel and return rather than nest on this thread. */ + if (rt->gcThread == cx->thread) { + JS_ASSERT(rt->gcLevel > 0); + rt->gcLevel++; + METER(if (rt->gcLevel > rt->gcStats.maxlevel) + rt->gcStats.maxlevel = rt->gcLevel); + if (gckind != GC_LAST_DITCH) + JS_UNLOCK_GC(rt); + return; + } + + /* + * If we're in one or more requests (possibly on more than one context) + * running on the current thread, indicate, temporarily, that all these + * requests are inactive. If cx->thread is NULL, then cx is not using + * the request model, and does not contribute to rt->requestCount. + */ + requestDebit = 0; + if (cx->thread) { + JSCList *head, *link; + + /* + * Check all contexts on cx->thread->contextList for active requests, + * counting each such context against requestDebit. + */ + head = &cx->thread->contextList; + for (link = head->next; link != head; link = link->next) { + acx = CX_FROM_THREAD_LINKS(link); + JS_ASSERT(acx->thread == cx->thread); + if (acx->requestDepth) + requestDebit++; + } + } else { + /* + * We assert, but check anyway, in case someone is misusing the API. + * Avoiding the loop over all of rt's contexts is a win in the event + * that the GC runs only on request-less contexts with null threads, + * in a special thread such as might be used by the UI/DOM/Layout + * "mozilla" or "main" thread in Mozilla-the-browser. + */ + JS_ASSERT(cx->requestDepth == 0); + if (cx->requestDepth) + requestDebit = 1; + } + if (requestDebit) { + JS_ASSERT(requestDebit <= rt->requestCount); + rt->requestCount -= requestDebit; + if (rt->requestCount == 0) + JS_NOTIFY_REQUEST_DONE(rt); + } + + /* If another thread is already in GC, don't attempt GC; wait instead. */ + if (rt->gcLevel > 0) { + /* Bump gcLevel to restart the current GC, so it finds new garbage. */ + rt->gcLevel++; + METER(if (rt->gcLevel > rt->gcStats.maxlevel) + rt->gcStats.maxlevel = rt->gcLevel); + + /* Wait for the other thread to finish, then resume our request. */ + while (rt->gcLevel > 0) + JS_AWAIT_GC_DONE(rt); + if (requestDebit) + rt->requestCount += requestDebit; + if (gckind != GC_LAST_DITCH) + JS_UNLOCK_GC(rt); + return; + } + + /* No other thread is in GC, so indicate that we're now in GC. */ + rt->gcLevel = 1; + rt->gcThread = cx->thread; + + /* Wait for all other requests to finish. */ + while (rt->requestCount > 0) + JS_AWAIT_REQUEST_DONE(rt); + +#else /* !JS_THREADSAFE */ + + /* Bump gcLevel and return rather than nest; the outer gc will restart. */ + rt->gcLevel++; + METER(if (rt->gcLevel > rt->gcStats.maxlevel) + rt->gcStats.maxlevel = rt->gcLevel); + if (rt->gcLevel > 1) + return; + +#endif /* !JS_THREADSAFE */ + + /* + * Set rt->gcRunning here within the GC lock, and after waiting for any + * active requests to end, so that new requests that try to JS_AddRoot, + * JS_RemoveRoot, or JS_RemoveRootRT block in JS_BeginRequest waiting for + * rt->gcLevel to drop to zero, while request-less calls to the *Root* + * APIs block in js_AddRoot or js_RemoveRoot (see above in this file), + * waiting for GC to finish. + */ + rt->gcRunning = JS_TRUE; + JS_UNLOCK_GC(rt); + + /* Reset malloc counter. */ + rt->gcMallocBytes = 0; + + /* Drop atoms held by the property cache, and clear property weak links. */ + js_DisablePropertyCache(cx); + js_FlushPropertyCache(cx); +#ifdef DEBUG_scopemeters + { extern void js_DumpScopeMeters(JSRuntime *rt); + js_DumpScopeMeters(rt); + } +#endif + +#ifdef JS_THREADSAFE + /* + * Set all thread local freelists to NULL. We may visit a thread's + * freelist more than once. To avoid redundant clearing we unroll the + * current thread's step. + * + * Also, in case a JSScript wrapped within an object was finalized, we + * null acx->thread->gsnCache.script and finish the cache's hashtable. + * Note that js_DestroyScript, called from script_finalize, will have + * already cleared cx->thread->gsnCache above during finalization, so we + * don't have to here. + */ + memset(cx->thread->gcFreeLists, 0, sizeof cx->thread->gcFreeLists); + iter = NULL; + while ((acx = js_ContextIterator(rt, JS_FALSE, &iter)) != NULL) { + if (!acx->thread || acx->thread == cx->thread) + continue; + memset(acx->thread->gcFreeLists, 0, sizeof acx->thread->gcFreeLists); + GSN_CACHE_CLEAR(&acx->thread->gsnCache); + } +#else + /* The thread-unsafe case just has to clear the runtime's GSN cache. */ + GSN_CACHE_CLEAR(&rt->gsnCache); +#endif + +restart: + rt->gcNumber++; + JS_ASSERT(!rt->gcUnscannedArenaStackTop); + JS_ASSERT(rt->gcUnscannedBagSize == 0); + + /* + * Mark phase. + */ + JS_DHashTableEnumerate(&rt->gcRootsHash, gc_root_marker, cx); + if (rt->gcLocksHash) + JS_DHashTableEnumerate(rt->gcLocksHash, gc_lock_marker, cx); + js_MarkAtomState(&rt->atomState, keepAtoms, gc_mark_atom_key_thing, cx); + js_MarkWatchPoints(cx); + js_MarkScriptFilenames(rt, keepAtoms); + js_MarkNativeIteratorStates(cx); + +#if JS_HAS_GENERATORS + genTodoTail = MarkScheduledGenerators(cx); + JS_ASSERT(!*genTodoTail); +#endif + + iter = NULL; + while ((acx = js_ContextIterator(rt, JS_TRUE, &iter)) != NULL) { + /* + * Iterate frame chain and dormant chains. Temporarily tack current + * frame onto the head of the dormant list to ease iteration. + * + * (NB: see comment on this whole "dormant" thing in js_Execute.) + */ + chain = acx->fp; + if (chain) { + JS_ASSERT(!chain->dormantNext); + chain->dormantNext = acx->dormantFrameChain; + } else { + chain = acx->dormantFrameChain; + } + + for (fp = chain; fp; fp = chain = chain->dormantNext) { + do { + js_MarkStackFrame(cx, fp); + } while ((fp = fp->down) != NULL); + } + + /* Cleanup temporary "dormant" linkage. */ + if (acx->fp) + acx->fp->dormantNext = NULL; + + /* Mark other roots-by-definition in acx. */ + GC_MARK(cx, acx->globalObject, "global object"); + MarkWeakRoots(cx, &acx->weakRoots); + if (acx->throwing) { + if (JSVAL_IS_GCTHING(acx->exception)) + GC_MARK(cx, JSVAL_TO_GCTHING(acx->exception), "exception"); + } else { + /* Avoid keeping GC-ed junk stored in JSContext.exception. */ + acx->exception = JSVAL_NULL; + } +#if JS_HAS_LVALUE_RETURN + if (acx->rval2set && JSVAL_IS_GCTHING(acx->rval2)) + GC_MARK(cx, JSVAL_TO_GCTHING(acx->rval2), "rval2"); +#endif + + for (sh = acx->stackHeaders; sh; sh = sh->down) { + METER(rt->gcStats.stackseg++); + METER(rt->gcStats.segslots += sh->nslots); + GC_MARK_JSVALS(cx, sh->nslots, JS_STACK_SEGMENT(sh), "stack"); + } + + if (acx->localRootStack) + js_MarkLocalRoots(cx, acx->localRootStack); + + for (tvr = acx->tempValueRooters; tvr; tvr = tvr->down) { + switch (tvr->count) { + case JSTVU_SINGLE: + if (JSVAL_IS_GCTHING(tvr->u.value)) { + GC_MARK(cx, JSVAL_TO_GCTHING(tvr->u.value), + "tvr->u.value"); + } + break; + case JSTVU_MARKER: + tvr->u.marker(cx, tvr); + break; + case JSTVU_SPROP: + MARK_SCOPE_PROPERTY(cx, tvr->u.sprop); + break; + case JSTVU_WEAK_ROOTS: + MarkWeakRoots(cx, tvr->u.weakRoots); + break; + default: + JS_ASSERT(tvr->count >= 0); + GC_MARK_JSVALS(cx, tvr->count, tvr->u.array, "tvr->u.array"); + } + } + + if (acx->sharpObjectMap.depth > 0) + js_GCMarkSharpMap(cx, &acx->sharpObjectMap); + } + +#ifdef DUMP_CALL_TABLE + js_DumpCallTable(cx); +#endif + + /* + * Mark children of things that caused too deep recursion during above + * marking phase. + */ + ScanDelayedChildren(cx); + +#if JS_HAS_GENERATORS + /* + * Close phase: search and mark part. See comments in + * FindAndMarkObjectsToClose for details. + */ + FindAndMarkObjectsToClose(cx, gckind, genTodoTail); + + /* + * Mark children of things that caused too deep recursion during the + * just-completed marking part of the close phase. + */ + ScanDelayedChildren(cx); +#endif + + JS_ASSERT(!cx->insideGCMarkCallback); + if (rt->gcCallback) { + cx->insideGCMarkCallback = JS_TRUE; + (void) rt->gcCallback(cx, JSGC_MARK_END); + JS_ASSERT(cx->insideGCMarkCallback); + cx->insideGCMarkCallback = JS_FALSE; + } + JS_ASSERT(rt->gcUnscannedBagSize == 0); + + /* Finalize iterator states before the objects they iterate over. */ + CloseIteratorStates(cx); + + /* + * Sweep phase. + * + * Finalize as we sweep, outside of rt->gcLock but with rt->gcRunning set + * so that any attempt to allocate a GC-thing from a finalizer will fail, + * rather than nest badly and leave the unmarked newborn to be swept. + * + * Finalize smaller objects before larger, to guarantee finalization of + * GC-allocated obj->slots after obj. See FreeSlots in jsobj.c. + */ + for (i = 0; i < GC_NUM_FREELISTS; i++) { + arenaList = &rt->gcArenaList[i]; + nbytes = GC_FREELIST_NBYTES(i); + limit = arenaList->lastLimit; + for (a = arenaList->last; a; a = a->prev) { + JS_ASSERT(!a->prevUnscanned); + JS_ASSERT(a->unscannedPages == 0); + firstPage = (uint8 *) FIRST_THING_PAGE(a); + for (offset = 0; offset != limit; offset += nbytes) { + if ((offset & GC_PAGE_MASK) == 0) { + JS_ASSERT(((JSGCPageInfo *)(firstPage + offset))-> + unscannedBitmap == 0); + offset += PAGE_THING_GAP(nbytes); + } + JS_ASSERT(offset < limit); + flagp = a->base + offset / sizeof(JSGCThing); + if (flagp >= firstPage) + flagp += GC_THINGS_SIZE; + flags = *flagp; + if (flags & GCF_MARK) { + *flagp &= ~GCF_MARK; + } else if (!(flags & (GCF_LOCK | GCF_FINAL))) { + /* Call the finalizer with GCF_FINAL ORed into flags. */ + type = flags & GCF_TYPEMASK; + finalizer = gc_finalizers[type]; + if (finalizer) { + thing = (JSGCThing *)(firstPage + offset); + *flagp = (uint8)(flags | GCF_FINAL); + if (type >= GCX_EXTERNAL_STRING) + js_PurgeDeflatedStringCache(rt, (JSString *)thing); + finalizer(cx, thing); + } + + /* Set flags to GCF_FINAL, signifying that thing is free. */ + *flagp = GCF_FINAL; + } + } + limit = GC_THINGS_SIZE; + } + } + + /* + * Sweep the runtime's property tree after finalizing objects, in case any + * had watchpoints referencing tree nodes. Then sweep atoms, which may be + * referenced from dead property ids. + */ + js_SweepScopeProperties(rt); + js_SweepAtomState(&rt->atomState); + + /* + * Sweep script filenames after sweeping functions in the generic loop + * above. In this way when a scripted function's finalizer destroys the + * script and calls rt->destroyScriptHook, the hook can still access the + * script's filename. See bug 323267. + */ + js_SweepScriptFilenames(rt); + + /* + * Free phase. + * Free any unused arenas and rebuild the JSGCThing freelist. + */ + for (i = 0; i < GC_NUM_FREELISTS; i++) { + arenaList = &rt->gcArenaList[i]; + ap = &arenaList->last; + a = *ap; + if (!a) + continue; + + allClear = JS_TRUE; + arenaList->freeList = NULL; + freeList = NULL; + METER(arenaList->stats.nthings = 0); + METER(arenaList->stats.freelen = 0); + + nbytes = GC_FREELIST_NBYTES(i); + limit = arenaList->lastLimit; + do { + METER(size_t nfree = 0); + firstPage = (uint8 *) FIRST_THING_PAGE(a); + for (offset = 0; offset != limit; offset += nbytes) { + if ((offset & GC_PAGE_MASK) == 0) + offset += PAGE_THING_GAP(nbytes); + JS_ASSERT(offset < limit); + flagp = a->base + offset / sizeof(JSGCThing); + if (flagp >= firstPage) + flagp += GC_THINGS_SIZE; + + if (*flagp != GCF_FINAL) { + allClear = JS_FALSE; + METER(++arenaList->stats.nthings); + } else { + thing = (JSGCThing *)(firstPage + offset); + thing->flagp = flagp; + thing->next = freeList; + freeList = thing; + METER(++nfree); + } + } + if (allClear) { + /* + * Forget just assembled free list head for the arena + * and destroy the arena itself. + */ + freeList = arenaList->freeList; + DestroyGCArena(rt, arenaList, ap); + } else { + allClear = JS_TRUE; + arenaList->freeList = freeList; + ap = &a->prev; + METER(arenaList->stats.freelen += nfree); + METER(arenaList->stats.totalfreelen += nfree); + METER(++arenaList->stats.totalarenas); + } + limit = GC_THINGS_SIZE; + } while ((a = *ap) != NULL); + } + + if (rt->gcCallback) + (void) rt->gcCallback(cx, JSGC_FINALIZE_END); +#ifdef DEBUG_srcnotesize + { extern void DumpSrcNoteSizeHist(); + DumpSrcNoteSizeHist(); + printf("GC HEAP SIZE %lu (%lu)\n", + (unsigned long)rt->gcBytes, (unsigned long)rt->gcPrivateBytes); + } +#endif + + JS_LOCK_GC(rt); + + /* + * We want to restart GC if js_GC was called recursively or if any of the + * finalizers called js_RemoveRoot or js_UnlockGCThingRT. + */ + if (rt->gcLevel > 1 || rt->gcPoke) { + rt->gcLevel = 1; + rt->gcPoke = JS_FALSE; + JS_UNLOCK_GC(rt); + goto restart; + } + js_EnablePropertyCache(cx); + rt->gcLevel = 0; + rt->gcLastBytes = rt->gcBytes; + rt->gcRunning = JS_FALSE; + +#ifdef JS_THREADSAFE + /* If we were invoked during a request, pay back the temporary debit. */ + if (requestDebit) + rt->requestCount += requestDebit; + rt->gcThread = NULL; + JS_NOTIFY_GC_DONE(rt); + + /* + * Unlock unless we have GC_LAST_DITCH which requires locked GC on return. + */ + if (gckind != GC_LAST_DITCH) + JS_UNLOCK_GC(rt); +#endif + + /* Execute JSGC_END callback outside the lock. */ + if (rt->gcCallback) { + JSWeakRoots savedWeakRoots; + JSTempValueRooter tvr; + + if (gckind == GC_LAST_DITCH) { + /* + * We allow JSGC_END implementation to force a full GC or allocate + * new GC things. Thus we must protect the weak roots from GC or + * overwrites. + */ + savedWeakRoots = cx->weakRoots; + JS_PUSH_TEMP_ROOT_WEAK_COPY(cx, &savedWeakRoots, &tvr); + JS_KEEP_ATOMS(rt); + JS_UNLOCK_GC(rt); + } + + (void) rt->gcCallback(cx, JSGC_END); + + if (gckind == GC_LAST_DITCH) { + JS_LOCK_GC(rt); + JS_UNKEEP_ATOMS(rt); + JS_POP_TEMP_ROOT(cx, &tvr); + } else if (gckind == GC_LAST_CONTEXT && rt->gcPoke) { + /* + * On shutdown iterate until JSGC_END callback stops creating + * garbage. + */ + goto restart_after_callback; + } + } +} + +void +js_UpdateMallocCounter(JSContext *cx, size_t nbytes) +{ + uint32 *pbytes, bytes; + +#ifdef JS_THREADSAFE + pbytes = &cx->thread->gcMallocBytes; +#else + pbytes = &cx->runtime->gcMallocBytes; +#endif + bytes = *pbytes; + *pbytes = ((uint32)-1 - bytes <= nbytes) ? (uint32)-1 : bytes + nbytes; +} |