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;