From 55b0ba4585da564bd1c6a78bc2b7b3d6ecbdc033 Mon Sep 17 00:00:00 2001 From: "brendan%mozilla.org" Date: Thu, 14 Sep 2000 06:14:45 +0000 Subject: [PATCH] Fixes to make JS GC truly exact: - All jsvals for which JSVAL_IS_GCTHING evaluates to true must contain tagged pointers into the GC heap -- therefore jsapi.c's JS_DefineConstDoubles cannot "cheat" by tagging addresses of static jsdoubles to avoid js_NewNumberValue. - Finalization is now interleaved with the Sweep phase, to avoid allocating memory for finalization records while sweeping. Instead, the JSRuntime holds a preallocated JSGCThing vector (gcFinalVec) that the Sweep phase fills and flushes via gc_finalize_phase, repeatedly. This means that finalizers cannot allocate a new GC thing, an incompatible but plausible change. js_AllocGCThing asserts and then checks whether it is called while rt->gcLevel is non-zero, and fails the allocation attempt if so. But this fixes bug 38942, where the old sweep-then-finalize with a sweep => malloc dependency could lead to memory exhaustion. - Instead of scanning whole stackPool arenas, which led to UMRs (bug 27924) and sometimes to gross over-scanning that depended on the GC bounds-checking all thing pointers against its heap, we scan exactly those stack slots in use: - arguments reachable from fp->argv; - variables reachable from fp->vars; - operands now reachable from fp->spbase, bounded above by the lesser of fp->sp or fp->spbase + fp->script->depth for an interpreted frame; if the latter, fp->sp has advanced logically above the operand budget, in order to call a native method, and all unused slots from fp->sp up to depth slots above fp->spbase must be set to JSVAL_VOID; - stack segments pushed when calling native methods, prefixed by JSStackHeader structs and linked from cx->stackSegments through each header. The stack segment headers help the GC avoid scanning unused portions of the stack: the generating pc slots running depth slots below fp->spbase, and slots at the end of an arena that aren't sufficient to satisfy a contiguous allocation for more args, vars, or operands. - Exact GC means the stack pointer must remain above live operands until the interpreter is done with them, so jsinterp.c got heavily whacked. Instead of POPs of various kinds followed by a PUSH for binary operators (e.g.), we use FETCH and STORE macros that index by -1 and -2 from sp, and minimize adjustments to sp. When sp is homed to fp->sp, this allows js_DecompileValueGenerator to find the value reliably, and if possible its generating pc. - Finally, the O(n**2) growth rate of gc_find_flags has been fixed, using the scheme sketched in bug 49816 and documented in a new major comment in jsgc.c. Briefly, by allocating flags and things from one arena, we can align things on 1024-byte "thing page" boundaries, and use JSGCPageInfo headers in each page to find a given thing's flags in O(1) time. /be git-svn-id: svn://10.0.0.236/trunk@79087 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/js/src/jsapi.c | 17 +- mozilla/js/src/jsarena.c | 5 +- mozilla/js/src/jscntxt.h | 12 +- mozilla/js/src/jsfun.c | 13 +- mozilla/js/src/jsgc.c | 663 +++++++++++++++++++++++--------------- mozilla/js/src/jsgc.h | 13 +- mozilla/js/src/jsinterp.c | 536 +++++++++++++++++------------- mozilla/js/src/jsinterp.h | 2 +- mozilla/js/src/jsnum.c | 4 +- mozilla/js/src/jsobj.c | 10 +- mozilla/js/src/jsopcode.c | 218 +++++++------ mozilla/js/src/jsopcode.h | 15 +- mozilla/js/src/jsprvtd.h | 1 + 13 files changed, 878 insertions(+), 631 deletions(-) diff --git a/mozilla/js/src/jsapi.c b/mozilla/js/src/jsapi.c index 145e4ebdebc..0d341cf0ee9 100644 --- a/mozilla/js/src/jsapi.c +++ b/mozilla/js/src/jsapi.c @@ -1938,22 +1938,9 @@ JS_DefineConstDoubles(JSContext *cx, JSObject *obj, JSConstDoubleSpec *cds) CHECK_REQUEST(cx); for (ok = JS_TRUE; cds->name; cds++) { -#if JS_ALIGN_OF_DOUBLE == 8 - /* - * The GC ignores references outside its pool such as &cds->dval, - * so we don't need to GC-alloc constant doubles. - */ - jsdouble d = cds->dval; - jsint i; - - value = (JSDOUBLE_IS_INT(d, i) && INT_FITS_IN_JSVAL(i)) - ? INT_TO_JSVAL(i) - : DOUBLE_TO_JSVAL(&cds->dval); -#else ok = js_NewNumberValue(cx, cds->dval, &value); if (!ok) break; -#endif flags = cds->flags; if (!flags) flags = JSPROP_READONLY | JSPROP_PERMANENT; @@ -3608,8 +3595,7 @@ JS_ClearPendingException(JSContext *cx) } #if JS_HAS_EXCEPTIONS -struct JSExceptionState -{ +struct JSExceptionState { JSBool throwing; jsval exception; }; @@ -3620,6 +3606,7 @@ JS_SaveExceptionState(JSContext *cx) { #if JS_HAS_EXCEPTIONS JSExceptionState *state; + CHECK_REQUEST(cx); state = (JSExceptionState *) JS_malloc(cx, sizeof(JSExceptionState)); if (state) { diff --git a/mozilla/js/src/jsarena.c b/mozilla/js/src/jsarena.c index a0c7e5adbfb..bfab18be66f 100644 --- a/mozilla/js/src/jsarena.c +++ b/mozilla/js/src/jsarena.c @@ -103,9 +103,8 @@ JS_ArenaAllocate(JSArenaPool *pool, JSUint32 nb) while ((b = *ap) != NULL) { /* reclaim a free arena */ /* * Insist on exact arenasize match if nb is not greater than - * arenasize. Otherwise take any arena big enough, but not - * more than nb + arenasize. The JS GC counts on arenasize - * matching to keep its thing and flags arenas parallel. + * arenasize. Otherwise take any arena big enough, but not by + * more than nb + arenasize. */ sz = (JSUint32)(b->limit - b->base); if ((nb > pool->arenasize) diff --git a/mozilla/js/src/jscntxt.h b/mozilla/js/src/jscntxt.h index e460b2005ae..91dff4a010c 100644 --- a/mozilla/js/src/jscntxt.h +++ b/mozilla/js/src/jscntxt.h @@ -66,7 +66,7 @@ struct JSRuntime { /* Garbage collector state, used by jsgc.c. */ JSArenaPool gcArenaPool; - JSArenaPool gcFlagsPool; + JSGCThing *gcFinalVec; JSHashTable *gcRootsHash; JSHashTable *gcLocksHash; JSGCThing *gcFreeList; @@ -184,6 +184,13 @@ struct JSArgumentFormatMap { }; #endif +struct JSStackHeader { + uintN nslots; + JSStackHeader *down; +}; + +#define JS_STACK_SEGMENT(sh) ((jsval *)(sh) + 2) + struct JSContext { JSCList links; @@ -268,6 +275,9 @@ struct JSContext { /* Non-null if init'ing standard classes lazily, to stop recursion. */ JSDHashTable *resolving; + + /* PDL of stack headers describing stack slots not rooted by argv, etc. */ + JSStackHeader *stackHeaders; }; /* Slightly more readable macros, also to hide bitset implementation detail. */ diff --git a/mozilla/js/src/jsfun.c b/mozilla/js/src/jsfun.c index db7374d28ec..b169c91ed05 100644 --- a/mozilla/js/src/jsfun.c +++ b/mozilla/js/src/jsfun.c @@ -983,8 +983,7 @@ fun_hasInstance(JSContext *cx, JSObject *obj, jsval v, JSBool *bp) * Throw a runtime error if instanceof is called on a function that * has a non-object as its .prototype value. */ - str = js_DecompileValueGenerator(cx, JS_TRUE, OBJECT_TO_JSVAL(obj), - NULL); + str = js_DecompileValueGenerator(cx, -1, OBJECT_TO_JSVAL(obj), NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_PROTOTYPE, JS_GetStringBytes(str)); @@ -1741,14 +1740,8 @@ js_ReportIsNotFunction(JSContext *cx, jsval *vp, JSBool constructing) type = JS_TypeOfValue(cx, *vp); fallback = ATOM_TO_STRING(cx->runtime->atomState.typeAtoms[type]); fp = cx->fp; - if (fp) { - jsval *sp = fp->sp; - fp->sp = vp; - str = js_DecompileValueGenerator(cx, JS_TRUE, *vp, fallback); - fp->sp = sp; - } else { - str = js_DecompileValueGenerator(cx, JS_TRUE, *vp, fallback); - } + str = js_DecompileValueGenerator(cx, fp ? vp - fp->sp : JSDVG_IGNORE_STACK, + *vp, fallback); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, (uintN)(constructing ? JSMSG_NOT_CONSTRUCTOR diff --git a/mozilla/js/src/jsgc.c b/mozilla/js/src/jsgc.c index 1e0245deec6..188237a6127 100644 --- a/mozilla/js/src/jsgc.c +++ b/mozilla/js/src/jsgc.c @@ -37,7 +37,7 @@ * * This GC allocates only fixed-sized things big enough to contain two words * (pointers) on any host architecture. It allocates from an arena pool (see - * jsarena.h). It uses a parallel arena-pool array of flag bytes to hold the + * jsarena.h). 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 @@ -63,21 +63,155 @@ #include "jsstr.h" /* - * Arena sizes, the first must be a multiple of the second so the two arena - * pools can be maintained (in particular, arenas may be destroyed from the - * middle of each pool) in parallel. + * 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). */ -#define GC_ARENA_SIZE 8192 /* 1024 (512 on Alpha) objects */ -#define GC_FLAGS_SIZE (GC_ARENA_SIZE / sizeof(JSGCThing)) -#define GC_ROOTS_SIZE 256 /* SWAG, small enough to amortize */ - -static JSHashNumber gc_hash_root(const void *key); +#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)) +#define GC_ARENA_SIZE (GC_THINGS_SIZE + GC_FLAGS_SIZE) +/* The private JSGCThing struct, which describes a gcFreelist element. */ struct JSGCThing { - JSGCThing *next; - uint8 *flagp; + JSGCThing *next; + uint8 *flagp; }; +/* + * A GC arena contains one flag byte for every 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. We mask a thing's + * address with ~1023 to find a JSGCPageInfo record at the front of a mythical + * "GC page" within the larger 8K thing arena. That JSGCPageInfo contains a + * pointer to the 128 flag bytes corresponding to the things in the page, so we + * index into this flags array using the thing's index within its page. + * + * To align thing pages on 1024-byte boundaries, we must allocate the 9KB of + * flags+things arena payload, then find the first 0 mod 1024 boundary after + * the first payload address. That's where things start, with a JSGCPageInfo + * taking up the first thing-slot, as usual for 0 mod 1024 byte boundaries. + * The effect of this alignment trick is to split the flags into at most 2 + * discontiguous spans, one before the things and one after (if we're really + * lucky, and the arena payload starts on a 0 mod 1024 byte boundary, no need + * to split). + * + * The overhead of this scheme for most platforms is (16+8*(8+1))/(16+9K) or + * .95% (assuming 16 byte JSArena header size, and 8 byte JSGCThing size). + * + * Here's some ASCII art showing an arena: + * + * split + * | + * 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. Therefore + * we'll have to test something to decide whether the split divides flags in + * a given thing's page. So we store the split pointer (the pointer to tp0) + * in each JSGCPageInfo, along with the flags pointer for the 128 flag bytes + * ideally starting, for tp0 things, at the beginning of the arena's payload + * (at the start of fB). + * + * That is, each JSGCPageInfo's flags pointer is 128 bytes from the previous, + * or at the start of the arena if there is no previous page in this arena. + * Thus these ideal 128-byte flag pages run contiguously from the start of the + * arena (right over the split!), and the JSGCPageInfo flags pointers contain + * no discontinuities over the split created by the thing pages. So if, for a + * given JSGCPageInfo *pi, we find that + * + * pi->flags + ((jsuword)thing % 1023) / sizeof(JSGCThing) >= pi->split + * + * then we must add GC_THINGS_SIZE to the nominal flags pointer to jump over + * all the thing pages that split the flags into two discontiguous spans. + * + * (If we need to implement card-marking for an incremental GC write barrier, + * we can use the low byte of the pi->split pointer as the card-mark, for an + * extremely efficient write barrier: when mutating an object obj, just store + * a 1 byte at (uint8 *) ((jsuword)obj & ~1023) for little-endian platforms. + * When finding flags, we'll of course have to mask split with ~255, but it is + * guaranteed to be 1024-byte aligned, so no information is lost by overlaying + * the card-mark byte on split's low byte.) + */ +#define GC_PAGE_SHIFT 10 +#define GC_PAGE_MASK JS_BITMASK(GC_PAGE_SHIFT) +#define GC_PAGE_SIZE JS_BIT(GC_PAGE_SHIFT) + +typedef struct JSGCPageInfo { + uint8 *split; + uint8 *flags; +} JSGCPageInfo; + +#define FIRST_THING_PAGE(a) (((a)->base + GC_FLAGS_SIZE) & ~GC_PAGE_MASK) + +static JSGCThing * +gc_new_arena(JSArenaPool *pool) +{ + uint8 *flagp, *split, *pagep, *limit; + JSArena *a; + JSGCThing *thing; + JSGCPageInfo *pi; + + /* Use JS_ArenaAllocate to grab another 9K-net-size hunk of space. */ + flagp = (uint8 *) JS_ArenaAllocate(pool, GC_ARENA_SIZE); + if (!flagp) + return NULL; + a = pool->current; + + /* Reset a->avail to start at the flags split, aka the first thing page. */ + a->avail = FIRST_THING_PAGE(a); + split = pagep = (uint8 *) a->avail; + a->avail += sizeof(JSGCPageInfo); + thing = (JSGCThing *) a->avail; + a->avail += sizeof(JSGCThing); + + /* Initialize the JSGCPageInfo records at the start of every thing page. */ + limit = pagep + GC_THINGS_SIZE; + do { + pi = (JSGCPageInfo *) pagep; + pi->split = split; + pi->flags = flagp; + flagp += GC_PAGE_SIZE >> (GC_THINGS_SHIFT - GC_PAGE_SHIFT); + pagep += GC_PAGE_SIZE; + } while (pagep < limit); + return thing; +} + +static uint8 * +gc_find_flags(void *thing) +{ + JSGCPageInfo *pi; + uint8 *flagp; + + pi = (JSGCPageInfo *) ((jsuword)thing & ~GC_PAGE_MASK); + flagp = pi->flags + ((jsuword)thing & GC_PAGE_MASK) / sizeof(JSGCThing); + if (flagp >= pi->split) + flagp += GC_THINGS_SIZE; + return flagp; +} + typedef void (*GCFinalizeOp)(JSContext *cx, JSGCThing *thing); static GCFinalizeOp gc_finalizers[GCX_NTYPES]; @@ -103,9 +237,22 @@ js_ChangeExternalStringFinalizer(JSStringFinalizeOp oldop, #define METER(x) /* nothing */ #endif +/* Initial size of the gcRootsHash table (SWAG, small enough to amortize). */ +#define GC_ROOTS_SIZE 256 +#define GC_FINALIZE_LEN 1024 + +static JSHashNumber gc_hash_root(const void *key); + JSBool js_InitGC(JSRuntime *rt, uint32 maxbytes) { + JS_ASSERT(sizeof(JSGCThing) == sizeof(JSGCPageInfo)); + JS_ASSERT(sizeof(JSGCThing) >= sizeof(JSObject)); + JS_ASSERT(sizeof(JSGCThing) >= sizeof(JSString)); + JS_ASSERT(sizeof(JSGCThing) >= sizeof(jsdouble)); + JS_ASSERT(GC_FLAGS_SIZE >= GC_PAGE_SIZE); + JS_ASSERT(sizeof(JSStackHeader) >= 2 * sizeof(jsval)); + if (!gc_finalizers[GCX_OBJECT]) { gc_finalizers[GCX_OBJECT] = (GCFinalizeOp)js_FinalizeObject; gc_finalizers[GCX_STRING] = (GCFinalizeOp)js_FinalizeString; @@ -116,13 +263,17 @@ js_InitGC(JSRuntime *rt, uint32 maxbytes) JS_InitArenaPool(&rt->gcArenaPool, "gc-arena", GC_ARENA_SIZE, sizeof(JSGCThing)); - JS_InitArenaPool(&rt->gcFlagsPool, "gc-flags", GC_FLAGS_SIZE, - sizeof(uint8)); + rt->gcFinalVec = malloc(GC_FINALIZE_LEN * sizeof(JSGCThing)); + if (!rt->gcFinalVec) + return JS_FALSE; rt->gcRootsHash = JS_NewHashTable(GC_ROOTS_SIZE, gc_hash_root, JS_CompareValues, JS_CompareValues, NULL, NULL); - if (!rt->gcRootsHash) + if (!rt->gcRootsHash) { + free(rt->gcFinalVec); + rt->gcFinalVec = NULL; return JS_FALSE; + } rt->gcLocksHash = NULL; /* create lazily */ rt->gcMaxBytes = maxbytes; return JS_TRUE; @@ -148,10 +299,9 @@ js_DumpGCStats(JSRuntime *rt, FILE *fp) fprintf(fp, " maximum GC nesting level: %lu\n", rt->gcStats.maxlevel); fprintf(fp, " potentially useful GC calls: %lu\n", rt->gcStats.poke); fprintf(fp, " useless GC calls: %lu\n", rt->gcStats.nopoke); - fprintf(fp, " thing arena corruption: %lu\n", rt->gcStats.badarena); - fprintf(fp, " flags arena corruption: %lu\n", rt->gcStats.badflag); fprintf(fp, " thing arenas freed so far: %lu\n", rt->gcStats.afree); - fprintf(fp, " flags arenas freed so far: %lu\n", rt->gcStats.fafree); + fprintf(fp, " extra stack segments scanned: %lu\n", rt->gcStats.stackseg); + fprintf(fp, " stack segment slots scanned: %lu\n", rt->gcStats.segslots); #ifdef JS_ARENAMETER JS_DumpArenaStats(fp); #endif @@ -168,8 +318,11 @@ js_FinishGC(JSRuntime *rt) js_DumpGCStats(rt, stdout); #endif JS_FinishArenaPool(&rt->gcArenaPool); - JS_FinishArenaPool(&rt->gcFlagsPool); JS_ArenaFinish(); + if (rt->gcFinalVec) { + free(rt->gcFinalVec); + rt->gcFinalVec = NULL; + } JS_HashTableDestroy(rt->gcRootsHash); rt->gcRootsHash = NULL; if (rt->gcLocksHash) { @@ -221,6 +374,11 @@ js_AllocGCThing(JSContext *cx, uintN flags) rt = cx->runtime; JS_LOCK_GC(rt); JS_ASSERT(rt->gcLevel == 0); + if (rt->gcLevel != 0) { + METER(rt->gcStats.finalfail++); + JS_UNLOCK_GC(rt); + return NULL; + } METER(rt->gcStats.alloc++); retry: thing = rt->gcFreeList; @@ -230,30 +388,54 @@ retry: METER(rt->gcStats.freelen--); METER(rt->gcStats.recycle++); } else { - flagp = NULL; - if (rt->gcBytes < rt->gcMaxBytes && + if (rt->gcBytes < rt->gcMaxBytes && (tried_gc || rt->gcMallocBytes < rt->gcMaxBytes)) { - JS_ARENA_ALLOCATE_TYPE(thing, JSGCThing, &rt->gcArenaPool); - if (thing) - JS_ARENA_ALLOCATE_TYPE(flagp, uint8, &rt->gcFlagsPool); - } - if (!thing || !flagp) { - if (thing) - JS_ARENA_RELEASE(&rt->gcArenaPool, thing); - if (!tried_gc) { - JS_UNLOCK_GC(rt); - js_GC(cx, GC_KEEP_ATOMS); - tried_gc = JS_TRUE; - JS_LOCK_GC(rt); - METER(rt->gcStats.retry++); - goto retry; - } - METER(rt->gcStats.fail++); - JS_UNLOCK_GC(rt); - JS_ReportOutOfMemory(cx); - return NULL; - } + /* + * Inline form of JS_ARENA_ALLOCATE adapted to truncate the current + * arena's limit to a GC_PAGE_SIZE boundary, and to skip over every + * GC_PAGE_SIZE-byte-aligned thing (which is actually not a thing, + * it's a JSGCPageInfo record). + */ + JSArenaPool *pool = &rt->gcArenaPool; + JSArena *a = pool->current; + size_t nb = sizeof(JSGCThing); + jsuword p = a->avail; + jsuword q = p + nb; + + if (q > (a->limit & ~GC_PAGE_MASK)) { + thing = gc_new_arena(pool); + } else { + if ((p & GC_PAGE_MASK) == 0) { + /* Beware, p points to a JSGCPageInfo record! */ + p = q; + q += nb; + JS_ArenaCountAllocation(pool, nb); + } + a->avail = q; + thing = (JSGCThing *)p; + } + JS_ArenaCountAllocation(pool, nb); + } + + /* Consider doing a "last ditch" GC if thing couldn't be allocated. */ + if (!thing) { + if (!tried_gc) { + JS_UNLOCK_GC(rt); + js_GC(cx, GC_KEEP_ATOMS); + tried_gc = JS_TRUE; + JS_LOCK_GC(rt); + METER(rt->gcStats.retry++); + goto retry; + } + METER(rt->gcStats.fail++); + JS_UNLOCK_GC(rt); + JS_ReportOutOfMemory(cx); + return NULL; + } + + /* Find the flags pointer given thing's address. */ + flagp = gc_find_flags(thing); } *flagp = (uint8)flags; rt->gcBytes += sizeof(JSGCThing) + sizeof(uint8); @@ -269,31 +451,6 @@ retry: return thing; } -static uint8 * -gc_find_flags(JSRuntime *rt, void *thing) -{ - jsuword index, offset, length; - JSArena *a, *fa; - - index = 0; - for (a = rt->gcArenaPool.first.next; a; a = a->next) { - offset = JS_UPTRDIFF(thing, a->base); - length = a->avail - a->base; - if (offset < length) { - index += offset / sizeof(JSGCThing); - for (fa = rt->gcFlagsPool.first.next; fa; fa = fa->next) { - offset = fa->avail - fa->base; - if (index < offset) - return (uint8 *)fa->base + index; - index -= offset; - } - return NULL; - } - index += length / sizeof(JSGCThing); - } - return NULL; -} - static JSHashNumber gc_hash_thing(const void *key) { @@ -317,15 +474,14 @@ js_LockGCThing(JSContext *cx, void *thing) if (!thing) return JS_TRUE; - rt = cx->runtime; - flagp = gc_find_flags(rt, thing); - if (!flagp) - return JS_FALSE; + flagp = gc_find_flags(thing); + flags = *flagp; ok = JS_TRUE; + rt = cx->runtime; JS_LOCK_GC(rt); - flags = *flagp; lockbits = (flags & GCF_LOCKMASK); + if (lockbits != GCF_LOCKMASK) { if ((flags & GCF_TYPEMASK) == GCX_OBJECT) { /* Objects may require "deep locking", i.e., rooting by value. */ @@ -385,14 +541,13 @@ js_UnlockGCThing(JSContext *cx, void *thing) if (!thing) return JS_TRUE; - rt = cx->runtime; - flagp = gc_find_flags(rt, thing); - if (!flagp) - return JS_FALSE; - - JS_LOCK_GC(rt); + flagp = gc_find_flags(thing); flags = *flagp; + + rt = cx->runtime; + JS_LOCK_GC(rt); lockbits = (flags & GCF_LOCKMASK); + if (lockbits != GCF_LOCKMASK) { if ((flags & GCF_TYPEMASK) == GCX_OBJECT) { /* Defend against a call on an unlocked object. */ @@ -434,10 +589,10 @@ JS_EXPORT_DATA(void *) js_LiveThingToFind; #include "dump_xpc.h" #endif -static const char* -gc_object_class_name(JSRuntime* rt, void* thing) +static const char * +gc_object_class_name(void* thing) { - uint8 *flagp = gc_find_flags(rt, thing); + uint8 *flagp = gc_find_flags(thing); const char *className = ""; if (flagp && ((*flagp & GCF_TYPEMASK) == GCX_OBJECT)) { @@ -452,7 +607,7 @@ gc_object_class_name(JSRuntime* rt, void* thing) ? NULL : JSVAL_TO_PRIVATE(privateValue); - const char* xpcClassName = GetXPCObjectClassName(privateThing); + const char *xpcClassName = GetXPCObjectClassName(privateThing); if (xpcClassName) className = xpcClassName; } @@ -463,8 +618,7 @@ gc_object_class_name(JSRuntime* rt, void* thing) } static void -gc_dump_thing(JSRuntime* rt, JSGCThing *thing, uint8 flags, GCMarkNode *prev, - FILE *fp) +gc_dump_thing(JSGCThing *thing, uint8 flags, GCMarkNode *prev, FILE *fp) { GCMarkNode *next = NULL; char *path = NULL; @@ -476,7 +630,7 @@ gc_dump_thing(JSRuntime* rt, JSGCThing *thing, uint8 flags, GCMarkNode *prev, while (next) { path = JS_sprintf_append(path, "%s(%s).", next->name, - gc_object_class_name(rt, next->thing)); + gc_object_class_name(next->thing)); next = next->next; } if (!path) @@ -491,7 +645,7 @@ gc_dump_thing(JSRuntime* rt, JSGCThing *thing, uint8 flags, GCMarkNode *prev, void *privateThing = JSVAL_IS_VOID(privateValue) ? NULL : JSVAL_TO_PRIVATE(privateValue); - const char* className = gc_object_class_name(rt, thing); + const char *className = gc_object_class_name(thing); fprintf(fp, "object %08p %s", privateThing, className); break; } @@ -514,6 +668,7 @@ static void gc_mark_atom_key_thing(void *thing, void *arg) { JSContext *cx = (JSContext *) arg; + GC_MARK(cx, thing, "atom", NULL); } @@ -544,8 +699,8 @@ js_MarkAtom(JSContext *cx, JSAtom *atom, void *arg) void js_MarkGCThing(JSContext *cx, void *thing, void *arg) { - JSRuntime *rt; uint8 flags, *flagp; + JSRuntime *rt; JSObject *obj; uint32 nslots; jsval v, *vp, *end; @@ -556,30 +711,26 @@ js_MarkGCThing(JSContext *cx, void *thing, void *arg) if (!thing) return; - rt = cx->runtime; - flagp = gc_find_flags(rt, thing); - if (!flagp) - return; - /* Check for something on the GC freelist to handle recycled stack. */ + flagp = gc_find_flags(thing); flags = *flagp; - if (flags == GCF_FINAL) - return; - + JS_ASSERT(flags != GCF_FINAL); #ifdef GC_MARK_DEBUG if (js_LiveThingToFind == thing) - gc_dump_thing(rt, thing, flags, arg, stderr); + gc_dump_thing(thing, flags, arg, stderr); #endif if (flags & GCF_MARK) return; + *flagp |= GCF_MARK; + rt = cx->runtime; METER(if (++rt->gcStats.depth > rt->gcStats.maxdepth) rt->gcStats.maxdepth = rt->gcStats.depth); #ifdef GC_MARK_DEBUG if (js_DumpGCHeap) - gc_dump_thing(rt, thing, flags, arg, js_DumpGCHeap); + gc_dump_thing(thing, flags, arg, js_DumpGCHeap); #endif if ((flags & GCF_TYPEMASK) == GCX_OBJECT) { @@ -666,21 +817,23 @@ gc_root_marker(JSHashEntry *he, intN i, void *arg) JSContext *cx = (JSContext *)arg; #ifdef DEBUG JSArena *a; + jsuword firstpage; JSBool root_points_to_gcArenaPool = JS_FALSE; void *thing = JSVAL_TO_GCTHING(v); for (a = cx->runtime->gcArenaPool.first.next; a; a = a->next) { - if (JS_UPTRDIFF(thing, a->base) < a->avail - a->base) { + firstpage = FIRST_THING_PAGE(a); + if (JS_UPTRDIFF(thing, firstpage) < a->avail - firstpage) { root_points_to_gcArenaPool = JS_TRUE; break; } } if (!root_points_to_gcArenaPool && he->value) { fprintf(stderr, - "Error: The address passed to JS_AddNamedRoot currently " - "holds an invalid jsval.\n " - " This is usually caused by a missing call to JS_RemoveRoot.\n " - " Root name is \"%s\".\n", (const char *) he->value); +"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", + (const char *) he->value); } JS_ASSERT(root_points_to_gcArenaPool); #endif @@ -712,21 +865,64 @@ js_ForceGC(JSContext *cx) JS_ArenaFinish(); } +#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, NULL); \ + } \ + JS_END_MACRO + +/* + * Finalize phase. + * Don't hold the GC lock while running finalizers! + */ +static void +gc_finalize_phase(JSContext *cx, uintN len) +{ + JSRuntime *rt; + JSGCThing *final, *limit, *thing; + uint8 flags, *flagp; + GCFinalizeOp finalizer; + + rt = cx->runtime; + JS_UNLOCK_GC(rt); + for (final = rt->gcFinalVec, limit = final + len; final < limit; final++) { + thing = final->next; + flagp = final->flagp; + flags = *flagp; + finalizer = gc_finalizers[flags & GCF_TYPEMASK]; + if (finalizer) { + *flagp = (uint8)(flags | GCF_FINAL); + finalizer(cx, thing); + } + + /* + * Set flags to GCF_FINAL, signifying that thing is free, but don't + * thread thing onto rt->gcFreeList. We need the GC lock to rebuild + * the freelist below while also looking for free-able arenas. + */ + *flagp = GCF_FINAL; + } + JS_LOCK_GC(rt); +} + void js_GC(JSContext *cx, uintN gcflags) { JSRuntime *rt; JSContext *iter, *acx; - JSArena *a, *ma, *fa, **ap, **fap; - jsval v, *vp, *sp; - jsuword begin, end; JSStackFrame *fp, *chain; - uintN i; - void *mark; - uint8 flags, *flagp; - JSGCThing *thing, *final, **flp, **oflp; - GCFinalizeOp finalizer; - JSBool a_all_clear, f_all_clear; + uintN i, depth, nslots; + JSStackHeader *sh; + JSArena *a, **ap; + uintN finalpos; + uint8 flags, *flagp, *split; + JSGCThing *thing, *limit, *final, **flp, **oflp; + JSBool all_clear; #ifdef JS_THREADSAFE jsword currentThread; uint32 requestDebit; @@ -873,54 +1069,33 @@ restart: } for (fp = chain; fp; fp = chain = chain->dormantNext) { - sp = fp->sp; - if (sp) { - for (a = acx->stackPool.first.next; a; a = a->next) { - /* - * Don't scan beyond the current context's top of stack, - * because we may be nesting a GC from within a call to - * js_AllocGCThing originating from a conversion call made - * by js_Interpret with local variables holding the only - * references to other, unrooted GC-things (e.g., a non- - * newborn object that was just popped off the stack). - * - * Yes, this means we're not doing "exact GC", exactly. - * This temporary failure to collect garbage held only - * by unrecycled stack space should be fixed, but it is - * not a leak bug, and the bloat issue should also be - * small and transient (the next GC will likely get any - * true garbage, as the stack will have pulsated). But - * it deserves an XXX. - */ - begin = a->base; - end = a->avail; - if (acx != cx && - JS_UPTRDIFF(sp, begin) < JS_UPTRDIFF(end, begin)) { - end = (jsuword)sp; - } - for (vp = (jsval *)begin; vp < (jsval *)end; vp++) { - v = *vp; - if (JSVAL_IS_GCTHING(v)) - GC_MARK(cx, JSVAL_TO_GCTHING(v), "stack", NULL); - } - if (end == (jsuword)sp) - break; - } - } - do { - GC_MARK(cx, fp->scopeChain, "scope chain", NULL); - GC_MARK(cx, fp->thisp, "this", NULL); - if (JSVAL_IS_GCTHING(fp->rval)) - GC_MARK(cx, JSVAL_TO_GCTHING(fp->rval), "rval", NULL); - if (fp->callobj) - GC_MARK(cx, fp->callobj, "call object", NULL); - if (fp->argsobj) - GC_MARK(cx, fp->argsobj, "arguments object", NULL); - if (fp->script) - js_MarkScript(cx, fp->script, NULL); - if (fp->sharpArray) - GC_MARK(cx, fp->sharpArray, "sharp array", NULL); - } while ((fp = fp->down) != NULL); + do { + if (fp->callobj) + GC_MARK(cx, fp->callobj, "call object", NULL); + if (fp->argsobj) + GC_MARK(cx, fp->argsobj, "arguments object", NULL); + if (fp->varobj) + GC_MARK(cx, fp->varobj, "variables object", NULL); + if (fp->script) { + js_MarkScript(cx, fp->script, NULL); + depth = fp->script->depth; + if (JS_UPTRDIFF(fp->sp, fp->spbase) < depth * sizeof(jsval)) + nslots = fp->sp - fp->spbase; + else + nslots = depth; + GC_MARK_JSVALS(cx, nslots, fp->spbase, "operand"); + } + GC_MARK(cx, fp->thisp, "this", NULL); + if (fp->argv) + GC_MARK_JSVALS(cx, fp->argc, fp->argv, "arg"); + if (JSVAL_IS_GCTHING(fp->rval)) + GC_MARK(cx, JSVAL_TO_GCTHING(fp->rval), "rval", NULL); + if (fp->vars) + GC_MARK_JSVALS(cx, fp->nvars, fp->vars, "var"); + GC_MARK(cx, fp->scopeChain, "scope chain", NULL); + if (fp->sharpArray) + GC_MARK(cx, fp->sharpArray, "sharp array", NULL); + } while ((fp = fp->down) != NULL); } /* Cleanup temporary "dormant" linkage. */ @@ -942,75 +1117,52 @@ restart: if (acx->rval2set && JSVAL_IS_GCTHING(acx->rval2)) GC_MARK(cx, JSVAL_TO_GCTHING(acx->rval2), "rval2", NULL); #endif + + for (sh = cx->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"); + } } /* - * Sweep phase. - * Mark tempPool for release of finalization records at label out. + * Sweep phase, with interleaved finalize phase. */ - ma = cx->tempPool.current; - mark = JS_ARENA_MARK(&cx->tempPool); + finalpos = 0; js_SweepAtomState(&rt->atomState, gcflags); - fa = rt->gcFlagsPool.first.next; - flagp = (uint8 *)fa->base; for (a = rt->gcArenaPool.first.next; a; a = a->next) { - for (thing = (JSGCThing *)a->base; thing < (JSGCThing *)a->avail; - thing++) { - if (flagp >= (uint8 *)fa->avail) { - fa = fa->next; - JS_ASSERT(fa); - if (!fa) { - METER(rt->gcStats.badflag++); - goto out; - } - flagp = (uint8 *)fa->base; - } - flags = *flagp; - if (flags & GCF_MARK) { - *flagp &= ~GCF_MARK; - } else if (!(flags & (GCF_LOCKMASK | GCF_FINAL))) { - JS_ARENA_ALLOCATE_TYPE(final, JSGCThing, &cx->tempPool); - if (!final) - goto finalize_phase; - final->next = thing; - final->flagp = flagp; - JS_ASSERT(rt->gcBytes >= sizeof(JSGCThing) + sizeof(uint8)); - rt->gcBytes -= sizeof(JSGCThing) + sizeof(uint8); - } - flagp++; - } + flagp = (uint8 *) a->base; + split = (uint8 *) FIRST_THING_PAGE(a); + limit = (JSGCThing *) a->avail; + for (thing = (JSGCThing *) split; thing < limit; thing++) { + if (((jsuword)thing & GC_PAGE_MASK) == 0) { + flagp++; + thing++; + } + flags = *flagp; + if (flags & GCF_MARK) { + *flagp &= ~GCF_MARK; + } else if (!(flags & (GCF_LOCKMASK | GCF_FINAL))) { + if (finalpos == GC_FINALIZE_LEN) { + gc_finalize_phase(cx, finalpos); + finalpos = 0; + } + final = &rt->gcFinalVec[finalpos++]; + final->next = thing; + final->flagp = flagp; + JS_ASSERT(rt->gcBytes >= sizeof(JSGCThing) + sizeof(uint8)); + rt->gcBytes -= sizeof(JSGCThing) + sizeof(uint8); + } + if (++flagp == split) + flagp += GC_THINGS_SIZE; + } } -finalize_phase: /* - * Finalize phase. - * Don't hold the GC lock while running finalizers! + * Last finalize phase, if needed. */ - JS_UNLOCK_GC(rt); - for (final = (JSGCThing *) mark; ; final++) { - if ((jsuword)final >= ma->avail) { - ma = ma->next; - if (!ma) - break; - final = (JSGCThing *)ma->base; - } - thing = final->next; - flagp = final->flagp; - flags = *flagp; - finalizer = gc_finalizers[flags & GCF_TYPEMASK]; - if (finalizer) { - *flagp |= GCF_FINAL; - finalizer(cx, thing); - } - - /* - * Set flags to GCF_FINAL, signifying that thing is free, but don't - * thread thing onto rt->gcFreeList. We need the GC lock to rebuild - * the freelist below while also looking for free-able arenas. - */ - *flagp = GCF_FINAL; - } - JS_LOCK_GC(rt); + if (finalpos) + gc_finalize_phase(cx, finalpos); /* * Free phase. @@ -1020,62 +1172,47 @@ finalize_phase: a = *ap; if (!a) goto out; - thing = (JSGCThing *)a->base; - a_all_clear = f_all_clear = JS_TRUE; + all_clear = JS_TRUE; flp = oflp = &rt->gcFreeList; *flp = NULL; METER(rt->gcStats.freelen = 0); - fap = &rt->gcFlagsPool.first.next; - while ((fa = *fap) != NULL) { - /* XXX optimize by unrolling to use word loads */ - for (flagp = (uint8 *)fa->base; ; flagp++) { - JS_ASSERT(a); - if (!a) { - METER(rt->gcStats.badarena++); - goto out; - } - if (thing >= (JSGCThing *)a->avail) { - if (a_all_clear) { - JS_ARENA_DESTROY(&rt->gcArenaPool, a, ap); - flp = oflp; - METER(rt->gcStats.afree++); - } else { - ap = &a->next; - a_all_clear = JS_TRUE; - oflp = flp; - } - a = *ap; - if (!a) - break; - thing = (JSGCThing *)a->base; - } - if (flagp >= (uint8 *)fa->avail) - break; - if (*flagp != GCF_FINAL) { - a_all_clear = f_all_clear = JS_FALSE; - } else { - thing->flagp = flagp; - *flp = thing; - flp = &thing->next; - METER(rt->gcStats.freelen++); - } - thing++; - } - if (f_all_clear) { - JS_ARENA_DESTROY(&rt->gcFlagsPool, fa, fap); - METER(rt->gcStats.fafree++); - } else { - fap = &fa->next; - f_all_clear = JS_TRUE; - } - } + do { + flagp = (uint8 *) a->base; + split = (uint8 *) FIRST_THING_PAGE(a); + limit = (JSGCThing *) a->avail; + for (thing = (JSGCThing *) split; thing < limit; thing++) { + if (((jsuword)thing & GC_PAGE_MASK) == 0) { + flagp++; + thing++; + } + if (*flagp != GCF_FINAL) { + all_clear = JS_FALSE; + } else { + thing->flagp = flagp; + *flp = thing; + flp = &thing->next; + METER(rt->gcStats.freelen++); + } + if (++flagp == split) + flagp += GC_THINGS_SIZE; + } + + if (all_clear) { + JS_ARENA_DESTROY(&rt->gcArenaPool, a, ap); + flp = oflp; + METER(rt->gcStats.afree++); + } else { + ap = &a->next; + all_clear = JS_TRUE; + oflp = flp; + } + } while ((a = *ap) != NULL); /* Terminate the new freelist. */ *flp = NULL; out: - JS_ARENA_RELEASE(&cx->tempPool, mark); if (rt->gcLevel > 1) { rt->gcLevel = 1; goto restart; diff --git a/mozilla/js/src/jsgc.h b/mozilla/js/src/jsgc.h index a8500896073..961894922d9 100644 --- a/mozilla/js/src/jsgc.h +++ b/mozilla/js/src/jsgc.h @@ -47,10 +47,10 @@ JS_BEGIN_EXTERN_C #define GCX_STRING 1 /* JSString */ #define GCX_DOUBLE 2 /* jsdouble */ #define GCX_EXTERNAL_STRING 3 /* JSString w/ external chars */ -#define GCX_NTYPES_LOG2 3 +#define GCX_NTYPES_LOG2 3 /* type index bits */ #define GCX_NTYPES JS_BIT(GCX_NTYPES_LOG2) -/* GC flag definitions (type index goes in low bits). */ +/* GC flag definitions, must fit in 8 bits (type index goes in the low bits). */ #define GCF_TYPEMASK JS_BITMASK(GCX_NTYPES_LOG2) #define GCF_MARK JS_BIT(GCX_NTYPES_LOG2) #define GCF_FINAL JS_BIT(GCX_NTYPES_LOG2 + 1) @@ -61,7 +61,8 @@ JS_BEGIN_EXTERN_C #if 1 /* * Since we're forcing a GC from JS_GC anyway, don't bother wasting cycles - * loading oldval. XXX remove implied force, etc. + * loading oldval. XXX remove implied force, fix jsinterp.c's "second arg + * ignored", etc. */ #define GC_POKE(cx, oldval) ((cx)->runtime->gcPoke = JS_TRUE) #else @@ -154,6 +155,7 @@ typedef struct JSGCStats { uint32 recycle; /* number of things recycled through gcFreeList */ uint32 retry; /* allocation attempt retries after running the GC */ uint32 fail; /* allocation failures */ + uint32 finalfail; /* finalizer calls allocator failures */ uint32 lock; /* valid lock calls */ uint32 unlock; /* valid unlock calls */ uint32 stuck; /* stuck reference counts seen by lock calls */ @@ -163,10 +165,9 @@ typedef struct JSGCStats { uint32 maxlevel; /* maximum GC nesting (indirect recursion) level */ uint32 poke; /* number of potentially useful GC calls */ uint32 nopoke; /* useless GC calls where js_PokeGC was not set */ - uint32 badarena; /* thing arena corruption */ - uint32 badflag; /* flags arena corruption */ uint32 afree; /* thing arenas freed so far */ - uint32 fafree; /* flags arenas freed so far */ + uint32 stackseg; /* total extraordinary stack segments scanned */ + uint32 segslots; /* total stack segment jsval slots scanned */ } JSGCStats; extern void diff --git a/mozilla/js/src/jsinterp.c b/mozilla/js/src/jsinterp.c index 81c74447d04..44cb14ebdb7 100644 --- a/mozilla/js/src/jsinterp.c +++ b/mozilla/js/src/jsinterp.c @@ -175,7 +175,13 @@ static JSClass prop_iterator_class = { */ #define PUSH(v) (*sp++ = (v)) #define POP() (*--sp) +#ifdef DEBUG +#define SAVE_SP(fp) \ + (JS_ASSERT((fp)->script || !(fp)->spbase || (sp) == (fp)->spbase), \ + (fp)->sp = sp) +#else #define SAVE_SP(fp) ((fp)->sp = sp) +#endif #define RESTORE_SP(fp) (sp = (fp)->sp) /* @@ -186,13 +192,16 @@ static JSClass prop_iterator_class = { * Interpret for these local variables' declarations and uses. */ #define PUSH_OPND(v) (sp[-depth] = (jsval)pc, PUSH(v)) +#define STORE_OPND(n,v) (sp[(n)-depth] = (jsval)pc, sp[n] = (v)) +#define POP_OPND() POP() +#define FETCH_OPND(n) (sp[n]) /* * Push the jsdouble d using sp, depth, and pc from the lexical environment. * Try to convert d to a jsint that fits in a jsval, otherwise GC-alloc space * for it and push a reference. */ -#define PUSH_NUMBER(cx, d) \ +#define STORE_NUMBER(cx, n, d) \ JS_BEGIN_MACRO \ jsint _i; \ jsval _v; \ @@ -204,31 +213,32 @@ static JSClass prop_iterator_class = { if (!ok) \ goto out; \ } \ - PUSH_OPND(_v); \ + STORE_OPND(n, _v); \ JS_END_MACRO -#define POP_NUMBER(cx, d) \ +#define PUSH_NUMBER(cx, d) { sp++; STORE_NUMBER(cx, -1, d); } + +#define FETCH_NUMBER(cx, n, d) \ JS_BEGIN_MACRO \ jsval _v; \ \ - _v = POP(); \ + _v = FETCH_OPND(n); \ VALUE_TO_NUMBER(cx, _v, d); \ JS_END_MACRO /* - * This POP variant is called only for bitwise operators, so we don't bother + * This FETCH variant is called only for bitwise operators, so we don't bother * to inline it. The calls in Interpret must therefore SAVE_SP first! */ static JSBool -PopInt(JSContext *cx, jsint *ip) +FetchInt(JSContext *cx, jsint n, jsint *ip) { JSStackFrame *fp; jsval *sp, v; fp = cx->fp; RESTORE_SP(fp); - v = POP(); - SAVE_SP(fp); + v = FETCH_OPND(n); if (JSVAL_IS_INT(v)) { *ip = JSVAL_TO_INT(v); return JS_TRUE; @@ -237,7 +247,7 @@ PopInt(JSContext *cx, jsint *ip) } static JSBool -PopUint(JSContext *cx, jsuint *ip) +FetchUint(JSContext *cx, jsint n, jsuint *ip) { JSStackFrame *fp; jsval *sp, v; @@ -245,8 +255,7 @@ PopUint(JSContext *cx, jsuint *ip) fp = cx->fp; RESTORE_SP(fp); - v = POP(); - SAVE_SP(fp); + v = FETCH_OPND(n); if (JSVAL_IS_INT(v) && (i = JSVAL_TO_INT(v)) >= 0) { *ip = i; return JS_TRUE; @@ -254,6 +263,9 @@ PopUint(JSContext *cx, jsuint *ip) return js_ValueToECMAUint32(cx, v, (uint32 *)ip); } +#define FETCH_INT(cx, n, ip) (SAVE_SP(fp), FetchInt(cx, n, ip)) +#define FETCH_UINT(cx, n, ip) (SAVE_SP(fp), FetchUint(cx, n, ip)) + /* * Optimized conversion macros that test for the desired type in v before * homing sp and calling a conversion function. @@ -274,7 +286,7 @@ PopUint(JSContext *cx, jsuint *ip) #define POP_BOOLEAN(cx, v, b) \ JS_BEGIN_MACRO \ - v = POP(); \ + v = FETCH_OPND(-1); \ if (v == JSVAL_NULL) { \ b = JS_FALSE; \ } else if (JSVAL_IS_BOOLEAN(v)) { \ @@ -285,6 +297,7 @@ PopUint(JSContext *cx, jsuint *ip) if (!ok) \ goto out; \ } \ + sp--; \ JS_END_MACRO #define VALUE_TO_OBJECT(cx, v, obj) \ @@ -333,7 +346,7 @@ PopUint(JSContext *cx, jsuint *ip) JS_END_MACRO JS_FRIEND_API(jsval *) -js_AllocStack(JSContext *cx, uintN nslots, void **markp) +js_AllocRawStack(JSContext *cx, uintN nslots, void **markp) { jsval *sp; @@ -349,9 +362,92 @@ js_AllocStack(JSContext *cx, uintN nslots, void **markp) return sp; } +JS_FRIEND_API(void) +js_FreeRawStack(JSContext *cx, void *mark) +{ + JS_ARENA_RELEASE(&cx->stackPool, mark); +} + +JS_FRIEND_API(jsval *) +js_AllocStack(JSContext *cx, uintN nslots, void **markp) +{ + jsval *sp, *vp, *end; + JSArena *a; + JSStackHeader *sh; + JSStackFrame *fp; + + /* Callers don't check for zero nslots: we do to avoid empty segments. */ + if (nslots == 0) { + *markp = NULL; + return JS_ARENA_MARK(&cx->stackPool); + } + + /* Allocate 2 extra slots for the stack segment header we'll likely need. */ + sp = js_AllocRawStack(cx, 2 + nslots, markp); + if (!sp) + return NULL; + + /* Try to avoid another header if we can piggyback on the last segment. */ + a = cx->stackPool.current; + sh = cx->stackHeaders; + if (sh && JS_STACK_SEGMENT(sh) + sh->nslots == sp) { + /* Extend the last stack segment, give back the 2 header slots. */ + sh->nslots += nslots; + a->avail -= 2 * sizeof(jsval); + } else { + /* + * Need a new stack segment, so we must initialize unused slots in the + * current frame. See js_GC, just before marking the "operand" jsvals, + * where we scan from fp->spbase to fp->sp or through fp->script->depth + * (whichever covers fewer slots). + */ + fp = cx->fp; + if (fp && fp->spbase && fp->script) { +#ifdef DEBUG + jsuword depthdiff = fp->script->depth * sizeof(jsval); + JS_ASSERT(JS_UPTRDIFF(fp->sp, fp->spbase) <= depthdiff); + JS_ASSERT(JS_UPTRDIFF(*markp, fp->spbase) <= depthdiff); +#endif + end = fp->spbase + fp->script->depth; + if (end > (jsval *) *markp) + end = (jsval *) *markp; + for (vp = fp->sp; vp < end; vp++) + *vp = JSVAL_VOID; + } + + /* Allocate and push a stack segment header from the 2 extra slots. */ + sh = (JSStackHeader *)sp; + sh->nslots = nslots; + sh->down = cx->stackHeaders; + cx->stackHeaders = sh; + sp += 2; + } + + return sp; +} + JS_FRIEND_API(void) js_FreeStack(JSContext *cx, void *mark) { + JSStackHeader *sh; + jsuword slotdiff; + + /* Check for zero nslots allocation special case. */ + if (!mark) + return; + + /* We can assert because js_FreeStack always balances js_AllocStack. */ + sh = cx->stackHeaders; + JS_ASSERT(sh); + + /* If mark is in the current segment, reduce sh->nslots, else pop sh. */ + slotdiff = JS_UPTRDIFF(mark, JS_STACK_SEGMENT(sh)) / sizeof(jsval); + if (slotdiff < (jsuword)sh->nslots) + sh->nslots = slotdiff; + else + cx->stackHeaders = sh->down; + + /* Release the stackPool space allocated since mark was set. */ JS_ARENA_RELEASE(&cx->stackPool, mark); } @@ -609,7 +705,7 @@ have_fun: thisp = parent; } - /* Initialize most of frame, except for varobj, thisp, and scopeChain. */ + /* Initialize frame except for varobj, thisp, sp, spbase, and scopeChain. */ frame.varobj = NULL; frame.callobj = frame.argsobj = NULL; frame.script = script; @@ -622,7 +718,6 @@ have_fun: frame.annotation = NULL; frame.scopeChain = NULL; /* set below for real, after cx->fp is set */ frame.pc = NULL; - frame.sp = sp; frame.sharpDepth = 0; frame.sharpArray = NULL; frame.overrides = 0; @@ -661,7 +756,7 @@ have_fun: /* Check whether we have enough space in the caller's frame. */ if (nalloc > 0) { /* Need space for actuals plus missing formals minus surplus. */ - newsp = js_AllocStack(cx, (uintN)nalloc, NULL); + newsp = js_AllocRawStack(cx, (uintN)nalloc, NULL); if (!newsp) { ok = JS_FALSE; goto out; @@ -676,8 +771,7 @@ have_fun: if (argc) memcpy(newsp, frame.argv, argc * sizeof(jsval)); frame.argv = newsp; - frame.vars = frame.sp = newsp + argc; - RESTORE_SP(&frame); + sp = frame.vars = newsp + argc; } } @@ -694,15 +788,14 @@ have_fun: if (nslots) { surplus = (intN)((jsval *)cx->stackPool.current->avail - frame.vars); if (surplus < nslots) { - newsp = js_AllocStack(cx, (uintN)nslots, NULL); + newsp = js_AllocRawStack(cx, (uintN)nslots, NULL); if (!newsp) { ok = JS_FALSE; goto out; } if (newsp != sp) { /* NB: Discontinuity between argv and vars. */ - frame.vars = frame.sp = newsp; - RESTORE_SP(&frame); + sp = frame.vars = newsp; } } @@ -712,6 +805,7 @@ have_fun: } /* Store the current sp in frame before calling fun. */ + frame.spbase = sp; SAVE_SP(&frame); /* call the hook if present */ @@ -811,17 +905,16 @@ js_InternalInvoke(JSContext *cx, JSObject *obj, jsval fval, uintN flags, ok = JS_FALSE; goto out; } - fp->sp = sp; PUSH(fval); PUSH(OBJECT_TO_JSVAL(obj)); for (i = 0; i < argc; i++) PUSH(argv[i]); - SAVE_SP(fp); + fp->sp = sp; ok = js_Invoke(cx, argc, flags | JSINVOKE_INTERNAL); if (ok) { RESTORE_SP(fp); - *rval = POP(); + *rval = POP_OPND(); } js_FreeStack(cx, mark); @@ -871,6 +964,7 @@ js_Execute(JSContext *cx, JSObject *chain, JSScript *script, JSFunction *fun, frame.scopeChain = chain; frame.pc = NULL; frame.sp = oldfp ? oldfp->sp : NULL; + frame.spbase = frame.sp; frame.sharpDepth = 0; frame.constructing = JS_FALSE; frame.overrides = 0; @@ -943,8 +1037,8 @@ ImportProperty(JSContext *cx, JSObject *obj, jsid id) if (!OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop)) return JS_FALSE; if (!prop) { - str = js_DecompileValueGenerator(cx, JS_FALSE, js_IdToValue(id), - NULL); + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + js_IdToValue(id), NULL); if (str) js_ReportIsNotDefined(cx, JS_GetStringBytes(str)); return JS_FALSE; @@ -954,8 +1048,8 @@ ImportProperty(JSContext *cx, JSObject *obj, jsid id) if (!ok) return JS_FALSE; if (!(attrs & JSPROP_EXPORTED)) { - str = js_DecompileValueGenerator(cx, JS_FALSE, js_IdToValue(id), - NULL); + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + js_IdToValue(id), NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NOT_EXPORTED, @@ -1177,13 +1271,13 @@ js_Interpret(JSContext *cx, jsval *result) * Allocate operand and pc stack slots for the script's worst-case depth. */ depth = (jsint) script->depth; - newsp = js_AllocStack(cx, (uintN)(2 * depth), &mark); + newsp = js_AllocRawStack(cx, (uintN)(2 * depth), &mark); if (!newsp) { ok = JS_FALSE; goto out; } - newsp += depth; - sp = newsp; + sp = newsp + depth; + fp->spbase = sp; SAVE_SP(fp); while (pc < endpc) { @@ -1204,16 +1298,14 @@ js_Interpret(JSContext *cx, jsval *result) tracefp); nuses = cs->nuses; if (nuses) { - fp->sp = sp - nuses; - for (n = 0; n < nuses; n++) { - str = js_DecompileValueGenerator(cx, JS_TRUE, *fp->sp, - NULL); + SAVE_SP(fp); + for (n = -nuses; n < 0; n++) { + str = js_DecompileValueGenerator(cx, n, sp[n], NULL); if (str != NULL) { fprintf(tracefp, "%s %s", (n == 0) ? " inputs:" : ",", JS_GetStringBytes(str)); } - fp->sp++; } putc('\n', tracefp); } @@ -1266,21 +1358,21 @@ js_Interpret(JSContext *cx, jsval *result) break; case JSOP_POPV: - *result = POP(); + *result = POP_OPND(); break; case JSOP_ENTERWITH: - rval = POP(); + rval = FETCH_OPND(-1); VALUE_TO_OBJECT(cx, rval, obj); withobj = js_NewObject(cx, &js_WithClass, obj, fp->scopeChain); if (!withobj) goto out; fp->scopeChain = withobj; - PUSH_OPND(OBJECT_TO_JSVAL(withobj)); + STORE_OPND(-1, OBJECT_TO_JSVAL(withobj)); break; case JSOP_LEAVEWITH: - rval = POP(); + rval = POP_OPND(); JS_ASSERT(JSVAL_IS_OBJECT(rval)); withobj = JSVAL_TO_OBJECT(rval); JS_ASSERT(OBJ_GET_CLASS(cx, withobj) == &js_WithClass); @@ -1292,7 +1384,7 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_RETURN: CHECK_BRANCH(-1); - fp->rval = POP(); + fp->rval = POP_OPND(); if (inlineCallCount) inline_return: { @@ -1310,9 +1402,6 @@ js_Interpret(JSContext *cx, jsval *result) vp = fp->argv - 2; *vp = fp->rval; - /* Restore newsp for sanity-checking assertions about sp. */ - newsp = ifp->oldsp; - /* Restore cx->fp and release the inline frame's space. */ cx->fp = fp = fp->down; JS_ARENA_RELEASE(&cx->stackPool, ifp->mark); @@ -1394,15 +1483,15 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_TOOBJECT: SAVE_SP(fp); - ok = js_ValueToObject(cx, sp[-1], &obj); + ok = js_ValueToObject(cx, FETCH_OPND(-1), &obj); if (!ok) goto out; - sp[-1] = OBJECT_TO_JSVAL(obj); + STORE_OPND(-1, OBJECT_TO_JSVAL(obj)); break; -#define POP_ELEMENT_ID(id) { \ +#define FETCH_ELEMENT_ID(n, id) { \ /* If the index is not a jsint, atomize it. */ \ - id = (jsid) POP(); \ + id = (jsid) FETCH_OPND(n); \ if (JSVAL_IS_INT(id)) { \ atom = NULL; \ } else { \ @@ -1415,9 +1504,11 @@ js_Interpret(JSContext *cx, jsval *result) } \ } +#define POP_ELEMENT_ID(id) { FETCH_ELEMENT_ID(-1, id); sp--; } + #if JS_HAS_IN_OPERATOR case JSOP_IN: - rval = POP(); + rval = POP_OPND(); if (JSVAL_IS_PRIMITIVE(rval)) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_IN_NOT_OBJECT); @@ -1425,11 +1516,11 @@ js_Interpret(JSContext *cx, jsval *result) goto out; } obj = JSVAL_TO_OBJECT(rval); - POP_ELEMENT_ID(id); + FETCH_ELEMENT_ID(-1, id); ok = OBJ_LOOKUP_PROPERTY(cx, obj, id, &obj2, &prop); if (!ok) goto out; - PUSH_OPND(BOOLEAN_TO_JSVAL(prop != NULL)); + STORE_OPND(-1, BOOLEAN_TO_JSVAL(prop != NULL)); if (prop) OBJ_DROP_PROPERTY(cx, obj2, prop); break; @@ -1440,9 +1531,10 @@ js_Interpret(JSContext *cx, jsval *result) * Handle JSOP_FORPROP first, so the cost of the goto do_forinloop * is not paid for the more common cases. */ - lval = POP(); + lval = FETCH_OPND(-1); atom = GET_ATOM(cx, script, pc); id = (jsid)atom; + i = -2; goto do_forinloop; case JSOP_FORNAME: @@ -1482,14 +1574,14 @@ js_Interpret(JSContext *cx, jsval *result) * enumerator until after the next property has been acquired, via * a JSOP_ENUMELEM bytecode. */ - /* FALL THROUGH */ + i = -1; do_forinloop: /* * ECMA-compatible for/in evals the object just once, before loop. * Bad old bytecodes (since removed) did it on every iteration. */ - obj = JSVAL_TO_OBJECT(sp[-1]); + obj = JSVAL_TO_OBJECT(sp[i]); /* If the thing to the right of 'in' has no properties, break. */ if (!obj) { @@ -1510,7 +1602,7 @@ js_Interpret(JSContext *cx, jsval *result) * rather than a native struct so that the iteration state is * cleaned up via GC if the for-in loop terminates abruptly.) */ - vp = sp - 2; + vp = &sp[i - 1]; rval = *vp; /* Is this the first iteration ? */ @@ -1605,8 +1697,7 @@ js_Interpret(JSContext *cx, jsval *result) goto out; } - /* Hold via sp[0] in case the GC runs under OBJ_SET_PROPERTY. */ - rval = sp[0] = STRING_TO_JSVAL(str); + rval = STRING_TO_JSVAL(str); } switch (op) { @@ -1642,39 +1733,46 @@ js_Interpret(JSContext *cx, jsval *result) rval = JSVAL_TRUE; end_forinloop: + sp += i + 1; PUSH_OPND(rval); break; case JSOP_DUP: - JS_ASSERT(sp > newsp); + JS_ASSERT(sp > fp->spbase); rval = sp[-1]; PUSH_OPND(rval); break; case JSOP_DUP2: - JS_ASSERT(sp - 1 > newsp); - lval = sp[-2]; - rval = sp[-1]; + JS_ASSERT(sp - 1 > fp->spbase); + lval = FETCH_OPND(-2); + rval = FETCH_OPND(-1); PUSH_OPND(lval); PUSH_OPND(rval); break; -#define PROPERTY_OP(call) { \ +#define PROPERTY_OP(n, call) { \ /* Pop the left part and resolve it to a non-null object. */ \ - lval = POP(); \ + lval = FETCH_OPND(n); \ VALUE_TO_OBJECT(cx, lval, obj); \ \ /* Get or set the property, set ok false if error, true if success. */ \ + SAVE_SP(fp); \ call; \ if (!ok) \ goto out; \ } -#define ELEMENT_OP(call) { \ - POP_ELEMENT_ID(id); \ - PROPERTY_OP(call); \ +#define ELEMENT_OP(n, call) { \ + FETCH_ELEMENT_ID(n, id); \ + PROPERTY_OP(n-1, call); \ } +/* + * Direct callers, i.e. those who do not wrap CACHED_GET and CACHED_SET calls + * in PROPERT_OP or ELEMENT_OP macro calls must SAVE_SP(fp); beforehand, just + * in case a getter or setter function is invoked. + */ #define CACHED_GET(call) { \ JS_LOCK_OBJ(cx, obj); \ PROPERTY_CACHE_TEST(&rt->propertyCache, obj, id, prop); \ @@ -1695,7 +1793,6 @@ js_Interpret(JSContext *cx, jsval *result) } \ } else { \ JS_UNLOCK_OBJ(cx, obj); \ - SAVE_SP(fp); \ ok = call; \ /* No fill here: js_GetProperty fills the cache. */ \ } \ @@ -1729,7 +1826,6 @@ js_Interpret(JSContext *cx, jsval *result) } \ } else { \ JS_UNLOCK_OBJ(cx, obj); \ - SAVE_SP(fp); \ ok = call; \ /* No fill here: js_SetProperty writes through the cache. */ \ } \ @@ -1738,14 +1834,14 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_SETCONST: obj = fp->varobj; atom = GET_ATOM(cx, script, pc); - rval = POP(); + rval = FETCH_OPND(-1); ok = OBJ_DEFINE_PROPERTY(cx, obj, (jsid)atom, rval, NULL, NULL, JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY, NULL); if (!ok) goto out; - PUSH_OPND(rval); + STORE_OPND(-1, rval); break; case JSOP_BINDNAME: @@ -1761,25 +1857,27 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_SETNAME: atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - rval = POP(); - lval = POP(); + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); JS_ASSERT(!JSVAL_IS_PRIMITIVE(lval)); obj = JSVAL_TO_OBJECT(lval); + SAVE_SP(fp); CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); if (!ok) goto out; - PUSH_OPND(rval); + sp--; + STORE_OPND(-1, rval); break; #define INTEGER_OP(OP, EXTRA_CODE) { \ SAVE_SP(fp); \ - ok = PopInt(cx, &j) && PopInt(cx, &i); \ - RESTORE_SP(fp); \ + ok = FetchInt(cx, -1, &j) && FetchInt(cx, -2, &i); \ if (!ok) \ goto out; \ EXTRA_CODE \ i = i OP j; \ - PUSH_NUMBER(cx, i); \ + sp--; \ + STORE_NUMBER(cx, -1, i); \ } #define BITWISE_OP(OP) INTEGER_OP(OP, (void) 0;) @@ -1807,8 +1905,8 @@ js_Interpret(JSContext *cx, jsval *result) #endif #define RELATIONAL_OP(OP) { \ - rval = POP(); \ - lval = POP(); \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ /* Optimize for two int-tagged operands (typical loop control). */ \ if ((lval & rval) & JSVAL_INT) { \ ltmp = lval ^ JSVAL_VOID; \ @@ -1822,27 +1920,24 @@ js_Interpret(JSContext *cx, jsval *result) } \ } else { \ VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_NUMBER, &lval); \ - /* Need lval for strcmp or ToNumber later, so root it via sp[0]. */ \ - sp[0] = lval; \ VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_NUMBER, &rval); \ if (JSVAL_IS_STRING(lval) && JSVAL_IS_STRING(rval)) { \ str = JSVAL_TO_STRING(lval); \ str2 = JSVAL_TO_STRING(rval); \ cond = js_CompareStrings(str, str2) OP 0; \ } else { \ - /* Need rval after ToNumber(lval), so root it via sp[1]. */ \ - sp[1] = rval; \ VALUE_TO_NUMBER(cx, lval, d); \ VALUE_TO_NUMBER(cx, rval, d2); \ cond = COMPARE_DOUBLES(d, OP, d2, JS_FALSE); \ } \ } \ - PUSH_OPND(BOOLEAN_TO_JSVAL(cond)); \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ } #define EQUALITY_OP(OP, IFNAN) { \ - rval = POP(); \ - lval = POP(); \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ ltmp = JSVAL_TAG(lval); \ rtmp = JSVAL_TAG(rval); \ if (ltmp == rtmp) { \ @@ -1876,15 +1971,14 @@ js_Interpret(JSContext *cx, jsval *result) str2 = JSVAL_TO_STRING(rval); \ cond = js_CompareStrings(str, str2) OP 0; \ } else { \ - /* Need rval after ToNumber(lval), so root it via sp[1]. */ \ - sp[1] = rval; \ VALUE_TO_NUMBER(cx, lval, d); \ VALUE_TO_NUMBER(cx, rval, d2); \ cond = COMPARE_DOUBLES(d, OP, d2, IFNAN); \ } \ } \ } \ - PUSH_OPND(BOOLEAN_TO_JSVAL(cond)); \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ } case JSOP_EQ: @@ -1897,8 +1991,8 @@ js_Interpret(JSContext *cx, jsval *result) #if !JS_BUG_FALLIBLE_EQOPS #define NEW_EQUALITY_OP(OP, IFNAN) { \ - rval = POP(); \ - lval = POP(); \ + rval = FETCH_OPND(-1); \ + lval = FETCH_OPND(-2); \ ltmp = JSVAL_TAG(lval); \ rtmp = JSVAL_TAG(rval); \ if (ltmp == rtmp) { \ @@ -1926,7 +2020,8 @@ js_Interpret(JSContext *cx, jsval *result) cond = lval OP rval; \ } \ } \ - PUSH_OPND(BOOLEAN_TO_JSVAL(cond)); \ + sp--; \ + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); \ } case JSOP_NEW_EQ: @@ -1984,13 +2079,13 @@ js_Interpret(JSContext *cx, jsval *result) uint32 u; SAVE_SP(fp); - ok = PopInt(cx, &j) && PopUint(cx, &u); - RESTORE_SP(fp); + ok = FetchInt(cx, -1, &j) && FetchUint(cx, -2, &u); if (!ok) goto out; j &= 31; d = u >> j; - PUSH_NUMBER(cx, d); + sp--; + STORE_NUMBER(cx, -1, d); break; } @@ -1999,31 +2094,19 @@ js_Interpret(JSContext *cx, jsval *result) #undef SIGNED_SHIFT_OP case JSOP_ADD: - rval = rtmp = POP(); - lval = ltmp = POP(); - VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_VOID, &lval); - if ((cond = JSVAL_IS_STRING(lval)) != 0) { - /* - * Keep lval on the stack so it isn't GC'd during either the - * next VALUE_TO_PRIMITIVE or the js_ValueToString(cx, rval). - */ - sp[0] = lval; - } - VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_VOID, &rval); - if (cond || JSVAL_IS_STRING(rval)) { + rval = FETCH_OPND(-1); + lval = FETCH_OPND(-2); + VALUE_TO_PRIMITIVE(cx, lval, JSTYPE_VOID, <mp); + VALUE_TO_PRIMITIVE(cx, rval, JSTYPE_VOID, &rtmp); + if ((cond = JSVAL_IS_STRING(ltmp)) || JSVAL_IS_STRING(rtmp)) { if (cond) { - str = JSVAL_TO_STRING(lval); + str = JSVAL_TO_STRING(ltmp); SAVE_SP(fp); - ok = (str2 = js_ValueToString(cx, rval)) != NULL; + ok = (str2 = js_ValueToString(cx, rtmp)) != NULL; } else { - /* - * Keep rval on the stack so it isn't GC'd during the next - * js_ValueToString. - */ - sp[1] = rval; - str2 = JSVAL_TO_STRING(rval); + str2 = JSVAL_TO_STRING(rtmp); SAVE_SP(fp); - ok = (str = js_ValueToString(cx, lval)) != NULL; + ok = (str = js_ValueToString(cx, ltmp)) != NULL; } if (!ok) goto out; @@ -2048,20 +2131,23 @@ js_Interpret(JSContext *cx, jsval *result) goto out; } } - PUSH_OPND(STRING_TO_JSVAL(str3)); + sp--; + STORE_OPND(-1, STRING_TO_JSVAL(str3)); } else { - VALUE_TO_NUMBER(cx, ltmp, d); - VALUE_TO_NUMBER(cx, rtmp, d2); + VALUE_TO_NUMBER(cx, lval, d); + VALUE_TO_NUMBER(cx, rval, d2); d += d2; - PUSH_NUMBER(cx, d); + sp--; + STORE_NUMBER(cx, -1, d); } break; #define BINARY_OP(OP) { \ - POP_NUMBER(cx, d2); \ - POP_NUMBER(cx, d); \ + FETCH_NUMBER(cx, -1, d2); \ + FETCH_NUMBER(cx, -2, d); \ d = d OP d2; \ - PUSH_NUMBER(cx, d); \ + sp--; \ + STORE_NUMBER(cx, -1, d); \ } case JSOP_SUB: @@ -2073,8 +2159,9 @@ js_Interpret(JSContext *cx, jsval *result) break; case JSOP_DIV: - POP_NUMBER(cx, d2); - POP_NUMBER(cx, d); + FETCH_NUMBER(cx, -1, d2); + FETCH_NUMBER(cx, -2, d); + sp--; if (d2 == 0) { #ifdef XP_PC /* XXX MSVC miscompiles such that (NaN == 0) */ @@ -2088,25 +2175,26 @@ js_Interpret(JSContext *cx, jsval *result) rval = DOUBLE_TO_JSVAL(rt->jsNegativeInfinity); else rval = DOUBLE_TO_JSVAL(rt->jsPositiveInfinity); - PUSH_OPND(rval); + STORE_OPND(-1, rval); } else { d /= d2; - PUSH_NUMBER(cx, d); + STORE_NUMBER(cx, -1, d); } break; case JSOP_MOD: - POP_NUMBER(cx, d2); - POP_NUMBER(cx, d); + FETCH_NUMBER(cx, -1, d2); + FETCH_NUMBER(cx, -2, d); + sp--; if (d2 == 0) { - PUSH_OPND(DOUBLE_TO_JSVAL(rt->jsNaN)); + STORE_OPND(-1, DOUBLE_TO_JSVAL(rt->jsNaN)); } else { #ifdef XP_PC /* Workaround MS fmod bug where 42 % (1/0) => NaN, not 42. */ if (!(JSDOUBLE_IS_FINITE(d) && JSDOUBLE_IS_INFINITE(d2))) #endif d = fmod(d, d2); - PUSH_NUMBER(cx, d); + STORE_NUMBER(cx, -1, d); } break; @@ -2116,17 +2204,15 @@ js_Interpret(JSContext *cx, jsval *result) break; case JSOP_BITNOT: - SAVE_SP(fp); - ok = PopInt(cx, &i); - RESTORE_SP(fp); + ok = FETCH_INT(cx, -1, &i); if (!ok) goto out; d = (jsdouble) ~i; - PUSH_NUMBER(cx, d); + STORE_NUMBER(cx, -1, d); break; case JSOP_NEG: - POP_NUMBER(cx, d); + FETCH_NUMBER(cx, -1, d); #ifdef HPUX /* * Negation of a zero doesn't produce a negative @@ -2137,12 +2223,12 @@ js_Interpret(JSContext *cx, jsval *result) #else d = -d; #endif - PUSH_NUMBER(cx, d); + STORE_NUMBER(cx, -1, d); break; case JSOP_POS: - POP_NUMBER(cx, d); - PUSH_NUMBER(cx, d); + FETCH_NUMBER(cx, -1, d); + STORE_NUMBER(cx, -1, d); break; case JSOP_NEW: @@ -2153,7 +2239,7 @@ js_Interpret(JSContext *cx, jsval *result) do_new: #endif vp = sp - (2 + argc); - JS_ASSERT(vp >= newsp); + JS_ASSERT(vp >= fp->spbase); fun = NULL; obj2 = NULL; @@ -2251,19 +2337,18 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_DELPROP: atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - SAVE_SP(fp); - PROPERTY_OP(ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); - PUSH_OPND(rval); + PROPERTY_OP(-1, ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); + STORE_OPND(-1, rval); break; case JSOP_DELELEM: - SAVE_SP(fp); - ELEMENT_OP(ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); - PUSH_OPND(rval); + ELEMENT_OP(-1, ok = OBJ_DELETE_PROPERTY(cx, obj, id, &rval)); + sp--; + STORE_OPND(-1, rval); break; case JSOP_TYPEOF: - rval = POP(); + rval = POP_OPND(); type = JS_TypeOfValue(cx, rval); atom = rt->atomState.typeAtoms[type]; str = ATOM_TO_STRING(atom); @@ -2271,7 +2356,7 @@ js_Interpret(JSContext *cx, jsval *result) break; case JSOP_VOID: - (void) POP(); + (void) POP_OPND(); PUSH_OPND(JSVAL_VOID); break; @@ -2302,7 +2387,7 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_PROPDEC: atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - lval = POP(); + lval = POP_OPND(); goto do_incop; case JSOP_INCELEM: @@ -2310,12 +2395,13 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_ELEMINC: case JSOP_ELEMDEC: POP_ELEMENT_ID(id); - lval = POP(); + lval = POP_OPND(); do_incop: VALUE_TO_OBJECT(cx, lval, obj); /* The operand must contain a number. */ + SAVE_SP(fp); CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval)); if (!ok) goto out; @@ -2385,40 +2471,46 @@ js_Interpret(JSContext *cx, jsval *result) /* Get an immediate atom naming the property. */ atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - PROPERTY_OP(CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); - PUSH_OPND(rval); + PROPERTY_OP(-1, CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + STORE_OPND(-1, rval); break; case JSOP_SETPROP: /* Pop the right-hand side into rval for OBJ_SET_PROPERTY. */ - rval = POP(); + rval = FETCH_OPND(-1); /* Get an immediate atom naming the property. */ atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - PROPERTY_OP(CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); - PUSH_OPND(rval); + PROPERTY_OP(-2, CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); break; case JSOP_GETELEM: - ELEMENT_OP(CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); - PUSH_OPND(rval); + ELEMENT_OP(-1, CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); break; case JSOP_SETELEM: - rval = POP(); - ELEMENT_OP(CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); - PUSH_OPND(rval); + rval = FETCH_OPND(-1); + ELEMENT_OP(-2, CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval))); + sp -= 2; + STORE_OPND(-1, rval); break; case JSOP_ENUMELEM: - POP_ELEMENT_ID(id); - lval = POP(); + /* Funky: the value to set is under the [obj, id] pair. */ + FETCH_ELEMENT_ID(-1, id); + lval = FETCH_OPND(-2); VALUE_TO_OBJECT(cx, lval, obj); - rval = POP(); + rval = FETCH_OPND(-3); + SAVE_SP(fp); CACHED_SET(OBJ_SET_PROPERTY(cx, obj, id, &rval)); if (!ok) goto out; + sp -= 3; break; case JSOP_PUSHOBJ: @@ -2441,7 +2533,6 @@ js_Interpret(JSContext *cx, jsval *result) /* inline_call: */ { uintN nframeslots, nvars; - jsval *oldsp; void *newmark; JSInlineFrame *newifp; JSInterpreterHook hook; @@ -2468,9 +2559,8 @@ js_Interpret(JSContext *cx, jsval *result) depth = (jsint) script->depth; /* Allocate the frame and space for vars and operands. */ - oldsp = newsp; - newsp = js_AllocStack(cx, nframeslots + nvars + 2 * depth, - &newmark); + newsp = js_AllocRawStack(cx, nframeslots + nvars + 2 * depth, + &newmark); if (!newsp) { ok = JS_FALSE; goto bad_inline_call; @@ -2489,13 +2579,12 @@ js_Interpret(JSContext *cx, jsval *result) newifp->frame.vars = newsp; newifp->frame.down = fp; newifp->frame.scopeChain = OBJ_GET_PARENT(cx, obj); - newifp->oldsp = oldsp; newifp->mark = newmark; /* Compute the 'this' parameter now that argv is set. */ ok = ComputeThis(cx, JSVAL_TO_OBJECT(vp[1]), &newifp->frame); if (!ok) { - js_FreeStack(cx, newmark); + js_FreeRawStack(cx, newmark); goto bad_inline_call; } @@ -2504,7 +2593,7 @@ js_Interpret(JSContext *cx, jsval *result) while (nvars--) PUSH(JSVAL_VOID); sp += depth; - newsp = sp; + newifp->frame.spbase = sp; SAVE_SP(&newifp->frame); /* Call the debugger hook if present. */ @@ -2531,7 +2620,6 @@ js_Interpret(JSContext *cx, jsval *result) bad_inline_call: script = fp->script; depth = (jsint) script->depth; - newsp = oldsp; goto out; } @@ -2558,8 +2646,10 @@ js_Interpret(JSContext *cx, jsval *result) */ PUSH_OPND(cx->rval2); cx->rval2set = JS_FALSE; - ELEMENT_OP(CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); - PUSH_OPND(rval); + ELEMENT_OP(-1, + CACHED_GET(OBJ_GET_PROPERTY(cx, obj, id, &rval))); + sp--; + STORE_OPND(-1, rval); } #endif obj = NULL; @@ -2690,16 +2780,15 @@ js_Interpret(JSContext *cx, jsval *result) */ if (cx->version == JSVERSION_DEFAULT || cx->version >= JSVERSION_1_4) { - rval = POP(); + rval = POP_OPND(); if (!JSVAL_IS_INT(rval)) break; i = JSVAL_TO_INT(rval); } else { - SAVE_SP(fp); - ok = PopInt(cx, &i); - RESTORE_SP(fp); + ok = FETCH_INT(cx, -1, &i); if (!ok) goto out; + sp--; } pc2 += JUMP_OFFSET_LEN; @@ -2717,7 +2806,7 @@ js_Interpret(JSContext *cx, jsval *result) break; case JSOP_LOOKUPSWITCH: - lval = POP(); + lval = POP_OPND(); pc2 = pc; len = GET_JUMP_OFFSET(pc2); @@ -2821,18 +2910,21 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_IMPORTALL: id = (jsid)JSVAL_VOID; - PROPERTY_OP(ok = ImportProperty(cx, obj, id)); + PROPERTY_OP(-1, ok = ImportProperty(cx, obj, id)); + sp--; break; case JSOP_IMPORTPROP: /* Get an immediate atom naming the property. */ atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - PROPERTY_OP(ok = ImportProperty(cx, obj, id)); + PROPERTY_OP(-1, ok = ImportProperty(cx, obj, id)); + sp--; break; case JSOP_IMPORTELEM: - ELEMENT_OP(ok = ImportProperty(cx, obj, id)); + ELEMENT_OP(-1, ok = ImportProperty(cx, obj, id)); + sp -= 2; break; #endif /* JS_HAS_EXPORT_IMPORT */ @@ -2883,7 +2975,7 @@ js_Interpret(JSContext *cx, jsval *result) JS_ASSERT(slot < fp->fun->nargs); vp = &fp->argv[slot]; GC_POKE(cx, *vp); - *vp = sp[-1]; + *vp = FETCH_OPND(-1); break; case JSOP_GETVAR: @@ -2899,7 +2991,7 @@ js_Interpret(JSContext *cx, jsval *result) JS_ASSERT(slot < fp->fun->nvars); vp = &fp->vars[slot]; GC_POKE(cx, *vp); - *vp = sp[-1]; + *vp = FETCH_OPND(-1); break; case JSOP_DEFCONST: @@ -3032,6 +3124,7 @@ js_Interpret(JSContext *cx, jsval *result) * current scope chain, and then making the new object the parent * of the Function object clone. */ + SAVE_SP(fp); parent = js_ConstructObject(cx, &js_ObjectClass, NULL, fp->scopeChain); if (!parent) { @@ -3171,31 +3264,35 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_SETPROP: atom = GET_ATOM(cx, script, pc); id = (jsid)atom; - rval = POP(); + i = -1; + rval = FETCH_OPND(i); goto gs_pop_lval; case JSOP_SETELEM: - rval = POP(); - POP_ELEMENT_ID(id); + rval = FETCH_OPND(-1); + i = -2; + FETCH_ELEMENT_ID(i, id); gs_pop_lval: - lval = POP(); + lval = FETCH_OPND(i-1); VALUE_TO_OBJECT(cx, lval, obj); break; #if JS_HAS_INITIALIZERS case JSOP_INITPROP: - JS_ASSERT(sp - newsp >= 2); - rval = POP(); + JS_ASSERT(sp - fp->spbase >= 2); + i = -1; + rval = FETCH_OPND(i); atom = GET_ATOM(cx, script, pc); id = (jsid)atom; goto gs_get_lval; case JSOP_INITELEM: - JS_ASSERT(sp - newsp >= 3); - rval = POP(); - POP_ELEMENT_ID(id); + JS_ASSERT(sp - fp->spbase >= 3); + rval = FETCH_OPND(-1); + i = -2; + FETCH_ELEMENT_ID(i, id); gs_get_lval: - lval = sp[-1]; + lval = FETCH_OPND(i-1); JS_ASSERT(JSVAL_IS_OBJECT(lval)); obj = JSVAL_TO_OBJECT(lval); break; @@ -3242,8 +3339,10 @@ js_Interpret(JSContext *cx, jsval *result) attrs, NULL); if (!ok) goto out; + + sp += i; if (cs->ndefs) - PUSH_OPND(rval); + STORE_OPND(-1, rval); break; #endif /* JS_HAS_GETTER_SETTER */ @@ -3258,33 +3357,35 @@ js_Interpret(JSContext *cx, jsval *result) fp->sharpArray = NULL; /* Re-set the newborn root to the top of this object tree. */ - JS_ASSERT(sp - newsp >= 1); - lval = sp[-1]; + JS_ASSERT(sp - fp->spbase >= 1); + lval = FETCH_OPND(-1); JS_ASSERT(JSVAL_IS_OBJECT(lval)); cx->newborn[GCX_OBJECT] = JSVAL_TO_GCTHING(lval); break; case JSOP_INITPROP: /* Pop the property's value into rval. */ - JS_ASSERT(sp - newsp >= 2); - rval = POP(); + JS_ASSERT(sp - fp->spbase >= 2); + rval = FETCH_OPND(-1); /* Get the immediate property name into id. */ atom = GET_ATOM(cx, script, pc); id = (jsid)atom; + i = -1; goto do_init; case JSOP_INITELEM: /* Pop the element's value into rval. */ - JS_ASSERT(sp - newsp >= 3); - rval = POP(); + JS_ASSERT(sp - fp->spbase >= 3); + rval = FETCH_OPND(-1); /* Pop and conditionally atomize the element id. */ - POP_ELEMENT_ID(id); + FETCH_ELEMENT_ID(-2, id); + i = -2; do_init: /* Find the object being initialized at top of stack. */ - lval = sp[-1]; + lval = FETCH_OPND(i-1); JS_ASSERT(JSVAL_IS_OBJECT(lval)); obj = JSVAL_TO_OBJECT(lval); @@ -3292,6 +3393,7 @@ js_Interpret(JSContext *cx, jsval *result) ok = OBJ_SET_PROPERTY(cx, obj, id, &rval); if (!ok) goto out; + sp += i; break; #if JS_HAS_SHARP_VARS @@ -3307,7 +3409,7 @@ js_Interpret(JSContext *cx, jsval *result) } i = (jsint) GET_ATOM_INDEX(pc); id = (jsid) INT_TO_JSVAL(i); - rval = sp[-1]; + rval = FETCH_OPND(-1); if (JSVAL_IS_PRIMITIVE(rval)) { char numBuf[12]; JS_snprintf(numBuf, sizeof numBuf, "%u", (unsigned) i); @@ -3355,7 +3457,7 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_SETSP: i = (jsint) GET_ATOM_INDEX(pc); JS_ASSERT(i >= 0); - sp = newsp + i; + sp = fp->spbase + i; break; case JSOP_GOSUB: @@ -3378,22 +3480,22 @@ js_Interpret(JSContext *cx, jsval *result) case JSOP_THROW: cx->throwing = JS_TRUE; - cx->exception = POP(); + cx->exception = POP_OPND(); ok = JS_FALSE; /* let the code at out try to catch the exception. */ goto out; case JSOP_INITCATCHVAR: /* Pop the property's value into rval. */ - JS_ASSERT(sp - newsp >= 2); - rval = POP(); + JS_ASSERT(sp - fp->spbase >= 2); + rval = POP_OPND(); /* Get the immediate catch variable name into id. */ atom = GET_ATOM(cx, script, pc); id = (jsid)atom; /* Find the object being initialized at top of stack. */ - lval = sp[-1]; + lval = FETCH_OPND(-1); JS_ASSERT(JSVAL_IS_OBJECT(lval)); obj = JSVAL_TO_OBJECT(lval); @@ -3407,9 +3509,10 @@ js_Interpret(JSContext *cx, jsval *result) #if JS_HAS_INSTANCEOF case JSOP_INSTANCEOF: - rval = POP(); + rval = FETCH_OPND(-1); if (JSVAL_IS_PRIMITIVE(rval)) { - str = js_DecompileValueGenerator(cx, JS_TRUE, rval, NULL); + SAVE_SP(fp); + str = js_DecompileValueGenerator(cx, -1, rval, NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BAD_INSTANCEOF_RHS, @@ -3419,7 +3522,7 @@ js_Interpret(JSContext *cx, jsval *result) goto out; } obj = JSVAL_TO_OBJECT(rval); - lval = POP(); + lval = FETCH_OPND(-2); cond = JS_FALSE; if (obj->map->ops->hasInstance) { SAVE_SP(fp); @@ -3427,7 +3530,8 @@ js_Interpret(JSContext *cx, jsval *result) if (!ok) goto out; } - PUSH_OPND(BOOLEAN_TO_JSVAL(cond)); + sp--; + STORE_OPND(-1, BOOLEAN_TO_JSVAL(cond)); break; #endif /* JS_HAS_INSTANCEOF */ @@ -3479,16 +3583,14 @@ js_Interpret(JSContext *cx, jsval *result) ndefs = cs->ndefs; if (ndefs) { - fp->sp = sp - ndefs; - for (n = 0; n < ndefs; n++) { - str = js_DecompileValueGenerator(cx, JS_TRUE, *fp->sp, - NULL); + SAVE_SP(fp); + for (n = -ndefs; n < 0; n++) { + str = js_DecompileValueGenerator(cx, n, sp[n], NULL); if (str) { fprintf(tracefp, "%s %s", (n == 0) ? " output:" : ",", JS_GetStringBytes(str)); } - fp->sp++; } putc('\n', tracefp); } @@ -3551,7 +3653,7 @@ no_catch: /* * Restore the previous frame's execution state. */ - js_FreeStack(cx, mark); + js_FreeRawStack(cx, mark); if (currentVersion != originalVersion) JS_SetVersion(cx, originalVersion); cx->interpLevel--; diff --git a/mozilla/js/src/jsinterp.h b/mozilla/js/src/jsinterp.h index 7be69b9f3e3..8fe97293363 100644 --- a/mozilla/js/src/jsinterp.h +++ b/mozilla/js/src/jsinterp.h @@ -62,6 +62,7 @@ struct JSStackFrame { JSObject *scopeChain; /* scope chain */ jsbytecode *pc; /* program counter */ jsval *sp; /* stack pointer */ + jsval *spbase; /* operand stack base */ uintN sharpDepth; /* array/object initializer depth */ JSObject *sharpArray; /* scope for #n= initializer vars */ JSPackedBool constructing; /* true if called via new operator */ @@ -73,7 +74,6 @@ struct JSStackFrame { typedef struct JSInlineFrame { JSStackFrame frame; /* base struct */ - jsval *oldsp; /* old frame's operand stack base */ void *mark; /* mark before inline frame */ void *hookData; /* debugger call hook data */ } JSInlineFrame; diff --git a/mozilla/js/src/jsnum.c b/mozilla/js/src/jsnum.c index a58c514829f..a187d5823dd 100644 --- a/mozilla/js/src/jsnum.c +++ b/mozilla/js/src/jsnum.c @@ -628,7 +628,7 @@ js_ValueToNumber(JSContext *cx, jsval v, jsdouble *dp) *dp = JSVAL_TO_BOOLEAN(v) ? 1 : 0; } else { #if JS_BUG_FALLIBLE_TONUM - str = js_DecompileValueGenerator(cx, JS_TRUE, v, NULL); + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); badstr: if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NAN, @@ -714,7 +714,7 @@ js_ValueToInt32(JSContext *cx, jsval v, int32 *ip) if (!js_ValueToNumber(cx, v, &d)) return JS_FALSE; if (JSDOUBLE_IS_NaN(d) || d <= -2147483649.0 || 2147483648.0 <= d) { - str = js_DecompileValueGenerator(cx, JS_TRUE, v, NULL); + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT, JS_GetStringBytes(str)); diff --git a/mozilla/js/src/jsobj.c b/mozilla/js/src/jsobj.c index 51750fe37e2..e4eecea0c0b 100644 --- a/mozilla/js/src/jsobj.c +++ b/mozilla/js/src/jsobj.c @@ -2275,7 +2275,8 @@ read_only: unlocked_read_only: if (JSVERSION_IS_ECMA(cx->version)) return JS_TRUE; - str = js_DecompileValueGenerator(cx, JS_FALSE, js_IdToValue(id), NULL); + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + js_IdToValue(id), NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_READ_ONLY, JS_GetStringBytes(str)); @@ -2416,7 +2417,8 @@ js_DeleteProperty(JSContext *cx, JSObject *obj, jsid id, jsval *rval) *rval = JSVAL_FALSE; return JS_TRUE; } - str = js_DecompileValueGenerator(cx, JS_FALSE, js_IdToValue(id), NULL); + str = js_DecompileValueGenerator(cx, JSDVG_IGNORE_STACK, + js_IdToValue(id), NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_PERMANENT, JS_GetStringBytes(str)); @@ -2543,7 +2545,7 @@ js_DefaultValue(JSContext *cx, JSObject *obj, JSType hint, jsval *vp) str = NULL; } *vp = OBJECT_TO_JSVAL(obj); - str = js_DecompileValueGenerator(cx, JS_TRUE, v, str); + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, str); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_CANT_CONVERT_TO, @@ -2901,7 +2903,7 @@ js_ValueToNonNullObject(JSContext *cx, jsval v) if (!js_ValueToObject(cx, v, &obj)) return NULL; if (!obj) { - str = js_DecompileValueGenerator(cx, JS_TRUE, v, NULL); + str = js_DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NULL); if (str) { JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NO_PROPERTIES, JS_GetStringBytes(str)); diff --git a/mozilla/js/src/jsopcode.c b/mozilla/js/src/jsopcode.c index 8a08296efa4..370a43c35f4 100644 --- a/mozilla/js/src/jsopcode.c +++ b/mozilla/js/src/jsopcode.c @@ -2307,10 +2307,10 @@ js_DecompileFunction(JSPrinter *jp, JSFunction *fun) } JSString * -js_DecompileValueGenerator(JSContext *cx, JSBool checkStack, jsval v, +js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, JSString *fallback) { - JSStackFrame *fp; + JSStackFrame *fp, *down; jsbytecode *pc, *begin, *end, *tmp; jsval *sp, *base, *limit; JSScript *script; @@ -2325,129 +2325,137 @@ js_DecompileValueGenerator(JSContext *cx, JSBool checkStack, jsval v, fp = cx->fp; if (!fp) - goto do_fallback; + goto do_fallback; /* Try to find sp's generating pc depth slots under it on the stack. */ pc = fp->pc; - limit = (jsval *) cx->stackPool.current->avail; - if (!pc && fp->argv && fp->down) { - /* - * Current frame is native, look under it for a scripted call in which - * a decompilable bytecode string that generated the value might exist. - * But if we're told not to check the stack for v, give up. - */ - if (!checkStack) - goto do_fallback; - script = fp->down->script; - if (!script) - goto do_fallback; + if (spindex == JSDVG_SEARCH_STACK) { + if (!pc) { + /* + * Current frame is native: look under it for a scripted call + * in which a decompilable bytecode string that generated the + * value as an actual argument might exist. + */ + JS_ASSERT(!fp->script && fp->fun && fp->fun->native); + down = fp->down; + if (!down) + goto do_fallback; + script = down->script; + base = fp->argv; + limit = base + fp->argc; + } else { + /* + * This should be a script activation, either a top-level + * script or a scripted function. But be paranoid about calls + * to js_DecompileValueGenerator from code that hasn't fully + * initialized a (default-all-zeroes) frame. + */ + script = fp->script; + base = fp->spbase; + limit = fp->sp; + } - /* - * Native frame called by script: try to match v with actual argument. - * If (fp->sp < fp->argv), normally an impossibility, then we are in - * js_ReportIsNotFunction and sp points to the offending non-function - * on the stack. - */ - for (sp = (fp->sp < fp->argv) ? fp->sp : fp->argv; sp < limit; sp++) { - if (*sp == v) { - depth = (intN)script->depth; - pc = (jsbytecode *) sp[-depth]; - break; - } - } + /* + * Pure paranoia about default-zeroed frames being active while + * js_DecompileValueGenerator is called. It can't hurt much now; + * error reporting performance is not an issue. + */ + if (!script || !base || !limit) + goto do_fallback; + + /* + * Try to find operand-generating pc depth slots below sp. + * + * In the native case, we know the arguments have generating pc's + * under them, on account of fp->down->script being non-null: all + * compiled scripts get depth slots for generating pc's allocated + * upon activation, at the top of js_Interpret. + * + * In the script or scripted function case, the same reasoning + * applies to fp rather than to fp->down. + */ + for (sp = base; sp < limit; sp++) { + if (*sp == v) { + depth = (intN)script->depth; + pc = (jsbytecode *) sp[-depth]; + break; + } + } } else { - /* - * At this point, pc may or may not be null. I.e., we could be in a - * script activation, or we could be in a native frame that was called - * by another native function. Check script. - */ - script = fp->script; - if (!script) - goto do_fallback; + /* + * At this point, pc may or may not be null, i.e., we could be in + * a script activation, or we could be in a native frame that was + * called by another native function. Check pc and script. + */ + if (!pc) + goto do_fallback; + script = fp->script; + if (!script) + goto do_fallback; - /* - * OK, we're in an interpreted frame. The interpreter calls us just - * after popping one or two operands for a given bytecode at fp->pc. - * So try the operand at sp, or one above it. - */ - if (checkStack) { - sp = fp->sp; - if (sp[0] != v && sp + 1 < limit && sp[1] == v) - sp++; + if (spindex != JSDVG_IGNORE_STACK) { + depth = (intN)script->depth; + JS_ASSERT(-depth <= spindex && spindex < 0); + spindex -= depth; - if (sp[0] == v) { - /* Try to find an operand-generating pc just above fp's vars. */ - depth = (intN)script->depth; - base = fp->vars - ? fp->vars + fp->nvars - : (jsval *) cx->stackPool.current->base; - if (JS_UPTRDIFF(sp - depth, base) < JS_UPTRDIFF(limit, base)) - pc = (jsbytecode *) sp[-depth]; - } - } - - /* - * If fp->pc was null, and either we had no luck checking the stack, - * or our caller synthesized v himself and does not want us to check - * the stack, then we fall back. - */ - if (!pc) - goto do_fallback; + base = (jsval *) cx->stackPool.current->base; + limit = (jsval *) cx->stackPool.current->avail; + sp = fp->sp + spindex; + if (JS_UPTRDIFF(sp, base) < JS_UPTRDIFF(limit, base)) + pc = (jsbytecode *) *sp; + } } /* - * Be paranoid about loading an invalid pc from sp[-depth]. - * - * Using an object for which js_DefaultValue fails as part of an expression - * blows this assert. Disabled for now. XXXbe true w/ JSINVOKE_INTERNAL? - * JS_ASSERT(JS_UPTRDIFF(pc, script->code) < (jsuword)script->length); + * Again, be paranoid, this time about possibly loading an invalid pc + * from sp[-(1+depth)]. */ if (JS_UPTRDIFF(pc, script->code) >= (jsuword)script->length) { - pc = fp->pc; - if (!pc) - goto do_fallback; + pc = fp->pc; + if (!pc) + goto do_fallback; } op = (JSOp) *pc; if (op == JSOP_TRAP) - op = JS_GetTrapOpcode(cx, script, pc); + op = JS_GetTrapOpcode(cx, script, pc); cs = &js_CodeSpec[op]; format = cs->format; mode = (format & JOF_MODEMASK); /* NAME ops are self-contained, but others require left context. */ if (mode == JOF_NAME) { - begin = pc; + begin = pc; } else { - sn = js_GetSrcNote(script, pc); - if (!sn || SN_TYPE(sn) != SRC_PCBASE) - goto do_fallback; - begin = pc - js_GetSrcNoteOffset(sn, 0); + sn = js_GetSrcNote(script, pc); + if (!sn || SN_TYPE(sn) != SRC_PCBASE) + goto do_fallback; + begin = pc - js_GetSrcNoteOffset(sn, 0); } end = pc + cs->length; len = PTRDIFF(end, begin, jsbytecode); if (format & (JOF_SET | JOF_DEL | JOF_INCDEC | JOF_IMPORT)) { - tmp = (jsbytecode *) JS_malloc(cx, len * sizeof(jsbytecode)); - if (!tmp) - return NULL; - memcpy(tmp, begin, len * sizeof(jsbytecode)); - if (mode == JOF_NAME) { - tmp[0] = JSOP_NAME; - } else { - /* - * We must replace the faulting pc's bytecode with a corresponding - * JSOP_GET* code. For JSOP_SET{PROP,ELEM}, we must use the "2nd" - * form of JSOP_GET{PROP,ELEM}, to throw away the assignment op's - * right-hand operand and decompile it as if it were a GET of its - * left-hand operand. - */ - off = len - cs->length; - JS_ASSERT(off == (uintN) PTRDIFF(pc, begin, jsbytecode)); - if (mode == JOF_PROP) { - tmp[off] = (format & JOF_SET) ? JSOP_GETPROP2 : JSOP_GETPROP; - } else if (mode == JOF_ELEM) { - tmp[off] = (format & JOF_SET) ? JSOP_GETELEM2 : JSOP_GETELEM; - } else { + tmp = (jsbytecode *) JS_malloc(cx, len * sizeof(jsbytecode)); + if (!tmp) + return NULL; + memcpy(tmp, begin, len * sizeof(jsbytecode)); + if (mode == JOF_NAME) { + tmp[0] = JSOP_NAME; + } else { + /* + * We must replace the faulting pc's bytecode with a corresponding + * JSOP_GET* code. For JSOP_SET{PROP,ELEM}, we must use the "2nd" + * form of JSOP_GET{PROP,ELEM}, to throw away the assignment op's + * right-hand operand and decompile it as if it were a GET of its + * left-hand operand. + */ + off = len - cs->length; + JS_ASSERT(off == (uintN) PTRDIFF(pc, begin, jsbytecode)); + if (mode == JOF_PROP) { + tmp[off] = (format & JOF_SET) ? JSOP_GETPROP2 : JSOP_GETPROP; + } else if (mode == JOF_ELEM) { + tmp[off] = (format & JOF_SET) ? JSOP_GETELEM2 : JSOP_GETELEM; + } else { #if JS_HAS_LVALUE_RETURN JS_ASSERT(op == JSOP_SETCALL); tmp[off] = JSOP_CALL; @@ -2455,21 +2463,21 @@ js_DecompileValueGenerator(JSContext *cx, JSBool checkStack, jsval v, JS_ASSERT(0); #endif } - } - begin = tmp; + } + begin = tmp; } else { - /* No need to revise script bytecode. */ - tmp = NULL; + /* No need to revise script bytecode. */ + tmp = NULL; } jp = js_NewPrinter(cx, "js_DecompileValueGenerator", 0, JS_FALSE); if (jp && js_DecompileCode(jp, script, begin, len)) - name = js_GetPrinterOutput(jp); + name = js_GetPrinterOutput(jp); else - name = NULL; + name = NULL; js_DestroyPrinter(jp); if (tmp) - JS_free(cx, tmp); + JS_free(cx, tmp); return name; do_fallback: diff --git a/mozilla/js/src/jsopcode.h b/mozilla/js/src/jsopcode.h index 1d1822562ac..59c3d525160 100644 --- a/mozilla/js/src/jsopcode.h +++ b/mozilla/js/src/jsopcode.h @@ -198,14 +198,21 @@ js_DecompileFunction(JSPrinter *jp, JSFunction *fun); /* * Find the source expression that resulted in v, and return a new string - * containing it. Fall back on v's string conversion if we can't find the - * bytecode that generated and pushed v on the operand stack. Don't look - * for v on the stack if checkStack is false. + * containing it. Fall back on v's string conversion (fallback) if we can't + * find the bytecode that generated and pushed v on the operand stack. + * + * Search the current stack frame if spindex is JSDVG_SEARCH_STACK. Don't + * look for v on the stack if spindex is JSDVG_IGNORE_STACK. Otherwise, + * spindex is the negative index of v, measured from cx->fp->sp, or from a + * lower frame's sp if cx->fp is native. */ extern JSString * -js_DecompileValueGenerator(JSContext *cx, JSBool checkStack, jsval v, +js_DecompileValueGenerator(JSContext *cx, intN spindex, jsval v, JSString *fallback); +#define JSDVG_IGNORE_STACK 0 +#define JSDVG_SEARCH_STACK 1 + JS_END_EXTERN_C #endif /* jsopcode_h___ */ diff --git a/mozilla/js/src/jsprvtd.h b/mozilla/js/src/jsprvtd.h index ce33f9e1aa4..f42e21c23b5 100644 --- a/mozilla/js/src/jsprvtd.h +++ b/mozilla/js/src/jsprvtd.h @@ -83,6 +83,7 @@ typedef struct JSScope JSScope; typedef struct JSScopeOps JSScopeOps; typedef struct JSScopeProperty JSScopeProperty; typedef struct JSStackFrame JSStackFrame; +typedef struct JSStackHeader JSStackHeader; typedef struct JSSubString JSSubString; typedef struct JSSymbol JSSymbol;