Bug 441479 - for-in loops should use one backward branch (with downward goto on entry; r=mrbkap).

This commit is contained in:
Brendan Eich
2008-10-22 12:47:51 -07:00
parent 130070cd97
commit c3225205e8
4 changed files with 209 additions and 250 deletions

View File

@@ -82,6 +82,7 @@
#undef OPDEF
static const char js_incop_strs[][3] = {"++", "--"};
static const char js_for_each_str[] = "for each";
const JSCodeSpec js_CodeSpec[] = {
#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
@@ -1717,7 +1718,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
{
JSContext *cx;
JSPrinter *jp, *jp2;
jsbytecode *startpc, *endpc, *pc2, *done, *forelem_tail, *forelem_done;
jsbytecode *startpc, *endpc, *pc2, *done;
ptrdiff_t tail, todo, len, oplen, cond, next;
JSOp op, lastop, saveop;
const JSCodeSpec *cs;
@@ -1748,6 +1749,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
static const char preindex_format[] = "%s%s[%s]";
static const char postindex_format[] = "%s[%s]%s";
static const char ss_format[] = "%s%s";
static const char sss_format[] = "%s%s%s";
/* Argument and variables decompilation uses the following to share code. */
JS_STATIC_ASSERT(ARGNO_LEN == SLOTNO_LEN);
@@ -1849,7 +1851,6 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
jp = ss->printer;
startpc = pc;
endpc = (nb < 0) ? jp->script->code + jp->script->length : pc + nb;
forelem_tail = forelem_done = NULL;
tail = -1;
todo = -2; /* NB: different from Sprint() error return. */
saveop = JSOP_NOP;
@@ -2872,34 +2873,36 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
rval = POP_STR();
/*
* Skip down over iterables left stacked by JSOP_FOR* until
* we hit a block-local or the new Array initialiser (empty
* destructuring patterns yield zero-count blocks).
* Skip the for loop head stacked by JSOP_FORLOCAL until we hit
* a block local slot (note empty destructuring patterns result
* in unit-count blocks).
*/
pos = ss->top;
while ((op = (JSOp) ss->opcodes[--pos]) != JSOP_ENTERBLOCK &&
op != JSOP_NEWINIT) {
if (pos == 0)
while (pos != 0) {
op = (JSOp) ss->opcodes[--pos];
if (op != JSOP_FORLOCAL)
break;
}
JS_ASSERT(op == JSOP_ENTERBLOCK);
/*
* Make forpos index the space before the left-most |for| in
* the single string of accumulated |for| heads and optional
* Here, forpos must index the space before the left-most |for|
* in the single string of accumulated |for| heads and optional
* final |if (condition)|.
*/
forpos = pos + (op == JSOP_ENTERBLOCK || op == JSOP_NEWINIT);
forpos = pos + 1;
LOCAL_ASSERT(forpos < ss->top);
/*
* Now skip down over the block's local slots, if any. There
* may be no locals for an empty destructuring pattern.
* Now move pos downward over the block's local slots. Even an
* empty destructuring pattern has one (dummy) local.
*/
while (ss->opcodes[pos] == JSOP_ENTERBLOCK) {
if (pos == 0)
break;
--pos;
}
JS_ASSERT_IF(saveop == JSOP_ARRAYPUSH, pos == GET_UINT16(pc));
#if JS_HAS_GENERATOR_EXPRS
if (saveop == JSOP_YIELD) {
@@ -2936,8 +2939,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
lval = OFF2STR(&ss->sprinter, start);
RETRACT(&ss->sprinter, lval);
todo = Sprint(&ss->sprinter, "%s%s%.*s",
lval, rval, rval - xval, xval);
todo = Sprint(&ss->sprinter, sss_format, lval, rval, xval);
if (todo < 0)
return NULL;
ss->offsets[pos] = todo;
@@ -2963,6 +2965,65 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
case JSOP_GOTOX:
sn = js_GetSrcNote(jp->script, pc);
switch (sn ? SN_TYPE(sn) : SRC_NULL) {
case SRC_FOR_IN:
/*
* The loop back-edge carries +1 stack balance, for the
* flag processed by JSOP_IFNE. We do not decompile the
* JSOP_IFNE, and instead pass the left-hand side of 'in'
* along the loop edge in this pushed stack slot.
*/
cond = GetJumpOffset(pc, pc);
next = js_GetSrcNoteOffset(sn, 0);
tail = js_GetSrcNoteOffset(sn, 1);
DECOMPILE_CODE(pc + cond, tail - cond);
DECOMPILE_CODE(pc + oplen, next - oplen);
if (ss->inArrayInit || ss->inGenExp) {
lval = POP_STR();
rval = POP_STR();
if (ss->opcodes[ss->top - 1] == JSOP_FORLOCAL) {
ss->sprinter.offset -= PAREN_SLOP;
if (Sprint(&ss->sprinter, " %s (%s in %s)",
foreach ? js_for_each_str : js_for_str,
lval, rval) < 0) {
return NULL;
}
/*
* Do not AddParentSlop here, as we will push the
* top-most offset again, which will add paren slop
* for us. We must push to balance the stack budget
* when nesting for heads in a comprehension.
*/
todo = ss->offsets[ss->top - 1];
} else {
LOCAL_ASSERT(ss->opcodes[ss->top - 1] == JSOP_ENTERBLOCK);
todo = Sprint(&ss->sprinter, " %s (%s in %s)",
foreach ? js_for_each_str : js_for_str,
lval, rval);
}
if (todo < 0 || !PushOff(ss, todo, JSOP_FORLOCAL))
return NULL;
DECOMPILE_CODE(pc + next, cond - next);
} else {
/*
* As above, rval or an extension of it must remain
* stacked during loop body decompilation.
*/
lval = POP_STR();
rval = GetStr(ss, ss->top - 1);
js_printf(jp, "\t%s (%s in %s) {\n",
foreach ? js_for_each_str : js_for_str,
lval, rval);
jp->indent += 4;
DECOMPILE_CODE(pc + next, cond - next);
jp->indent -= 4;
js_printf(jp, "\t}\n");
}
pc += tail;
LOCAL_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX);
len = js_CodeSpec[*pc].length;
break;
case SRC_WHILE:
cond = GetJumpOffset(pc, pc);
tail = js_GetSrcNoteOffset(sn, 0);
@@ -3153,31 +3214,34 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
goto do_logical_connective;
case JSOP_FORARG:
atom = GetArgOrVarAtom(jp, GET_ARGNO(pc));
LOCAL_ASSERT(atom);
goto do_fornameinloop;
sn = NULL;
i = GET_ARGNO(pc);
goto do_forvarslot;
case JSOP_FORCONST:
case JSOP_FORLOCAL:
if (IsVarSlot(jp, pc, &i)) {
atom = GetArgOrVarAtom(jp, i);
LOCAL_ASSERT(atom);
goto do_fornameinloop;
sn = js_GetSrcNote(jp->script, pc);
if (!IsVarSlot(jp, pc, &i)) {
JS_ASSERT(op == JSOP_FORLOCAL);
todo = Sprint(&ss->sprinter, ss_format, VarPrefix(sn), GetStr(ss, i));
break;
}
JS_ASSERT(op == JSOP_FORLOCAL);
lval = GetStr(ss, i);
atom = NULL;
goto do_forlvalinloop;
do_forvarslot:
atom = GetArgOrVarAtom(jp, i);
LOCAL_ASSERT(atom);
todo = SprintCString(&ss->sprinter, VarPrefix(sn));
if (todo < 0 || !QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
return NULL;
break;
case JSOP_FORNAME:
LOAD_ATOM(0);
do_fornameinloop:
lval = "";
do_forlvalinloop:
sn = js_GetSrcNote(jp->script, pc);
xval = NULL;
goto do_forinloop;
todo = SprintCString(&ss->sprinter, VarPrefix(sn));
if (todo < 0 || !QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
return NULL;
break;
case JSOP_FORPROP:
xval = NULL;
@@ -3190,121 +3254,15 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
atom = NULL;
}
lval = POP_STR();
sn = NULL;
do_forinloop:
pc += oplen;
LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
oplen = js_CodeSpec[*pc].length;
len = GetJumpOffset(pc, pc);
sn2 = js_GetSrcNote(jp->script, pc);
tail = js_GetSrcNoteOffset(sn2, 0);
do_forinhead:
if (!atom && xval) {
/*
* If xval is not a dummy empty string, we have to strdup
* it to save it from being clobbered by the first Sprint
* below. Standard dumb decompiler operating procedure!
*/
if (*xval == '\0') {
xval = NULL;
} else {
xval = JS_strdup(cx, xval);
if (!xval)
return NULL;
}
}
#if JS_HAS_XML_SUPPORT
if (foreach) {
foreach = JS_FALSE;
todo = Sprint(&ss->sprinter, "for %s (%s%s",
js_each_str, VarPrefix(sn), lval);
} else
#endif
{
todo = Sprint(&ss->sprinter, "for (%s%s",
VarPrefix(sn), lval);
}
if (atom) {
if (*lval && SprintPut(&ss->sprinter, ".", 1) < 0)
return NULL;
xval = QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0);
if (!xval)
return NULL;
} else if (xval) {
LOCAL_ASSERT(*xval != '\0');
ok = (Sprint(&ss->sprinter,
(JOF_OPMODE(lastop) == JOF_XMLNAME)
? ".%s"
: "[%s]",
xval)
>= 0);
JS_free(cx, (char *)xval);
if (!ok)
return NULL;
}
todo = Sprint(&ss->sprinter, ss_format, lval, *lval ? "." : "");
if (todo < 0)
return NULL;
lval = OFF2STR(&ss->sprinter, todo);
rval = GetStr(ss, ss->top-1);
RETRACT(&ss->sprinter, rval);
if (ss->inArrayInit || ss->inGenExp) {
if (ss->top > 1 &&
(js_CodeSpec[ss->opcodes[ss->top-2]].format &
JOF_FOR)) {
ss->sprinter.offset -= PAREN_SLOP;
}
todo = Sprint(&ss->sprinter, " %s in %s)", lval, rval);
if (todo < 0)
return NULL;
ss->offsets[ss->top-1] = todo;
ss->opcodes[ss->top-1] = op;
AddParenSlop(ss);
DECOMPILE_CODE(pc + oplen, tail - oplen);
} else {
js_printf(jp, "\t%s in %s) {\n", lval, rval);
jp->indent += 4;
DECOMPILE_CODE(pc + oplen, tail - oplen);
jp->indent -= 4;
js_printf(jp, "\t}\n");
}
todo = -2;
if (!QuoteString(&ss->sprinter, ATOM_TO_STRING(atom), 0))
return NULL;
break;
case JSOP_FORELEM:
pc++;
LOCAL_ASSERT(*pc == JSOP_IFEQ || *pc == JSOP_IFEQX);
len = js_CodeSpec[*pc].length;
/*
* Arrange for the JSOP_ENUMELEM case to set tail for use by
* do_forinhead: code that uses on it to find the loop-closing
* jump (whatever its format, normal or extended), in order to
* bound the recursively decompiled loop body.
*/
sn = js_GetSrcNote(jp->script, pc);
LOCAL_ASSERT(!forelem_tail);
forelem_tail = pc + js_GetSrcNoteOffset(sn, 0);
/*
* This gets a little wacky. Only the length of the for loop
* body PLUS the element-indexing expression is known here, so
* we pass the after-loop pc to the JSOP_ENUMELEM case, which
* is immediately below, to decompile that helper bytecode via
* the 'forelem_done' local.
*
* Since a for..in loop can't nest in the head of another for
* loop, we can use forelem_{tail,done} singletons to remember
* state from JSOP_FORELEM to JSOP_ENUMELEM, thence (via goto)
* to label do_forinhead.
*/
LOCAL_ASSERT(!forelem_done);
forelem_done = pc + GetJumpOffset(pc, pc);
/* Our net stack balance after forelem;ifeq is +1. */
LOCAL_ASSERT(pc[1] == JSOP_IFNE || pc[1] == JSOP_IFNEX);
todo = SprintCString(&ss->sprinter, forelem_cookie);
break;
@@ -3325,13 +3283,16 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
op = saveop;
rval = POP_STR();
LOCAL_ASSERT(strcmp(rval, forelem_cookie) == 0);
LOCAL_ASSERT(forelem_tail > pc);
tail = forelem_tail - pc;
forelem_tail = NULL;
LOCAL_ASSERT(forelem_done > pc);
len = forelem_done - pc;
forelem_done = NULL;
goto do_forinhead;
if (*xval == '\0') {
todo = SprintCString(&ss->sprinter, lval);
} else {
todo = Sprint(&ss->sprinter,
(JOF_OPMODE(lastop) == JOF_XMLNAME)
? dot_format
: index_format,
lval, xval);
}
break;
#if JS_HAS_GETTER_SETTER
case JSOP_GETTER:
@@ -3363,27 +3324,16 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
rval = POP_STR();
if (strcmp(rval, forelem_cookie) == 0) {
LOCAL_ASSERT(forelem_tail > pc);
tail = forelem_tail - pc;
forelem_tail = NULL;
LOCAL_ASSERT(forelem_done > pc);
len = forelem_done - pc;
forelem_done = NULL;
xval = NULL;
atom = NULL;
todo = Sprint(&ss->sprinter, ss_format,
VarPrefix(sn), lval);
/*
* Null sn if this is a 'for (var [k, v] = i in o)'
* loop, because 'var [k, v = i;' has already been
* hoisted.
*/
if (js_GetSrcNoteOffset(sn, 0) == SRC_DECL_VAR)
sn = NULL;
goto do_forinhead;
// Skip POP so the SRC_FOR_IN code can pop for itself.
if (*pc == JSOP_POP)
len = JSOP_POP_LENGTH;
} else {
todo = Sprint(&ss->sprinter, "%s%s = %s",
VarPrefix(sn), lval, rval);
}
todo = Sprint(&ss->sprinter, "%s%s = %s",
VarPrefix(sn), lval, rval);
break;
}
#endif
@@ -3855,7 +3805,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
if (!rval)
return NULL;
RETRACT(&ss->sprinter, rval);
todo = Sprint(&ss->sprinter, "%s%s%s",
todo = Sprint(&ss->sprinter, sss_format,
VarPrefix(sn), lval, rval);
break;
@@ -3925,8 +3875,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
outer = jp->script;
LOCAL_ASSERT(JS_UPTRDIFF(pc, outer->code) <= outer->length);
jp->script = inner;
if (!Decompile(&ss2, inner->code, inner->length,
JSOP_NOP)) {
if (!Decompile(&ss2, inner->code, inner->length, JSOP_NOP)) {
JS_ARENA_RELEASE(&cx->tempPool, mark);
return NULL;
}
@@ -4390,7 +4339,7 @@ Decompile(SprintStack *ss, jsbytecode *pc, intN nb, JSOp nextop)
goto do_initprop;
}
maybeComma = isFirst ? "" : ", ";
todo = Sprint(&ss->sprinter, "%s%s%s",
todo = Sprint(&ss->sprinter, sss_format,
lval,
maybeComma,
rval);