--- a/js/src/jit-test/tests/basic/bug646968-4.js
+++ b/js/src/jit-test/tests/basic/bug646968-4.js
@@ -1,4 +1,5 @@
+dis();
var s = '', x = {a: 1, b: 2, c: 3};
for (let x in eval('x'))
s += x;
assertEq(s, 'abc');
--- a/js/src/jsanalyze.cpp
+++ b/js/src/jsanalyze.cpp
@@ -452,17 +452,16 @@ Script::analyze(JSContext *cx, JSScript
case JSOP_CALLNAME:
case JSOP_BINDNAME:
case JSOP_SETNAME:
case JSOP_DELNAME:
case JSOP_INCNAME:
case JSOP_DECNAME:
case JSOP_NAMEINC:
case JSOP_NAMEDEC:
- case JSOP_FORNAME:
usesScope = true;
break;
/* Watch for opcodes the method JIT doesn't compile. */
case JSOP_GOSUB:
case JSOP_GOSUBX:
case JSOP_IFCANTCALLTOP:
case JSOP_FILTER:
@@ -574,18 +573,17 @@ Script::analyze(JSContext *cx, JSScript
case JSOP_LOCALINC:
case JSOP_LOCALDEC: {
uint32 local = GET_SLOTNO(pc);
if (local < nfixed && !localDefined(local, offset))
setLocal(local, LOCAL_USE_BEFORE_DEF);
break;
}
- case JSOP_SETLOCAL:
- case JSOP_FORLOCAL: {
+ case JSOP_SETLOCAL: {
uint32 local = GET_SLOTNO(pc);
/*
* The local variable may already have been marked as unconditionally
* defined at a later point in the script, if that definition was in the
* condition for a loop which then jumped back here. In such cases we
* will not treat the variable as ever being defined in the loop body
* (see setLocal).
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -1138,17 +1138,17 @@ struct JSContext
JSSecurityCallbacks *securityCallbacks;
/* Stored here to avoid passing it around as a parameter. */
uintN resolveFlags;
/* Random number generator state, used by jsmath.cpp. */
int64 rngSeed;
- /* Location to stash the iteration value between JSOP_MOREITER and JSOP_FOR*. */
+ /* Location to stash the iteration value between JSOP_MOREITER and JSOP_ITERNEXT. */
js::Value iterValue;
#ifdef JS_TRACER
/*
* True if traces may be executed. Invariant: The value of traceJitenabled
* is always equal to the expression in updateJITEnabled below.
*
* This flag and the fields accessed by updateJITEnabled are written only
--- a/js/src/jsemit.cpp
+++ b/js/src/jsemit.cpp
@@ -2017,17 +2017,16 @@ TryConvertToGname(JSCodeGenerator *cg, J
!(cg->flags & TCF_STRICT_MODE_CODE)) {
switch (*op) {
case JSOP_NAME: *op = JSOP_GETGNAME; break;
case JSOP_SETNAME: *op = JSOP_SETGNAME; break;
case JSOP_INCNAME: *op = JSOP_INCGNAME; break;
case JSOP_NAMEINC: *op = JSOP_GNAMEINC; break;
case JSOP_DECNAME: *op = JSOP_DECGNAME; break;
case JSOP_NAMEDEC: *op = JSOP_GNAMEDEC; break;
- case JSOP_FORNAME: *op = JSOP_FORGNAME; break;
case JSOP_SETCONST:
case JSOP_DELNAME:
/* Not supported. */
return false;
default: JS_NOT_REACHED("gname");
}
return true;
}
@@ -2362,30 +2361,28 @@ BindNameToSlot(JSContext *cx, JSCodeGene
case JSDefinition::LET:
switch (op) {
case JSOP_NAME: op = JSOP_GETLOCAL; break;
case JSOP_SETNAME: op = JSOP_SETLOCAL; break;
case JSOP_INCNAME: op = JSOP_INCLOCAL; break;
case JSOP_NAMEINC: op = JSOP_LOCALINC; break;
case JSOP_DECNAME: op = JSOP_DECLOCAL; break;
case JSOP_NAMEDEC: op = JSOP_LOCALDEC; break;
- case JSOP_FORNAME: op = JSOP_FORLOCAL; break;
default: JS_NOT_REACHED("let");
}
break;
case JSDefinition::ARG:
switch (op) {
case JSOP_NAME: op = JSOP_GETARG; break;
case JSOP_SETNAME: op = JSOP_SETARG; break;
case JSOP_INCNAME: op = JSOP_INCARG; break;
case JSOP_NAMEINC: op = JSOP_ARGINC; break;
case JSOP_DECNAME: op = JSOP_DECARG; break;
case JSOP_NAMEDEC: op = JSOP_ARGDEC; break;
- case JSOP_FORNAME: op = JSOP_FORARG; break;
default: JS_NOT_REACHED("arg");
}
JS_ASSERT(!pn->isConst());
break;
case JSDefinition::VAR:
if (PN_OP(dn) == JSOP_CALLEE) {
JS_ASSERT(op != JSOP_CALLEE);
@@ -2434,17 +2431,16 @@ BindNameToSlot(JSContext *cx, JSCodeGene
switch (op) {
case JSOP_NAME: op = JSOP_GETLOCAL; break;
case JSOP_SETNAME: op = JSOP_SETLOCAL; break;
case JSOP_SETCONST: op = JSOP_SETLOCAL; break;
case JSOP_INCNAME: op = JSOP_INCLOCAL; break;
case JSOP_NAMEINC: op = JSOP_LOCALINC; break;
case JSOP_DECNAME: op = JSOP_DECLOCAL; break;
case JSOP_NAMEDEC: op = JSOP_LOCALDEC; break;
- case JSOP_FORNAME: op = JSOP_FORLOCAL; break;
default: JS_NOT_REACHED("local");
}
JS_ASSERT_IF(dn_kind == JSDefinition::CONST, pn->pn_dflags & PND_CONST);
break;
}
JS_ASSERT(op != PN_OP(pn));
pn->pn_op = op;
@@ -4401,16 +4397,216 @@ EmitVariables(JSContext *cx, JSCodeGener
return JS_FALSE;
if (!(pn->pn_xflags & PNX_POPVAR))
return js_Emit1(cx, cg, JSOP_NOP) >= 0;
}
return !(pn->pn_xflags & PNX_POPVAR) || js_Emit1(cx, cg, JSOP_POP) >= 0;
}
+static bool
+EmitAssignment(JSContext *cx, JSCodeGenerator *cg, JSParseNode *lhs, JSOp op, JSParseNode *rhs)
+{
+ ptrdiff_t top = CG_OFFSET(cg);
+
+ /*
+ * Check left operand type and generate specialized code for it.
+ * Specialize to avoid ECMA "reference type" values on the operand
+ * stack, which impose pervasive runtime "GetValue" costs.
+ */
+ jsatomid atomIndex = (jsatomid) -1; /* quell GCC overwarning */
+ jsbytecode offset = 1;
+
+ switch (PN_TYPE(lhs)) {
+ case TOK_NAME:
+ if (!BindNameToSlot(cx, cg, lhs))
+ return false;
+ if (!lhs->pn_cookie.isFree()) {
+ atomIndex = lhs->pn_cookie.asInteger();
+ } else {
+ if (!cg->makeAtomIndex(lhs->pn_atom, &atomIndex))
+ return false;
+ if (!lhs->isConst()) {
+ JSOp op = PN_OP(lhs) == JSOP_SETGNAME ? JSOP_BINDGNAME : JSOP_BINDNAME;
+ EMIT_INDEX_OP(op, atomIndex);
+ offset++;
+ }
+ }
+ break;
+ case TOK_DOT:
+ if (!js_EmitTree(cx, cg, lhs->expr()))
+ return false;
+ offset++;
+ if (!cg->makeAtomIndex(lhs->pn_atom, &atomIndex))
+ return false;
+ break;
+ case TOK_LB:
+ JS_ASSERT(lhs->pn_arity == PN_BINARY);
+ if (!js_EmitTree(cx, cg, lhs->pn_left))
+ return false;
+ if (!js_EmitTree(cx, cg, lhs->pn_right))
+ return false;
+ offset += 2;
+ break;
+#if JS_HAS_DESTRUCTURING
+ case TOK_RB:
+ case TOK_RC:
+ break;
+#endif
+ case TOK_LP:
+ if (!js_EmitTree(cx, cg, lhs))
+ return false;
+ offset++;
+ break;
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+ JS_ASSERT(lhs->pn_op == JSOP_SETXMLNAME);
+ if (!js_EmitTree(cx, cg, lhs->pn_kid))
+ return false;
+ if (js_Emit1(cx, cg, JSOP_BINDXMLNAME) < 0)
+ return false;
+ offset++;
+ break;
+#endif
+ default:
+ JS_ASSERT(0);
+ }
+
+ if (op != JSOP_NOP) {
+ JS_ASSERT(rhs);
+ switch (lhs->pn_type) {
+ case TOK_NAME:
+ if (lhs->isConst()) {
+ if (PN_OP(lhs) == JSOP_CALLEE) {
+ if (js_Emit1(cx, cg, JSOP_CALLEE) < 0)
+ return false;
+ } else {
+ EMIT_INDEX_OP(PN_OP(lhs), atomIndex);
+ }
+ } else if (PN_OP(lhs) == JSOP_SETNAME) {
+ if (js_Emit1(cx, cg, JSOP_DUP) < 0)
+ return false;
+ EMIT_INDEX_OP(JSOP_GETXPROP, atomIndex);
+ } else if (PN_OP(lhs) == JSOP_SETGNAME) {
+ if (!BindGlobal(cx, cg, lhs, lhs->pn_atom))
+ return false;
+ if (lhs->pn_cookie.isFree())
+ EmitAtomOp(cx, lhs, JSOP_GETGNAME, cg);
+ else
+ EMIT_UINT16_IMM_OP(JSOP_GETGLOBAL, lhs->pn_cookie.asInteger());
+ } else {
+ EMIT_UINT16_IMM_OP((PN_OP(lhs) == JSOP_SETARG)
+ ? JSOP_GETARG
+ : JSOP_GETLOCAL,
+ atomIndex);
+ }
+ break;
+ case TOK_DOT:
+ if (js_Emit1(cx, cg, JSOP_DUP) < 0)
+ return false;
+ if (lhs->pn_atom == cx->runtime->atomState.lengthAtom) {
+ if (js_Emit1(cx, cg, JSOP_LENGTH) < 0)
+ return false;
+ } else if (lhs->pn_atom == cx->runtime->atomState.protoAtom) {
+ if (!EmitIndexOp(cx, JSOP_QNAMEPART, atomIndex, cg))
+ return false;
+ if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
+ return false;
+ } else {
+ EMIT_INDEX_OP(JSOP_GETPROP, atomIndex);
+ }
+ break;
+ case TOK_LB:
+ case TOK_LP:
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+#endif
+ if (js_Emit1(cx, cg, JSOP_DUP2) < 0)
+ return false;
+ if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
+ return false;
+ break;
+ default:;
+ }
+ }
+
+ /* Now emit the right operand (it may affect the namespace). */
+ if (rhs) {
+ if (!js_EmitTree(cx, cg, rhs))
+ return false;
+ } else {
+ /* The value to assign is the next enumeration value in a for-in loop. */
+ if (js_Emit2(cx, cg, JSOP_ITERNEXT, offset) < 0)
+ return false;
+ }
+
+ /* If += etc., emit the binary operator with a decompiler note. */
+ if (op != JSOP_NOP) {
+ /*
+ * Take care to avoid SRC_ASSIGNOP if the left-hand side is a const
+ * declared in the current compilation unit, as in this case (just
+ * a bit further below) we will avoid emitting the assignment op.
+ */
+ if (lhs->pn_type != TOK_NAME || !lhs->isConst()) {
+ if (js_NewSrcNote(cx, cg, SRC_ASSIGNOP) < 0)
+ return false;
+ }
+ if (js_Emit1(cx, cg, op) < 0)
+ return false;
+ }
+
+ /* Left parts such as a.b.c and a[b].c need a decompiler note. */
+ if (lhs->pn_type != TOK_NAME &&
+#if JS_HAS_DESTRUCTURING
+ lhs->pn_type != TOK_RB &&
+ lhs->pn_type != TOK_RC &&
+#endif
+ js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0) {
+ return false;
+ }
+
+ /* Finally, emit the specialized assignment bytecode. */
+ switch (lhs->pn_type) {
+ case TOK_NAME:
+ if (lhs->isConst()) {
+ if (!rhs) {
+ ReportCompileErrorNumber(cx, CG_TS(cg), lhs, JSREPORT_ERROR,
+ JSMSG_BAD_FOR_LEFTSIDE);
+ return false;
+ }
+ break;
+ }
+ /* FALL THROUGH */
+ case TOK_DOT:
+ EMIT_INDEX_OP(PN_OP(lhs), atomIndex);
+ break;
+ case TOK_LB:
+ case TOK_LP:
+ if (js_Emit1(cx, cg, JSOP_SETELEM) < 0)
+ return false;
+ break;
+#if JS_HAS_DESTRUCTURING
+ case TOK_RB:
+ case TOK_RC:
+ if (!EmitDestructuringOps(cx, cg, JSOP_SETNAME, lhs))
+ return false;
+ break;
+#endif
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+ if (js_Emit1(cx, cg, JSOP_SETXMLNAME) < 0)
+ return false;
+ break;
+#endif
+ default:
+ JS_ASSERT(0);
+ }
+ return true;
+}
+
#if defined DEBUG_brendan || defined DEBUG_mrbkap
static JSBool
GettableNoteForNextOp(JSCodeGenerator *cg)
{
ptrdiff_t offset, target;
jssrcnote *sn, *end;
offset = 0;
@@ -4978,54 +5174,39 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
js_PushStatement(cg, &stmtInfo, STMT_FOR_LOOP, top);
if (pn2->pn_type == TOK_IN) {
/* Set stmtInfo type for later testing. */
stmtInfo.type = STMT_FOR_IN_LOOP;
/*
* If the left part is 'var x', emit code to define x if necessary
- * using a prolog opcode, but do not emit a pop. If the left part
- * is 'var x = i', emit prolog code to define x if necessary; then
- * emit code to evaluate i, assign the result to x, and pop the
- * result off the stack.
- *
- * All the logic to do this is implemented in the outer switch's
- * TOK_VAR case, conditioned on pn_xflags flags set by the parser.
- *
- * In the 'for (var x = i in o) ...' case, the js_EmitTree(...pn3)
- * called here will generate the proper note for the assignment
- * op that sets x = i, hoisting the initialized var declaration
- * out of the loop: 'var x = i; for (x in o) ...'.
- *
- * In the 'for (var x in o) ...' case, nothing but the prolog op
- * (if needed) should be generated here, we must emit the note
- * just before the JSOP_FOR* opcode in the switch on pn3->pn_type
- * a bit below, so nothing is hoisted: 'for (var x in o) ...'.
- *
- * A 'for (let x = i in o)' loop must not be hoisted, since in
- * this form the let variable is scoped by the loop body (but not
- * the head). The initializer expression i must be evaluated for
- * any side effects. So we hoist only i in the let case.
+ * using a prolog opcode, but do not emit a pop. If the left part
+ * was originally 'var x = i', the parser will have rewritten it;
+ * see Parser::forStatement. 'for (let x = i in o)' is mercifully
+ * banned.
*/
- pn3 = pn2->pn_left;
- type = PN_TYPE(pn3);
- cg->flags |= TCF_IN_FOR_INIT;
- if (TokenKindIsDecl(type) && !js_EmitTree(cx, cg, pn3))
- return JS_FALSE;
- cg->flags &= ~TCF_IN_FOR_INIT;
+ bool forLet = false;
+ if (JSParseNode *decl = pn2->pn_kid1) {
+ JS_ASSERT(TokenKindIsDecl(PN_TYPE(decl)));
+ forLet = PN_TYPE(decl) == TOK_LET;
+ cg->flags |= TCF_IN_FOR_INIT;
+ if (!js_EmitTree(cx, cg, decl))
+ return JS_FALSE;
+ cg->flags &= ~TCF_IN_FOR_INIT;
+ }
/* Compile the object expression to the right of 'in'. */
{
TempPopScope tps;
- if (type == TOK_LET && !tps.popBlock(cx, cg))
+ if (forLet && !tps.popBlock(cx, cg))
return JS_FALSE;
- if (!js_EmitTree(cx, cg, pn2->pn_right))
+ if (!js_EmitTree(cx, cg, pn2->pn_kid3))
return JS_FALSE;
- if (type == TOK_LET && !tps.repushBlock(cx, cg))
+ if (forLet && !tps.repushBlock(cx, cg))
return JS_FALSE;
}
/*
* Emit a bytecode to convert top of stack value to the iterator
* object depending on the loop variant (for-in, for-each-in, or
* destructuring for-in).
*/
@@ -5055,166 +5236,36 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
if (EmitTraceOp(cx, cg) < 0)
return JS_FALSE;
#ifdef DEBUG
intN loopDepth = cg->stackDepth;
#endif
/*
- * Compile a JSOP_FOR* bytecode based on the left hand side.
- *
- * Initialize op to JSOP_SETNAME in case of |for ([a, b] in o)...|
- * or similar, to signify assignment, rather than declaration, to
- * the decompiler. EmitDestructuringOps takes a prolog bytecode
- * parameter and emits the appropriate source note, defaulting to
- * assignment, so JSOP_SETNAME is not critical here; many similar
- * ops could be used -- just not JSOP_NOP (which means 'let').
+ * Emit code to get the next enumeration value and assign it to the
+ * left hand side. The JSOP_POP after this assignment is annotated
+ * so that the decompiler can distinguish 'for (x in y)' from
+ * 'for (var x in y)'.
*/
- op = JSOP_SETNAME;
- switch (type) {
-#if JS_HAS_BLOCK_SCOPE
- case TOK_LET:
-#endif
- case TOK_VAR:
- JS_ASSERT(pn3->pn_arity == PN_LIST && pn3->pn_count == 1);
- pn3 = pn3->pn_head;
-#if JS_HAS_DESTRUCTURING
- if (pn3->pn_type == TOK_ASSIGN) {
- pn3 = pn3->pn_left;
- JS_ASSERT(pn3->pn_type == TOK_RB || pn3->pn_type == TOK_RC);
- }
- if (pn3->pn_type == TOK_RB || pn3->pn_type == TOK_RC) {
- op = PN_OP(pn2->pn_left);
- goto destructuring_for;
- }
-#else
- JS_ASSERT(pn3->pn_type == TOK_NAME);
-#endif
- /* FALL THROUGH */
-
- case TOK_NAME:
- {
- /*
- * Always annotate JSOP_FORLOCAL if given input of the form
- * 'for (let x in * o)' -- the decompiler must not hoist the
- * 'let x' out of the loop head, or x will be bound in the
- * wrong scope. Likewise, but in this case only for the sake
- * of higher decompilation fidelity only, do not hoist 'var x'
- * when given 'for (var x in o)'.
- */
- if ((
-#if JS_HAS_BLOCK_SCOPE
- type == TOK_LET ||
-#endif
- (type == TOK_VAR && !pn3->maybeExpr())) &&
- js_NewSrcNote2(cx, cg, SRC_DECL,
- (type == TOK_VAR)
- ? SRC_DECL_VAR
- : SRC_DECL_LET) < 0) {
- return JS_FALSE;
- }
- UpvarCookie cookie = pn3->pn_cookie;
- if (!cookie.isFree()) {
- op = PN_OP(pn3);
- switch (op) {
- case JSOP_GETARG:
- case JSOP_SETARG:
- op = JSOP_FORARG;
- break;
- case JSOP_GETLOCAL:
- case JSOP_SETLOCAL:
- op = JSOP_FORLOCAL;
- break;
- case JSOP_GETGLOBAL:
- op = JSOP_FORGNAME;
- cookie.makeFree();
- break;
- default:
- JS_NOT_REACHED("unexpected opcode");
- }
- } else {
- pn3->pn_op = JSOP_FORNAME;
- if (!BindNameToSlot(cx, cg, pn3))
- return JS_FALSE;
- op = PN_OP(pn3);
- cookie = pn3->pn_cookie;
- }
- if (pn3->isConst()) {
- ReportCompileErrorNumber(cx, CG_TS(cg), pn3, JSREPORT_ERROR,
- JSMSG_BAD_FOR_LEFTSIDE);
- return JS_FALSE;
- }
- if (!cookie.isFree()) {
- atomIndex = cookie.asInteger();
- EMIT_UINT16_IMM_OP(op, atomIndex);
- } else {
- if (!EmitAtomOp(cx, pn3, op, cg))
- return JS_FALSE;
- }
- break;
- }
-
- case TOK_DOT:
- /*
- * 'for (o.p in q)' can use JSOP_FORPROP only if evaluating 'o'
- * has no side effects.
- */
- useful = JS_FALSE;
- if (!CheckSideEffects(cx, cg, pn3->expr(), &useful))
- return JS_FALSE;
- if (!useful) {
- if (!EmitPropOp(cx, pn3, JSOP_FORPROP, cg, JS_FALSE))
- return JS_FALSE;
- break;
- }
- /* FALL THROUGH */
-
-#if JS_HAS_DESTRUCTURING
- destructuring_for:
-#endif
- default:
- if (js_Emit1(cx, cg, JSOP_FORELEM) < 0)
- return JS_FALSE;
- JS_ASSERT(cg->stackDepth >= 2);
-
-#if JS_HAS_DESTRUCTURING
- if (pn3->pn_type == TOK_RB || pn3->pn_type == TOK_RC) {
- if (!EmitDestructuringOps(cx, cg, op, pn3))
- return JS_FALSE;
- if (js_Emit1(cx, cg, JSOP_POP) < 0)
- return JS_FALSE;
- } else
-#endif
- if (pn3->pn_type == TOK_LP) {
- JS_ASSERT(pn3->pn_xflags & PNX_SETCALL);
- if (!js_EmitTree(cx, cg, pn3))
- return JS_FALSE;
- if (js_Emit1(cx, cg, JSOP_ENUMELEM) < 0)
- return JS_FALSE;
- } else
-#if JS_HAS_XML_SUPPORT
- if (pn3->pn_type == TOK_UNARYOP) {
- JS_ASSERT(pn3->pn_op == JSOP_BINDXMLNAME);
- if (!js_EmitTree(cx, cg, pn3))
- return JS_FALSE;
- if (js_Emit1(cx, cg, JSOP_ENUMELEM) < 0)
- return JS_FALSE;
- } else
-#endif
- if (!EmitElemOp(cx, pn3, JSOP_ENUMELEM, cg))
- return JS_FALSE;
- break;
+ if (!EmitAssignment(cx, cg, pn2->pn_kid2, JSOP_NOP, NULL))
+ return false;
+ tmp2 = CG_OFFSET(cg);
+ if (pn2->pn_kid1 && !js_NewSrcNote2(cx, cg, SRC_DECL,
+ (pn2->pn_kid1->pn_op == JSOP_DEFVAR)
+ ? SRC_DECL_VAR
+ : SRC_DECL_LET) < 0) {
+ return false;
}
-
- /* The stack should be balanced around the JSOP_FOR* opcode sequence. */
+ if (js_Emit1(cx, cg, JSOP_POP) < 0)
+ return false;
+
+ /* The stack should be balanced around the assignment opcode sequence. */
JS_ASSERT(cg->stackDepth == loopDepth);
- tmp2 = CG_OFFSET(cg);
-
/* Emit code for the loop body. */
if (!js_EmitTree(cx, cg, pn->pn_right))
return JS_FALSE;
/* Set loop and enclosing "update" offsets, for continue. */
stmt = &stmtInfo;
do {
stmt->update = CG_OFFSET(cg);
@@ -6102,190 +6153,18 @@ js_EmitTree(JSContext *cx, JSCodeGenerat
if (noteIndex < 0 ||
js_Emit1(cx, cg, JSOP_POP) < 0) {
return JS_FALSE;
}
}
break;
case TOK_ASSIGN:
- /*
- * Check left operand type and generate specialized code for it.
- * Specialize to avoid ECMA "reference type" values on the operand
- * stack, which impose pervasive runtime "GetValue" costs.
- */
- pn2 = pn->pn_left;
- atomIndex = (jsatomid) -1; /* quell GCC overwarning */
- switch (PN_TYPE(pn2)) {
- case TOK_NAME:
- if (!BindNameToSlot(cx, cg, pn2))
- return JS_FALSE;
- if (!pn2->pn_cookie.isFree()) {
- atomIndex = pn2->pn_cookie.asInteger();
- } else {
- if (!cg->makeAtomIndex(pn2->pn_atom, &atomIndex))
- return JS_FALSE;
- if (!pn2->isConst()) {
- JSOp op = PN_OP(pn2) == JSOP_SETGNAME ? JSOP_BINDGNAME : JSOP_BINDNAME;
- EMIT_INDEX_OP(op, atomIndex);
- }
- }
- break;
- case TOK_DOT:
- if (!js_EmitTree(cx, cg, pn2->expr()))
- return JS_FALSE;
- if (!cg->makeAtomIndex(pn2->pn_atom, &atomIndex))
- return JS_FALSE;
- break;
- case TOK_LB:
- JS_ASSERT(pn2->pn_arity == PN_BINARY);
- if (!js_EmitTree(cx, cg, pn2->pn_left))
- return JS_FALSE;
- if (!js_EmitTree(cx, cg, pn2->pn_right))
- return JS_FALSE;
- break;
-#if JS_HAS_DESTRUCTURING
- case TOK_RB:
- case TOK_RC:
- break;
-#endif
- case TOK_LP:
- if (!js_EmitTree(cx, cg, pn2))
- return JS_FALSE;
- break;
-#if JS_HAS_XML_SUPPORT
- case TOK_UNARYOP:
- JS_ASSERT(pn2->pn_op == JSOP_SETXMLNAME);
- if (!js_EmitTree(cx, cg, pn2->pn_kid))
- return JS_FALSE;
- if (js_Emit1(cx, cg, JSOP_BINDXMLNAME) < 0)
- return JS_FALSE;
- break;
-#endif
- default:
- JS_ASSERT(0);
- }
-
- op = PN_OP(pn);
- if (op != JSOP_NOP) {
- switch (pn2->pn_type) {
- case TOK_NAME:
- if (pn2->isConst()) {
- if (PN_OP(pn2) == JSOP_CALLEE) {
- if (js_Emit1(cx, cg, JSOP_CALLEE) < 0)
- return JS_FALSE;
- } else {
- EMIT_INDEX_OP(PN_OP(pn2), atomIndex);
- }
- } else if (PN_OP(pn2) == JSOP_SETNAME) {
- if (js_Emit1(cx, cg, JSOP_DUP) < 0)
- return JS_FALSE;
- EMIT_INDEX_OP(JSOP_GETXPROP, atomIndex);
- } else if (PN_OP(pn2) == JSOP_SETGNAME) {
- if (!BindGlobal(cx, cg, pn2, pn2->pn_atom))
- return JS_FALSE;
- if (pn2->pn_cookie.isFree())
- EmitAtomOp(cx, pn2, JSOP_GETGNAME, cg);
- else
- EMIT_UINT16_IMM_OP(JSOP_GETGLOBAL, pn2->pn_cookie.asInteger());
- } else {
- EMIT_UINT16_IMM_OP((PN_OP(pn2) == JSOP_SETARG)
- ? JSOP_GETARG
- : JSOP_GETLOCAL,
- atomIndex);
- }
- break;
- case TOK_DOT:
- if (js_Emit1(cx, cg, JSOP_DUP) < 0)
- return JS_FALSE;
- if (pn2->pn_atom == cx->runtime->atomState.lengthAtom) {
- if (js_Emit1(cx, cg, JSOP_LENGTH) < 0)
- return JS_FALSE;
- } else if (pn2->pn_atom == cx->runtime->atomState.protoAtom) {
- if (!EmitIndexOp(cx, JSOP_QNAMEPART, atomIndex, cg))
- return JS_FALSE;
- if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
- return JS_FALSE;
- } else {
- EMIT_INDEX_OP(JSOP_GETPROP, atomIndex);
- }
- break;
- case TOK_LB:
- case TOK_LP:
-#if JS_HAS_XML_SUPPORT
- case TOK_UNARYOP:
-#endif
- if (js_Emit1(cx, cg, JSOP_DUP2) < 0)
- return JS_FALSE;
- if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
- return JS_FALSE;
- break;
- default:;
- }
- }
-
- /* Now emit the right operand (it may affect the namespace). */
- if (!js_EmitTree(cx, cg, pn->pn_right))
- return JS_FALSE;
-
- /* If += etc., emit the binary operator with a decompiler note. */
- if (op != JSOP_NOP) {
- /*
- * Take care to avoid SRC_ASSIGNOP if the left-hand side is a const
- * declared in the current compilation unit, as in this case (just
- * a bit further below) we will avoid emitting the assignment op.
- */
- if (pn2->pn_type != TOK_NAME || !pn2->isConst()) {
- if (js_NewSrcNote(cx, cg, SRC_ASSIGNOP) < 0)
- return JS_FALSE;
- }
- if (js_Emit1(cx, cg, op) < 0)
- return JS_FALSE;
- }
-
- /* Left parts such as a.b.c and a[b].c need a decompiler note. */
- if (pn2->pn_type != TOK_NAME &&
-#if JS_HAS_DESTRUCTURING
- pn2->pn_type != TOK_RB &&
- pn2->pn_type != TOK_RC &&
-#endif
- js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0) {
- return JS_FALSE;
- }
-
- /* Finally, emit the specialized assignment bytecode. */
- switch (pn2->pn_type) {
- case TOK_NAME:
- if (pn2->isConst())
- break;
- /* FALL THROUGH */
- case TOK_DOT:
- EMIT_INDEX_OP(PN_OP(pn2), atomIndex);
- break;
- case TOK_LB:
- case TOK_LP:
- if (js_Emit1(cx, cg, JSOP_SETELEM) < 0)
- return JS_FALSE;
- break;
-#if JS_HAS_DESTRUCTURING
- case TOK_RB:
- case TOK_RC:
- if (!EmitDestructuringOps(cx, cg, JSOP_SETNAME, pn2))
- return JS_FALSE;
- break;
-#endif
-#if JS_HAS_XML_SUPPORT
- case TOK_UNARYOP:
- if (js_Emit1(cx, cg, JSOP_SETXMLNAME) < 0)
- return JS_FALSE;
- break;
-#endif
- default:
- JS_ASSERT(0);
- }
+ if (!EmitAssignment(cx, cg, pn->pn_left, PN_OP(pn), pn->pn_right))
+ return false;
break;
case TOK_HOOK:
/* Emit the condition, then branch if false to the else part. */
if (!js_EmitTree(cx, cg, pn->pn_kid1))
return JS_FALSE;
noteIndex = js_NewSrcNote(cx, cg, SRC_COND);
if (noteIndex < 0)
--- a/js/src/jsinterp.cpp
+++ b/js/src/jsinterp.cpp
@@ -2570,105 +2570,37 @@ BEGIN_CASE(JSOP_MOREITER)
bool cond;
if (!IteratorMore(cx, ®s.sp[-2].toObject(), &cond, ®s.sp[-1]))
goto error;
CHECK_INTERRUPT_HANDLER();
regs.sp[-1].setBoolean(cond);
}
END_CASE(JSOP_MOREITER)
+BEGIN_CASE(JSOP_ITERNEXT)
+{
+ Value *itervp = regs.sp - GET_INT8(regs.pc);
+ JS_ASSERT(itervp >= regs.fp()->base());
+ JS_ASSERT(itervp->isObject());
+ PUSH_NULL();
+ if (!IteratorNext(cx, &itervp->toObject(), ®s.sp[-1]))
+ goto error;
+}
+END_CASE(JSOP_ITERNEXT)
+
BEGIN_CASE(JSOP_ENDITER)
{
JS_ASSERT(regs.sp - 1 >= regs.fp()->base());
bool ok = !!js_CloseIterator(cx, ®s.sp[-1].toObject());
regs.sp--;
if (!ok)
goto error;
}
END_CASE(JSOP_ENDITER)
-BEGIN_CASE(JSOP_FORARG)
-{
- JS_ASSERT(regs.sp - 1 >= regs.fp()->base());
- uintN slot = GET_ARGNO(regs.pc);
- JS_ASSERT(slot < regs.fp()->numFormalArgs());
- JS_ASSERT(regs.sp[-1].isObject());
- if (!IteratorNext(cx, ®s.sp[-1].toObject(), &argv[slot]))
- goto error;
-}
-END_CASE(JSOP_FORARG)
-
-BEGIN_CASE(JSOP_FORLOCAL)
-{
- JS_ASSERT(regs.sp - 1 >= regs.fp()->base());
- uintN slot = GET_SLOTNO(regs.pc);
- JS_ASSERT(slot < regs.fp()->numSlots());
- JS_ASSERT(regs.sp[-1].isObject());
- if (!IteratorNext(cx, ®s.sp[-1].toObject(), ®s.fp()->slots()[slot]))
- goto error;
-}
-END_CASE(JSOP_FORLOCAL)
-
-BEGIN_CASE(JSOP_FORNAME)
-BEGIN_CASE(JSOP_FORGNAME)
-{
- JS_ASSERT(regs.sp - 1 >= regs.fp()->base());
- JSAtom *atom;
- LOAD_ATOM(0, atom);
- jsid id = ATOM_TO_JSID(atom);
- JSObject *obj, *obj2;
- JSProperty *prop;
- if (!js_FindProperty(cx, id, &obj, &obj2, &prop))
- goto error;
-
- {
- AutoValueRooter tvr(cx);
- JS_ASSERT(regs.sp[-1].isObject());
- if (!IteratorNext(cx, ®s.sp[-1].toObject(), tvr.addr()))
- goto error;
- if (!obj->setProperty(cx, id, tvr.addr(), script->strictModeCode))
- goto error;
- }
-}
-END_CASE(JSOP_FORNAME)
-
-BEGIN_CASE(JSOP_FORPROP)
-{
- JS_ASSERT(regs.sp - 2 >= regs.fp()->base());
- JSAtom *atom;
- LOAD_ATOM(0, atom);
- jsid id = ATOM_TO_JSID(atom);
- JSObject *obj;
- FETCH_OBJECT(cx, -1, obj);
- {
- AutoValueRooter tvr(cx);
- JS_ASSERT(regs.sp[-2].isObject());
- if (!IteratorNext(cx, ®s.sp[-2].toObject(), tvr.addr()))
- goto error;
- if (!obj->setProperty(cx, id, tvr.addr(), script->strictModeCode))
- goto error;
- }
- regs.sp--;
-}
-END_CASE(JSOP_FORPROP)
-
-BEGIN_CASE(JSOP_FORELEM)
- /*
- * JSOP_FORELEM simply dups the property identifier at top of stack and
- * lets the subsequent JSOP_ENUMELEM opcode sequence handle the left-hand
- * side expression evaluation and assignment. This opcode exists solely to
- * help the decompiler.
- */
- JS_ASSERT(regs.sp - 1 >= regs.fp()->base());
- JS_ASSERT(regs.sp[-1].isObject());
- PUSH_NULL();
- if (!IteratorNext(cx, ®s.sp[-2].toObject(), ®s.sp[-1]))
- goto error;
-END_CASE(JSOP_FORELEM)
-
BEGIN_CASE(JSOP_DUP)
{
JS_ASSERT(regs.sp > regs.fp()->base());
const Value &rref = regs.sp[-1];
PUSH_COPY(rref);
}
END_CASE(JSOP_DUP)
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -3098,44 +3098,41 @@ Decompile(SprintStack *ss, jsbytecode *p
#if JS_HAS_GENERATOR_EXPRS
LOCAL_ASSERT(SN_TYPE(sn) == SRC_HIDDEN);
/* FALL THROUGH */
#endif
case JSOP_ARRAYPUSH:
{
- uintN pos, forpos;
- ptrdiff_t start;
-
/* Turn off most parens. */
op = JSOP_SETNAME;
/* Pop the expression being pushed or yielded. */
rval = POP_STR();
/*
- * 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).
+ * Skip the for loop head stacked by JSOP_GOTO:SRC_FOR_IN until
+ * we hit a block local slot (note empty destructuring patterns
+ * result in unit-count blocks).
*/
- pos = ss->top;
+ uintN pos = ss->top;
while (pos != 0) {
op = (JSOp) ss->opcodes[--pos];
if (op == JSOP_ENTERBLOCK)
break;
}
JS_ASSERT(op == JSOP_ENTERBLOCK);
/*
* 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 + 1;
+ uintN forpos = pos + 1;
LOCAL_ASSERT(forpos < ss->top);
/*
* 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)
@@ -3167,17 +3164,17 @@ Decompile(SprintStack *ss, jsbytecode *p
/*
* Array comprehension: retract the sprinter to the beginning
* of the array initialiser and decompile "[<rval> for ...]".
*/
JS_ASSERT(jp->script->nfixed + pos == GET_UINT16(pc));
LOCAL_ASSERT(ss->opcodes[pos] == JSOP_NEWINIT);
- start = ss->offsets[pos];
+ ptrdiff_t start = ss->offsets[pos];
LOCAL_ASSERT(ss->sprinter.base[start] == '[' ||
ss->sprinter.base[start] == '#');
LOCAL_ASSERT(forpos < ss->top);
xval = OFF2STR(&ss->sprinter, ss->offsets[forpos]);
lval = OFF2STR(&ss->sprinter, start);
RETRACT(&ss->sprinter, lval);
todo = Sprint(&ss->sprinter, sss_format, lval, rval, xval);
@@ -3221,28 +3218,57 @@ Decompile(SprintStack *ss, jsbytecode *p
break;
case JSOP_GOTO:
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 push the left-hand side of 'in'
- * after the loop edge in this stack slot (the JSOP_FOR*
- * opcodes' decompilers do this pushing).
+ * The bytecode around pc looks like this:
+ * <<RHS>>
+ * iter
+ * pc: goto/gotox C [src_for_in(B, D)]
+ * A: <<LHS = iternext>>
+ * B: pop [maybe a src_decl_var/let]
+ * <<S>>
+ * C: moreiter
+ * ifne/ifnex A
+ * enditer
+ * D: ...
+ *
+ * In an array comprehension or generator expression, we
+ * construct the for-head and store it in the slot pushed
+ * by JSOP_ITER, then recurse to decompile S. The
+ * culminating JSOP_ARRAYPUSH or JSOP_YIELD instruction
+ * (which S must contain, by construction) glues all the
+ * clauses together.
+ *
+ * Otherwise this is a for-in statement. We eagerly output
+ * the for-head and recurse to decompile the controlled
+ * statement S.
+ *
+ * We never decompile the obligatory JSOP_POP,
+ * JSOP_MOREITER or JSOP_IFNE, though we do quick asserts
+ * to check that they are there.
*/
cond = GetJumpOffset(pc, pc);
next = js_GetSrcNoteOffset(sn, 0);
tail = js_GetSrcNoteOffset(sn, 1);
+ JS_ASSERT(pc[next] == JSOP_POP);
JS_ASSERT(pc[cond] == JSOP_MOREITER);
DECOMPILE_CODE(pc + oplen, next - oplen);
lval = POP_STR();
+
+ /*
+ * This string "<next>" comes from jsopcode.tbl. It stands
+ * for the result pushed by JSOP_ITERNEXT.
+ */
+ JS_ASSERT(strcmp(lval + strlen(lval) - 9, " = <next>") == 0);
+ const_cast<char *>(lval)[strlen(lval) - 9] = '\0';
LOCAL_ASSERT(ss->top >= 1);
if (ss->inArrayInit || ss->inGenExp) {
rval = POP_STR();
if (ss->top >= 1 && ss->opcodes[ss->top - 1] == JSOP_FORLOCAL) {
ss->sprinter.offset = ss->offsets[ss->top] - PAREN_SLOP;
if (Sprint(&ss->sprinter, " %s (%s in %s)",
foreach ? js_for_each_str : js_for_str,
@@ -3259,28 +3285,29 @@ Decompile(SprintStack *ss, jsbytecode *p
todo = ss->offsets[ss->top - 1];
} else {
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);
+ DECOMPILE_CODE(pc + next + JSOP_POP_LENGTH, cond - next - JSOP_POP_LENGTH);
} else {
/*
* As above, rval or an extension of it must remain
* stacked during loop body decompilation.
*/
rval = GetStr(ss, ss->top - 1);
- js_printf(jp, "\t%s (%s in %s) {\n",
+ xval = VarPrefix(js_GetSrcNote(jp->script, pc + next));
+ js_printf(jp, "\t%s (%s%s in %s) {\n",
foreach ? js_for_each_str : js_for_str,
- lval, rval);
+ xval, lval, rval);
jp->indent += 4;
- DECOMPILE_CODE(pc + next, cond - next);
+ DECOMPILE_CODE(pc + next + JSOP_POP_LENGTH, cond - next - JSOP_POP_LENGTH);
jp->indent -= 4;
js_printf(jp, "\t}\n");
}
pc += tail;
LOCAL_ASSERT(*pc == JSOP_IFNE || *pc == JSOP_IFNEX);
len = js_CodeSpec[*pc].length;
break;
@@ -3475,73 +3502,16 @@ Decompile(SprintStack *ss, jsbytecode *p
cx->free_((char *)lval);
break;
case JSOP_AND:
case JSOP_ANDX:
xval = "&&";
goto do_logical_connective;
- case JSOP_FORARG:
- sn = NULL;
- i = GET_ARGNO(pc);
- goto do_forvarslot;
-
- case JSOP_FORLOCAL:
- 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;
- }
-
- do_forvarslot:
- atom = GetArgOrVarAtom(jp, i);
- LOCAL_ASSERT(atom);
- todo = SprintCString(&ss->sprinter, VarPrefix(sn));
- if (todo < 0 || !QuoteString(&ss->sprinter, atom, 0))
- return NULL;
- break;
-
- case JSOP_FORNAME:
- case JSOP_FORGNAME:
- LOAD_ATOM(0);
-
- sn = js_GetSrcNote(jp->script, pc);
- todo = SprintCString(&ss->sprinter, VarPrefix(sn));
- if (todo < 0 || !QuoteString(&ss->sprinter, atom, 0))
- return NULL;
- break;
-
- case JSOP_FORPROP:
- xval = NULL;
- LOAD_ATOM(0);
- if (!ATOM_IS_IDENTIFIER(atom)) {
- xval = QuoteString(&ss->sprinter, atom,
- (jschar)'\'');
- if (!xval)
- return NULL;
- }
- lval = POP_STR();
- if (xval) {
- JS_ASSERT(*lval);
- todo = Sprint(&ss->sprinter, index_format, lval, xval);
- } else {
- todo = Sprint(&ss->sprinter, ss_format, lval, *lval ? "." : "");
- if (todo < 0)
- return NULL;
- if (!QuoteString(&ss->sprinter, atom, 0))
- return NULL;
- }
- break;
-
- case JSOP_FORELEM:
- todo = SprintCString(&ss->sprinter, forelem_cookie);
- break;
-
case JSOP_ENUMELEM:
case JSOP_ENUMCONSTELEM:
/*
* The stack has the object under the (top) index expression.
* The "rval" property id is underneath those two on the stack.
* The for loop body net and gross lengths can now be adjusted
* to account for the length of the indexing expression that
* came after JSOP_FORELEM and before JSOP_ENUMELEM.
--- a/js/src/jsopcode.h
+++ b/js/src/jsopcode.h
@@ -60,23 +60,24 @@ JS_BEGIN_EXTERN_C
typedef enum JSOp {
#define OPDEF(op,val,name,token,length,nuses,ndefs,prec,format) \
op = val,
#include "jsopcode.tbl"
#undef OPDEF
JSOP_LIMIT,
/*
- * These pseudo-ops help js_DecompileValueGenerator decompile JSOP_SETNAME,
- * JSOP_SETPROP, and JSOP_SETELEM, respectively. They are never stored in
- * bytecode, so they don't preempt valid opcodes.
+ * These pseudo-ops help js_DecompileValueGenerator decompile JSOP_SETPROP,
+ * JSOP_SETELEM, and comprehension-tails, respectively. They are never
+ * stored in bytecode, so they don't preempt valid opcodes.
*/
JSOP_GETPROP2 = JSOP_LIMIT,
JSOP_GETELEM2 = JSOP_LIMIT + 1,
- JSOP_FAKE_LIMIT = JSOP_GETELEM2
+ JSOP_FORLOCAL = JSOP_LIMIT + 2,
+ JSOP_FAKE_LIMIT = JSOP_FORLOCAL
} JSOp;
/*
* JS bytecode formats.
*/
#define JOF_BYTE 0 /* single bytecode, no immediates */
#define JOF_JUMP 1 /* signed 16-bit jump offset immediate */
#define JOF_ATOM 2 /* unsigned 16-bit constant index */
--- a/js/src/jsopcode.tbl
+++ b/js/src/jsopcode.tbl
@@ -120,19 +120,18 @@ OPDEF(JSOP_LEAVEWITH, 4, "leavewith",
OPDEF(JSOP_RETURN, 5, "return", NULL, 1, 1, 0, 2, JOF_BYTE)
OPDEF(JSOP_GOTO, 6, "goto", NULL, 3, 0, 0, 0, JOF_JUMP)
OPDEF(JSOP_IFEQ, 7, "ifeq", NULL, 3, 1, 0, 4, JOF_JUMP|JOF_DETECTING)
OPDEF(JSOP_IFNE, 8, "ifne", NULL, 3, 1, 0, 0, JOF_JUMP|JOF_PARENHEAD)
/* Get the arguments object for the current, lightweight function activation. */
OPDEF(JSOP_ARGUMENTS, 9, js_arguments_str, js_arguments_str, 1, 0, 1, 18, JOF_BYTE)
-/* ECMA-compliant for-in loop with argument or local loop control. */
-OPDEF(JSOP_FORARG, 10, "forarg", NULL, 3, 1, 1, 19, JOF_QARG|JOF_NAME|JOF_FOR|JOF_TMPSLOT)
-OPDEF(JSOP_FORLOCAL, 11, "forlocal", NULL, 3, 1, 1, 19, JOF_LOCAL|JOF_NAME|JOF_FOR|JOF_TMPSLOT)
+OPDEF(JSOP_SWAP, 10, "swap", NULL, 1, 2, 2, 0, JOF_BYTE)
+OPDEF(JSOP_POPN, 11, "popn", NULL, 3, -1, 0, 0, JOF_UINT16)
/* More long-standing bytecodes. */
OPDEF(JSOP_DUP, 12, "dup", NULL, 1, 1, 2, 0, JOF_BYTE)
OPDEF(JSOP_DUP2, 13, "dup2", NULL, 1, 2, 4, 0, JOF_BYTE)
OPDEF(JSOP_SETCONST, 14, "setconst", NULL, 3, 1, 1, 3, JOF_ATOM|JOF_NAME|JOF_SET)
OPDEF(JSOP_BITOR, 15, "bitor", "|", 1, 2, 1, 7, JOF_BYTE|JOF_LEFTASSOC)
OPDEF(JSOP_BITXOR, 16, "bitxor", "^", 1, 2, 1, 8, JOF_BYTE|JOF_LEFTASSOC)
OPDEF(JSOP_BITAND, 17, "bitand", "&", 1, 2, 1, 9, JOF_BYTE|JOF_LEFTASSOC)
@@ -214,20 +213,20 @@ OPDEF(JSOP_SETCALL, 74, "setcall",
* true if another value is available, and false otherwise. It is followed
* immediately by JSOP_IFNE{,X}.
*
* JSOP_ENDITER cleans up after the loop. It uses the slot above the iterator
* for temporary GC rooting.
*/
OPDEF(JSOP_ITER, 75, "iter", NULL, 2, 1, 1, 0, JOF_UINT8)
OPDEF(JSOP_MOREITER, 76, "moreiter", NULL, 1, 1, 2, 0, JOF_BYTE)
-OPDEF(JSOP_ENDITER, 77, "enditer", NULL, 1, 1, 0, 0, JOF_BYTE)
+OPDEF(JSOP_ITERNEXT, 77, "iternext", "<next>", 2, 0, 1, 0, JOF_UINT8)
+OPDEF(JSOP_ENDITER, 78, "enditer", NULL, 1, 1, 0, 0, JOF_BYTE)
-OPDEF(JSOP_FUNAPPLY, 78, "funapply", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE)
-OPDEF(JSOP_SWAP, 79, "swap", NULL, 1, 2, 2, 0, JOF_BYTE)
+OPDEF(JSOP_FUNAPPLY, 79, "funapply", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE)
/* Push object literal: either an XML object or initialiser object. */
OPDEF(JSOP_OBJECT, 80, "object", NULL, 3, 0, 1, 19, JOF_OBJECT)
/* Pop value and discard it. */
OPDEF(JSOP_POP, 81, "pop", NULL, 1, 1, 0, 2, JOF_BYTE)
/* Call a function as a constructor; operand is argc. */
@@ -270,21 +269,25 @@ OPDEF(JSOP_ARGDEC, 100, "argdec",
OPDEF(JSOP_INCLOCAL, 101,"inclocal", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_INC|JOF_TMPSLOT3)
OPDEF(JSOP_DECLOCAL, 102,"declocal", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_TMPSLOT3)
OPDEF(JSOP_LOCALINC, 103,"localinc", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_INC|JOF_POST|JOF_TMPSLOT3)
OPDEF(JSOP_LOCALDEC, 104,"localdec", NULL, 3, 0, 1, 15, JOF_LOCAL|JOF_NAME|JOF_DEC|JOF_POST|JOF_TMPSLOT3)
OPDEF(JSOP_IMACOP, 105,"imacop", NULL, 1, 0, 0, 0, JOF_BYTE)
-/* ECMA-compliant for/in ops. */
-OPDEF(JSOP_FORNAME, 106,"forname", NULL, 3, 1, 1, 19, JOF_ATOM|JOF_NAME|JOF_FOR|JOF_TMPSLOT3)
-OPDEF(JSOP_FORPROP, 107,"forprop", NULL, 3, 2, 1, 18, JOF_ATOM|JOF_PROP|JOF_FOR|JOF_TMPSLOT3)
-OPDEF(JSOP_FORELEM, 108,"forelem", NULL, 1, 1, 2, 18, JOF_BYTE |JOF_ELEM|JOF_FOR)
-OPDEF(JSOP_POPN, 109,"popn", NULL, 3, -1, 0, 0, JOF_UINT16)
+/* Static binding for globals. */
+OPDEF(JSOP_GETGLOBAL, 106,"getglobal", NULL, 3, 0, 1, 19, JOF_GLOBAL|JOF_NAME)
+OPDEF(JSOP_CALLGLOBAL,107,"callglobal", NULL, 3, 0, 2, 19, JOF_GLOBAL|JOF_NAME|JOF_CALLOP)
+
+/* Like JSOP_FUNAPPLY but for f.call instead of f.apply. */
+OPDEF(JSOP_FUNCALL, 108,"funcall", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE)
+
+/* This opcode stores an index that is unique to the given loop. */
+OPDEF(JSOP_TRACE, 109,"trace", NULL, 3, 0, 0, 0, JOF_UINT16)
/* ECMA-compliant assignment ops. */
OPDEF(JSOP_BINDNAME, 110,"bindname", NULL, 3, 0, 1, 0, JOF_ATOM|JOF_NAME|JOF_SET)
OPDEF(JSOP_SETNAME, 111,"setname", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_NAME|JOF_SET|JOF_DETECTING)
/* Exception handling ops. */
OPDEF(JSOP_THROW, 112,js_throw_str, NULL, 1, 1, 0, 0, JOF_BYTE)
@@ -581,29 +584,17 @@ OPDEF(JSOP_LAMBDA_FC, 227,"lambda_fc
/*
* Ensure that the value on the top of the stack is an object. The one
* argument is an error message, defined in js.msg, that takes one parameter
* (the decompilation of the primitive value).
*/
OPDEF(JSOP_OBJTOP, 228,"objtop", NULL, 3, 0, 0, 0, JOF_UINT16)
-/* This opcode stores an index that is unique to the given loop. */
-OPDEF(JSOP_TRACE, 229, "trace", NULL, 3, 0, 0, 0, JOF_UINT16)
-
/*
* Joined function object as method optimization support.
*/
-OPDEF(JSOP_SETMETHOD, 230,"setmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
-OPDEF(JSOP_INITMETHOD, 231,"initmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
-OPDEF(JSOP_UNBRAND, 232,"unbrand", NULL, 1, 1, 1, 0, JOF_BYTE)
-OPDEF(JSOP_UNBRANDTHIS, 233,"unbrandthis", NULL, 1, 0, 0, 0, JOF_BYTE)
-
-OPDEF(JSOP_SHARPINIT, 234,"sharpinit", NULL, 3, 0, 0, 0, JOF_UINT16|JOF_SHARPSLOT)
+OPDEF(JSOP_SETMETHOD, 229,"setmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
+OPDEF(JSOP_INITMETHOD, 230,"initmethod", NULL, 3, 2, 1, 3, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING)
+OPDEF(JSOP_UNBRAND, 231,"unbrand", NULL, 1, 1, 1, 0, JOF_BYTE)
+OPDEF(JSOP_UNBRANDTHIS, 232,"unbrandthis", NULL, 1, 0, 0, 0, JOF_BYTE)
-/* Static binding for globals. */
-OPDEF(JSOP_GETGLOBAL, 235,"getglobal", NULL, 3, 0, 1, 19, JOF_GLOBAL|JOF_NAME)
-OPDEF(JSOP_CALLGLOBAL, 236,"callglobal", NULL, 3, 0, 2, 19, JOF_GLOBAL|JOF_NAME|JOF_CALLOP)
-
-/* Like JSOP_FUNAPPLY but for f.call instead of f.apply. */
-OPDEF(JSOP_FUNCALL, 237,"funcall", NULL, 3, -1, 1, 18, JOF_UINT16|JOF_INVOKE)
-
-OPDEF(JSOP_FORGNAME, 238,"forgname", NULL, 3, 1, 1, 19, JOF_ATOM|JOF_GNAME|JOF_FOR|JOF_TMPSLOT3)
+OPDEF(JSOP_SHARPINIT, 233,"sharpinit", NULL, 3, 0, 0, 0, JOF_UINT16|JOF_SHARPSLOT)
--- a/js/src/jsparse.cpp
+++ b/js/src/jsparse.cpp
@@ -695,22 +695,29 @@ NewOrRecycledNode(JSTreeContext *tc)
return pn;
}
/* used only by static create methods of subclasses */
JSParseNode *
JSParseNode::create(JSParseNodeArity arity, JSTreeContext *tc)
{
+ const Token &tok = tc->parser->tokenStream.currentToken();
+ return create(arity, tok.type, JSOP_NOP, tok.pos, tc);
+}
+
+JSParseNode *
+JSParseNode::create(JSParseNodeArity arity, TokenKind type, JSOp op, const TokenPos &pos,
+ JSTreeContext *tc)
+{
JSParseNode *pn = NewOrRecycledNode(tc);
if (!pn)
return NULL;
- const Token &tok = tc->parser->tokenStream.currentToken();
- pn->init(tok.type, JSOP_NOP, arity);
- pn->pn_pos = tok.pos;
+ pn->init(type, op, arity);
+ pn->pn_pos = pos;
return pn;
}
JSParseNode *
JSParseNode::newBinaryOrAppend(TokenKind tt, JSOp op, JSParseNode *left, JSParseNode *right,
JSTreeContext *tc)
{
JSParseNode *pn, *pn1, *pn2;
@@ -773,17 +780,17 @@ JSParseNode::newBinaryOrAppend(TokenKind
pn = NewOrRecycledNode(tc);
if (!pn)
return NULL;
pn->init(tt, op, PN_BINARY);
pn->pn_pos.begin = left->pn_pos.begin;
pn->pn_pos.end = right->pn_pos.end;
pn->pn_left = left;
pn->pn_right = right;
- return (BinaryNode *)pn;
+ return pn;
}
namespace js {
inline void
NameNode::initCommon(JSTreeContext *tc)
{
pn_expr = NULL;
@@ -3566,16 +3573,25 @@ MatchLabel(JSContext *cx, TokenStream *t
label = ts->currentToken().t_atom;
} else {
label = NULL;
}
pn->pn_atom = label;
return JS_TRUE;
}
+/*
+ * Define a let-variable in a block, let-expression, or comprehension scope. tc
+ * must already be in such a scope.
+ *
+ * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a
+ * property for the new variable on the block object, tc->blockChain();
+ * populate data->pn->pn_{op,cookie,defn,dflags}; and stash a pointer to
+ * data->pn in a slot of the block object.
+ */
static JSBool
BindLet(JSContext *cx, BindData *data, JSAtom *atom, JSTreeContext *tc)
{
JSParseNode *pn;
JSObject *blockObj;
jsint n;
/*
@@ -4373,16 +4389,18 @@ Parser::destructuringExpr(BindData *data
/*
* Currently used only #if JS_HAS_DESTRUCTURING, in Statement's TOK_FOR case.
* This function assumes the cloned tree is for use in the same statement and
* binding context as the original tree.
*/
static JSParseNode *
CloneParseTree(JSParseNode *opn, JSTreeContext *tc)
{
+ JS_CHECK_RECURSION(tc->parser->context, return NULL);
+
JSParseNode *pn, *pn2, *opn2;
pn = NewOrRecycledNode(tc);
if (!pn)
return NULL;
pn->pn_type = opn->pn_type;
pn->pn_pos = opn->pn_pos;
pn->pn_op = opn->pn_op;
@@ -4884,16 +4902,96 @@ Parser::switchStatement()
PopStatement(tc);
pn->pn_pos.end = pn2->pn_pos.end = tokenStream.currentToken().pos.end;
pn->pn_left = pn1;
pn->pn_right = pn2;
return pn;
}
+/*
+ * Used by Parser::forStatement and comprehensionTail to clone the TARGET in
+ * for (var/const/let TARGET in EXPR)
+ *
+ * opn must be the pn_head of a node produced by Parser::variables, so its form
+ * is known to be LHS = NAME | [LHS] | {id:LHS}.
+ *
+ * The cloned tree is for use only in the same statement and binding context as
+ * the original tree.
+ */
+static JSParseNode *
+CloneLeftHandSide(JSParseNode *opn, JSTreeContext *tc)
+{
+ JSParseNode *pn = NewOrRecycledNode(tc);
+ if (!pn)
+ return NULL;
+ pn->pn_type = opn->pn_type;
+ pn->pn_pos = opn->pn_pos;
+ pn->pn_op = opn->pn_op;
+ pn->pn_used = opn->pn_used;
+ pn->pn_defn = opn->pn_defn;
+ pn->pn_arity = opn->pn_arity;
+ pn->pn_parens = opn->pn_parens;
+
+#if JS_HAS_DESTRUCTURING
+ if (opn->pn_arity == PN_LIST) {
+ JS_ASSERT(opn->pn_type == TOK_RB || opn->pn_type == TOK_RC);
+ pn->makeEmpty();
+ for (JSParseNode *opn2 = opn->pn_head; opn2; opn2 = opn2->pn_next) {
+ JSParseNode *pn2;
+ if (opn->pn_type == TOK_RC) {
+ JS_ASSERT(opn2->pn_arity == PN_BINARY);
+ JS_ASSERT(opn2->pn_type == TOK_COLON);
+
+ JSParseNode *tag = CloneParseTree(opn2->pn_left, tc);
+ if (!tag)
+ return NULL;
+ JSParseNode *target = CloneLeftHandSide(opn2->pn_right, tc);
+ if (!target)
+ return NULL;
+ pn2 = BinaryNode::create(TOK_COLON, JSOP_INITPROP, opn2->pn_pos, tag, target, tc);
+ } else if (opn2->pn_arity == PN_NULLARY) {
+ JS_ASSERT(opn2->pn_type == TOK_COMMA);
+ pn2 = CloneParseTree(opn2, tc);
+ } else {
+ pn2 = CloneLeftHandSide(opn2, tc);
+ }
+
+ if (!pn2)
+ return NULL;
+ pn->append(pn2);
+ }
+ pn->pn_xflags = opn->pn_xflags;
+ return pn;
+ }
+#endif
+
+ JS_ASSERT(opn->pn_arity == PN_NAME);
+ JS_ASSERT(opn->pn_type == TOK_NAME);
+
+ /* If opn is a definition or use, make pn a use. */
+ pn->pn_u.name = opn->pn_u.name;
+ pn->pn_op = JSOP_SETNAME;
+ if (opn->pn_used) {
+ JSDefinition *dn = pn->pn_lexdef;
+
+ pn->pn_link = dn->dn_uses;
+ dn->dn_uses = pn;
+ } else if (opn->pn_defn) {
+ /* We copied some definition-specific state into pn. Clear it out. */
+ pn->pn_expr = NULL;
+ pn->pn_cookie.makeFree();
+ pn->pn_dflags &= ~PND_BOUND;
+ pn->pn_defn = false;
+
+ LinkUseToDef(pn, (JSDefinition *) opn, tc);
+ }
+ return pn;
+}
+
JSParseNode *
Parser::forStatement()
{
JSParseNode *pnseq = NULL;
#if JS_HAS_BLOCK_SCOPE
JSParseNode *pnlet = NULL;
JSStmtInfo blockInfo;
#endif
@@ -4918,18 +5016,20 @@ Parser::forStatement()
TokenKind tt = tokenStream.peekToken(TSF_OPERAND);
#if JS_HAS_BLOCK_SCOPE
bool let = false;
#endif
JSParseNode *pn1;
if (tt == TOK_SEMI) {
- if (pn->pn_iflags & JSITER_FOREACH)
- goto bad_for_each;
+ if (pn->pn_iflags & JSITER_FOREACH) {
+ reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP);
+ return NULL;
+ }
/* No initializer -- set first kid of left sub-node to null. */
pn1 = NULL;
} else {
/*
* Set pn1 to a var list or an initializing expression.
*
* Set the TCF_IN_FOR_INIT flag during parsing of the first clause
@@ -4970,17 +5070,28 @@ Parser::forStatement()
}
/*
* We can be sure that it's a for/in loop if there's still an 'in'
* keyword here, even if JavaScript recognizes 'in' as an operator,
* as we've excluded 'in' from being parsed in RelExpr by setting
* the TCF_IN_FOR_INIT flag in our JSTreeContext.
*/
+ JSParseNode *pn2, *pn3;
+ JSParseNode *pn4 = TernaryNode::create(tc);
+ if (!pn4)
+ return NULL;
if (pn1 && tokenStream.matchToken(TOK_IN)) {
+ /*
+ * Parse the rest of the for/in head.
+ *
+ * Here pn1 is everything to the left of 'in'. At the end of this block,
+ * pn1 is a decl or NULL, pn2 is the assignment target that receives the
+ * enumeration value each iteration, and pn3 is the rhs of 'in'.
+ */
pn->pn_iflags |= JSITER_ENUMERATE;
stmtInfo.type = STMT_FOR_IN_LOOP;
/* Check that the left side of the 'in' is valid. */
JS_ASSERT(!TokenKindIsDecl(tt) || PN_TYPE(pn1) == tt);
if (TokenKindIsDecl(tt)
? (pn1->pn_count > 1 || pn1->pn_op == JSOP_DEFCONST
#if JS_HAS_DESTRUCTURING
@@ -5009,35 +5120,41 @@ Parser::forStatement()
(pn1->pn_type != TOK_UNARYOP ||
pn1->pn_op != JSOP_XMLNAME) &&
#endif
pn1->pn_type != TOK_LB)) {
reportErrorNumber(pn1, JSREPORT_ERROR, JSMSG_BAD_FOR_LEFTSIDE);
return NULL;
}
- /* pn2 points to the name or destructuring pattern on in's left. */
- JSParseNode *pn2 = NULL;
+ /*
+ * After the following if-else, pn2 will point to the name or
+ * destructuring pattern on in's left. pn1 will point to the decl, if
+ * any, else NULL. Note that the "declaration with initializer" case
+ * rewrites the loop-head, moving the decl and setting pn1 to NULL.
+ */
+ pn2 = NULL;
uintN dflag = PND_ASSIGNED;
-
if (TokenKindIsDecl(tt)) {
- /* Tell js_EmitTree(TOK_VAR) that pn1 is part of a for/in. */
+ /* Tell EmitVariables that pn1 is part of a for/in. */
pn1->pn_xflags |= PNX_FORINVAR;
- /*
- * Rewrite 'for (<decl> x = i in o)' where <decl> is 'var' or
- * 'const' to hoist the initializer or the entire decl out of
- * the loop head. TOK_VAR is the type for both 'var' and 'const'.
- */
pn2 = pn1->pn_head;
if ((pn2->pn_type == TOK_NAME && pn2->maybeExpr())
#if JS_HAS_DESTRUCTURING
|| pn2->pn_type == TOK_ASSIGN
#endif
) {
+ /*
+ * Declaration with initializer.
+ *
+ * Rewrite 'for (<decl> x = i in o)' where <decl> is 'var' or
+ * 'const' to hoist the initializer or the entire decl out of
+ * the loop head. TOK_VAR is the type for both 'var' and 'const'.
+ */
#if JS_HAS_BLOCK_SCOPE
if (tt == TOK_LET) {
reportErrorNumber(pn2, JSREPORT_ERROR, JSMSG_INVALID_FOR_IN_INIT);
return NULL;
}
#endif /* JS_HAS_BLOCK_SCOPE */
pnseq = ListNode::create(tc);
@@ -5057,65 +5174,53 @@ Parser::forStatement()
* side) and it has an initializer.
*/
pn1->pn_xflags &= ~PNX_FORINVAR;
pn1->pn_xflags |= PNX_POPVAR;
pnseq->initList(pn1);
#if JS_HAS_DESTRUCTURING
if (pn2->pn_type == TOK_ASSIGN) {
- pn1 = CloneParseTree(pn2->pn_left, tc);
- if (!pn1)
- return NULL;
- } else
+ pn2 = pn2->pn_left;
+ JS_ASSERT(pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC ||
+ pn2->pn_type == TOK_NAME);
+ }
#endif
- {
- JS_ASSERT(pn2->pn_type == TOK_NAME);
- pn1 = NameNode::create(pn2->pn_atom, tc);
- if (!pn1)
- return NULL;
- pn1->pn_type = TOK_NAME;
- pn1->pn_op = JSOP_NAME;
- pn1->pn_pos = pn2->pn_pos;
- if (pn2->pn_defn)
- LinkUseToDef(pn1, (JSDefinition *) pn2, tc);
- }
- pn2 = pn1;
- }
- }
-
- if (!pn2) {
+ pn1 = NULL;
+ }
+
+ /*
+ * pn2 is part of a declaration. Make a copy that can be passed to
+ * EmitAssignment.
+ */
+ pn2 = CloneLeftHandSide(pn2, tc);
+ if (!pn2)
+ return NULL;
+ } else {
+ /* Not a declaration. */
pn2 = pn1;
- if (pn2->pn_type == TOK_LP &&
- !MakeSetCall(context, pn2, tc, JSMSG_BAD_LEFTSIDE_OF_ASS)) {
+ pn1 = NULL;
+
+ if (!setAssignmentLhsOps(pn2, JSOP_NOP))
return NULL;
- }
-#if JS_HAS_XML_SUPPORT
- if (pn2->pn_type == TOK_UNARYOP)
- pn2->pn_op = JSOP_BINDXMLNAME;
-#endif
}
switch (pn2->pn_type) {
case TOK_NAME:
/* Beware 'for (arguments in ...)' with or without a 'var'. */
NoteLValue(context, pn2, tc, dflag);
break;
#if JS_HAS_DESTRUCTURING
case TOK_ASSIGN:
- pn2 = pn2->pn_left;
- JS_ASSERT(pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC);
- /* FALL THROUGH */
+ JS_NOT_REACHED("forStatement TOK_ASSIGN");
+ break;
+
case TOK_RB:
case TOK_RC:
- /* Check for valid lvalues in var-less destructuring for-in. */
- if (pn1 == pn2 && !CheckDestructuring(context, NULL, pn2, tc))
- return NULL;
-
if (versionNumber() == JSVERSION_1_7) {
/*
* Destructuring for-in requires [key, value] enumeration
* in JS1.7.
*/
JS_ASSERT(pn->pn_op == JSOP_ITER);
if (!(pn->pn_iflags & JSITER_FOREACH))
pn->pn_iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
@@ -5131,71 +5236,63 @@ Parser::forStatement()
* removing the top statement from the statement-stack if this is a
* 'for (let x in y)' loop.
*/
#if JS_HAS_BLOCK_SCOPE
JSStmtInfo *save = tc->topStmt;
if (let)
tc->topStmt = save->down;
#endif
- pn2 = expr();
+ pn3 = expr();
#if JS_HAS_BLOCK_SCOPE
if (let)
tc->topStmt = save;
#endif
- pn2 = JSParseNode::newBinaryOrAppend(TOK_IN, JSOP_NOP, pn1, pn2, tc);
- if (!pn2)
- return NULL;
- pn->pn_left = pn2;
+ pn4->pn_type = TOK_IN;
} else {
- if (pn->pn_iflags & JSITER_FOREACH)
- goto bad_for_each;
+ if (pn->pn_iflags & JSITER_FOREACH) {
+ reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP);
+ return NULL;
+ }
pn->pn_op = JSOP_NOP;
/* Parse the loop condition or null into pn2. */
MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_INIT);
tt = tokenStream.peekToken(TSF_OPERAND);
- JSParseNode *pn2;
if (tt == TOK_SEMI) {
pn2 = NULL;
} else {
pn2 = expr();
if (!pn2)
return NULL;
}
/* Parse the update expression or null into pn3. */
MUST_MATCH_TOKEN(TOK_SEMI, JSMSG_SEMI_AFTER_FOR_COND);
tt = tokenStream.peekToken(TSF_OPERAND);
- JSParseNode *pn3;
if (tt == TOK_RP) {
pn3 = NULL;
} else {
pn3 = expr();
if (!pn3)
return NULL;
}
- /* Build the FORHEAD node to use as the left kid of pn. */
- JSParseNode *pn4 = TernaryNode::create(tc);
- if (!pn4)
- return NULL;
pn4->pn_type = TOK_FORHEAD;
- pn4->pn_op = JSOP_NOP;
- pn4->pn_kid1 = pn1;
- pn4->pn_kid2 = pn2;
- pn4->pn_kid3 = pn3;
- pn->pn_left = pn4;
- }
+ }
+ pn4->pn_op = JSOP_NOP;
+ pn4->pn_kid1 = pn1;
+ pn4->pn_kid2 = pn2;
+ pn4->pn_kid3 = pn3;
+ pn->pn_left = pn4;
MUST_MATCH_TOKEN(TOK_RP, JSMSG_PAREN_AFTER_FOR_CTRL);
/* Parse the loop body into pn->pn_right. */
- JSParseNode *pn2;
pn2 = statement();
if (!pn2)
return NULL;
pn->pn_right = pn2;
/* Record the absolute line number for source note emission. */
pn->pn_pos.end = pn2->pn_pos.end;
@@ -5208,20 +5305,16 @@ Parser::forStatement()
#endif
if (pnseq) {
pnseq->pn_pos.end = pn->pn_pos.end;
pnseq->append(pn);
pn = pnseq;
}
PopStatement(tc);
return pn;
-
- bad_for_each:
- reportErrorNumber(pn, JSREPORT_ERROR, JSMSG_BAD_FOR_EACH_LOOP);
- return NULL;
}
JSParseNode *
Parser::tryStatement()
{
JSParseNode *catchList, *lastCatch;
/*
@@ -6037,18 +6130,17 @@ Parser::variables(bool inLetHead)
tc->flags |= TCF_DECL_DESTRUCTURING;
pn2 = primaryExpr(tt, JS_FALSE);
tc->flags &= ~TCF_DECL_DESTRUCTURING;
if (!pn2)
return NULL;
if (!CheckDestructuring(context, &data, pn2, tc))
return NULL;
- if ((tc->flags & TCF_IN_FOR_INIT) &&
- tokenStream.peekToken() == TOK_IN) {
+ if ((tc->flags & TCF_IN_FOR_INIT) && tokenStream.peekToken() == TOK_IN) {
pn->append(pn2);
continue;
}
MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL);
if (tokenStream.currentToken().t_op != JSOP_NOP)
goto bad_var_init;
@@ -6074,19 +6166,18 @@ Parser::variables(bool inLetHead)
if (!pn2)
return NULL;
pn->append(pn2);
continue;
}
#endif /* JS_HAS_DESTRUCTURING */
if (tt != TOK_NAME) {
- if (tt != TOK_ERROR) {
+ if (tt != TOK_ERROR)
reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NO_VARIABLE_NAME);
- }
return NULL;
}
atom = tokenStream.currentToken().t_atom;
pn2 = NewBindingNode(atom, tc, let);
if (!pn2)
return NULL;
if (data.op == JSOP_DEFCONST)
@@ -6360,16 +6451,62 @@ Parser::condExpr1()
pn->pn_kid1 = pn1;
pn->pn_kid2 = pn2;
pn->pn_kid3 = pn3;
tokenStream.getToken(); /* need to read one token past the end */
}
return pn;
}
+bool
+Parser::setAssignmentLhsOps(JSParseNode *pn, JSOp op)
+{
+ switch (pn->pn_type) {
+ case TOK_NAME:
+ if (!CheckStrictAssignment(context, tc, pn))
+ return false;
+ pn->pn_op = (pn->pn_op == JSOP_GETLOCAL) ? JSOP_SETLOCAL : JSOP_SETNAME;
+ NoteLValue(context, pn, tc);
+ break;
+ case TOK_DOT:
+ pn->pn_op = JSOP_SETPROP;
+ break;
+ case TOK_LB:
+ pn->pn_op = JSOP_SETELEM;
+ break;
+#if JS_HAS_DESTRUCTURING
+ case TOK_RB:
+ case TOK_RC:
+ if (op != JSOP_NOP) {
+ reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_DESTRUCT_ASS);
+ return false;
+ }
+ if (!CheckDestructuring(context, NULL, pn, tc))
+ return false;
+ break;
+#endif
+ case TOK_LP:
+ if (!MakeSetCall(context, pn, tc, JSMSG_BAD_LEFTSIDE_OF_ASS))
+ return false;
+ break;
+#if JS_HAS_XML_SUPPORT
+ case TOK_UNARYOP:
+ if (pn->pn_op == JSOP_XMLNAME) {
+ pn->pn_op = JSOP_SETXMLNAME;
+ break;
+ }
+ /* FALL THROUGH */
+#endif
+ default:
+ reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS);
+ return false;
+ }
+ return true;
+}
+
JSParseNode *
Parser::assignExpr()
{
JS_CHECK_RECURSION(context, return NULL);
#if JS_HAS_GENERATORS
if (tokenStream.matchToken(TOK_YIELD, TSF_OPERAND))
return returnOrYield(true);
@@ -6380,62 +6517,23 @@ Parser::assignExpr()
return NULL;
if (!tokenStream.isCurrentTokenType(TOK_ASSIGN)) {
tokenStream.ungetToken();
return pn;
}
JSOp op = tokenStream.currentToken().t_op;
- switch (pn->pn_type) {
- case TOK_NAME:
- if (!CheckStrictAssignment(context, tc, pn))
- return NULL;
- pn->pn_op = JSOP_SETNAME;
- NoteLValue(context, pn, tc);
- break;
- case TOK_DOT:
- pn->pn_op = JSOP_SETPROP;
- break;
- case TOK_LB:
- pn->pn_op = JSOP_SETELEM;
- break;
-#if JS_HAS_DESTRUCTURING
- case TOK_RB:
- case TOK_RC:
- {
- if (op != JSOP_NOP) {
- reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_DESTRUCT_ASS);
- return NULL;
- }
- JSParseNode *rhs = assignExpr();
- if (!rhs || !CheckDestructuring(context, NULL, pn, tc))
- return NULL;
- return JSParseNode::newBinaryOrAppend(TOK_ASSIGN, op, pn, rhs, tc);
- }
-#endif
- case TOK_LP:
- if (!MakeSetCall(context, pn, tc, JSMSG_BAD_LEFTSIDE_OF_ASS))
- return NULL;
- break;
-#if JS_HAS_XML_SUPPORT
- case TOK_UNARYOP:
- if (pn->pn_op == JSOP_XMLNAME) {
- pn->pn_op = JSOP_SETXMLNAME;
- break;
- }
- /* FALL THROUGH */
-#endif
- default:
- reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_LEFTSIDE_OF_ASS);
- return NULL;
- }
+ if (!setAssignmentLhsOps(pn, op))
+ return NULL;
JSParseNode *rhs = assignExpr();
- if (rhs && PN_TYPE(pn) == TOK_NAME && pn->pn_used) {
+ if (!rhs)
+ return NULL;
+ if (PN_TYPE(pn) == TOK_NAME && pn->pn_used) {
JSDefinition *dn = pn->pn_lexdef;
/*
* If the definition is not flagged as assigned, we must have imputed
* the initialized flag to it, to optimize for flat closures. But that
* optimization uses source coordinates to check dominance relations,
* so we must extend the end of the definition to cover the right-hand
* side of this assignment, i.e., the initializer.
@@ -7131,17 +7229,36 @@ Parser::comprehensionTail(JSParseNode *k
data.pn = pn3;
if (!data.binder(context, &data, atom, tc))
return NULL;
break;
default:;
}
- pn2->pn_left = JSParseNode::newBinaryOrAppend(TOK_IN, JSOP_NOP, pn3, pn4, tc);
+ /*
+ * Synthesize a declaration. Every definition must appear in the parse
+ * tree in order for ComprehensionTranslator to work.
+ */
+ JSParseNode *vars = ListNode::create(tc);
+ if (!vars)
+ return NULL;
+ vars->pn_op = JSOP_NOP;
+ vars->pn_type = TOK_VAR;
+ vars->pn_pos = pn3->pn_pos;
+ vars->makeEmpty();
+ vars->append(pn3);
+ vars->pn_xflags |= PNX_FORINVAR;
+
+ /* Definitions can't be passed directly to EmitAssignment as lhs. */
+ pn3 = CloneLeftHandSide(pn3, tc);
+ if (!pn3)
+ return NULL;
+
+ pn2->pn_left = TernaryNode::create(TOK_IN, JSOP_NOP, vars, pn3, pn4, tc);
if (!pn2->pn_left)
return NULL;
*pnp = pn2;
pnp = &pn2->pn_right;
} while (tokenStream.matchToken(TOK_FOR));
if (tokenStream.matchToken(TOK_IF)) {
pn2 = TernaryNode::create(tc);
@@ -8347,34 +8464,33 @@ Parser::primaryExpr(TokenKind tt, JSBool
* for (i in o) let {
* for (j in p)
* if (i != j)
* array.push(i * j)
* }
* array
* }
*
- * where array is a nameless block-local variable. The "roughly"
+ * where array is a nameless block-local variable. The "roughly"
* means that an implementation may optimize away the array.push.
* An array comprehension opens exactly one block scope, no matter
* how many for heads it contains.
*
* Each let () {...} or for (let ...) ... compiles to:
*
* JSOP_ENTERBLOCK <o> ... JSOP_LEAVEBLOCK <n>
*
* where <o> is a literal object representing the block scope,
* with <n> properties, naming each var declared in the block.
*
* Each var declaration in a let-block binds a name in <o> at
* compile time, and allocates a slot on the operand stack at
- * runtime via JSOP_ENTERBLOCK. A block-local var is accessed
- * by the JSOP_GETLOCAL and JSOP_SETLOCAL ops, and iterated with
- * JSOP_FORLOCAL. These ops all have an immediate operand, the
- * local slot's stack index from fp->spbase.
+ * runtime via JSOP_ENTERBLOCK. A block-local var is accessed by
+ * the JSOP_GETLOCAL and JSOP_SETLOCAL ops. These ops have an
+ * immediate operand, the local slot's stack index from fp->spbase.
*
* The array comprehension iteration step, array.push(i * j) in
* the example above, is done by <i * j>; JSOP_ARRAYCOMP <array>,
* where <array> is the index of array's stack slot.
*/
if (index == 0 && pn->pn_count != 0 && tokenStream.matchToken(TOK_FOR)) {
JSParseNode *pnexp, *pntop;
--- a/js/src/jsparse.h
+++ b/js/src/jsparse.h
@@ -100,21 +100,24 @@ JS_BEGIN_EXTERN_C
* TOK_LEXICALSCOPE node that contains the list of
* TOK_CASE nodes.
* TOK_CASE, binary pn_left: case expr or null if TOK_DEFAULT
* TOK_DEFAULT pn_right: TOK_LC node for this case's statements
* pn_val: constant value if lookup or table switch
* TOK_WHILE binary pn_left: cond, pn_right: body
* TOK_DO binary pn_left: body, pn_right: cond
* TOK_FOR binary pn_left: either
- * for/in loop: a binary TOK_IN node with
- * pn_left: TOK_VAR or TOK_NAME to left of 'in'
- * if TOK_VAR, its pn_xflags may have PNX_POPVAR
+ * for/in loop: a ternary TOK_IN node with
+ * pn_kid1: TOK_VAR to left of 'in', or NULL
+ * its pn_xflags may have PNX_POPVAR
* and PNX_FORINVAR bits set
- * pn_right: object expr to right of 'in'
+ * pn_kid2: TOK_NAME or destructuring expr
+ * to left of 'in'; if pn_kid1, then this
+ * is a clone of pn_kid1->pn_head
+ * pn_kid3: object expr to right of 'in'
* for(;;) loop: a ternary TOK_RESERVED node with
* pn_kid1: init expr before first ';'
* pn_kid2: cond expr before second ';'
* pn_kid3: update expr after second ';'
* any kid may be null
* pn_right: body
* TOK_THROW unary pn_op: JSOP_THROW, pn_kid: exception
* TOK_TRY ternary pn_kid1: try block
@@ -456,21 +459,26 @@ protected:
pn_parens = false;
JS_ASSERT(!pn_used);
JS_ASSERT(!pn_defn);
pn_names.init();
pn_next = pn_link = NULL;
}
static JSParseNode *create(JSParseNodeArity arity, JSTreeContext *tc);
+ static JSParseNode *create(JSParseNodeArity arity, js::TokenKind type, JSOp op,
+ const js::TokenPos &pos, JSTreeContext *tc);
public:
static JSParseNode *newBinaryOrAppend(js::TokenKind tt, JSOp op, JSParseNode *left,
JSParseNode *right, JSTreeContext *tc);
+ static JSParseNode *newTernary(js::TokenKind tt, JSOp op, JSParseNode *kid1, JSParseNode *kid2,
+ JSParseNode *kid3, JSTreeContext *tc);
+
/*
* The pn_expr and lexdef members are arms of an unsafe union. Unless you
* know exactly what you're doing, use only the following methods to access
* them. For less overhead and assertions for protection, use pn->expr()
* and pn->lexdef(). Otherwise, use pn->maybeExpr() and pn->maybeLexDef().
*/
JSParseNode *expr() const {
JS_ASSERT(!pn_used);
@@ -700,22 +708,48 @@ struct NullaryNode : public JSParseNode
struct UnaryNode : public JSParseNode {
static inline UnaryNode *create(JSTreeContext *tc) {
return (UnaryNode *)JSParseNode::create(PN_UNARY, tc);
}
};
struct BinaryNode : public JSParseNode {
+ static inline BinaryNode *create(TokenKind type, JSOp op, const TokenPos &pos,
+ JSParseNode *left, JSParseNode *right,
+ JSTreeContext *tc) {
+ BinaryNode *pn = (BinaryNode *) JSParseNode::create(PN_BINARY, type, op, pos, tc);
+ if (pn) {
+ pn->pn_left = left;
+ pn->pn_right = right;
+ }
+ return pn;
+ }
+
static inline BinaryNode *create(JSTreeContext *tc) {
return (BinaryNode *)JSParseNode::create(PN_BINARY, tc);
}
};
struct TernaryNode : public JSParseNode {
+ static inline TernaryNode *create(TokenKind type, JSOp op,
+ JSParseNode *kid1, JSParseNode *kid2, JSParseNode *kid3,
+ JSTreeContext *tc) {
+ TokenPos pos;
+ pos.begin = (kid1 ? kid1 : kid2)->pn_pos.begin;
+ pos.end = kid3->pn_pos.end;
+ TernaryNode *pn = (TernaryNode *) JSParseNode::create(PN_TERNARY, type, op, pos, tc);
+ if (pn) {
+ pn->pn_kid1 = kid1;
+ pn->pn_kid2 = kid2;
+ pn->pn_kid3 = kid3;
+ }
+ return pn;
+ }
+
static inline TernaryNode *create(JSTreeContext *tc) {
return (TernaryNode *)JSParseNode::create(PN_TERNARY, tc);
}
};
struct ListNode : public JSParseNode {
static inline ListNode *create(JSTreeContext *tc) {
return (ListNode *)JSParseNode::create(PN_LIST, tc);
@@ -1231,16 +1265,18 @@ private:
JSParseNode *xmlExpr(JSBool inTag);
JSParseNode *xmlAtomNode();
JSParseNode *xmlNameExpr();
JSParseNode *xmlTagContent(js::TokenKind tagtype, JSAtom **namep);
JSBool xmlElementContent(JSParseNode *pn);
JSParseNode *xmlElementOrList(JSBool allowList);
JSParseNode *xmlElementOrListRoot(JSBool allowList);
#endif /* JS_HAS_XML_SUPPORT */
+
+ bool setAssignmentLhsOps(JSParseNode *pn, JSOp op);
};
inline bool
Parser::reportErrorNumber(JSParseNode *pn, uintN flags, uintN errorNumber, ...)
{
va_list args;
va_start(args, errorNumber);
bool result = tokenStream.reportCompileErrorNumberVA(pn, flags, errorNumber, args);
--- a/js/src/jsreflect.cpp
+++ b/js/src/jsreflect.cpp
@@ -2180,22 +2180,22 @@ ASTSerializer::statement(JSParseNode *pn
if (!statement(pn->pn_right, &stmt))
return false;
bool isForEach = pn->pn_iflags & JSITER_FOREACH;
if (PN_TYPE(head) == TOK_IN) {
Value var, expr;
- return (PN_TYPE(head->pn_left) == TOK_VAR
- ? variableDeclaration(head->pn_left, false, &var)
- : PN_TYPE(head->pn_left) == TOK_LET
- ? variableDeclaration(head->pn_left, true, &var)
- : pattern(head->pn_left, NULL, &var)) &&
- expression(head->pn_right, &expr) &&
+ return (!head->pn_kid1
+ ? pattern(head->pn_kid2, NULL, &var)
+ : variableDeclaration(head->pn_kid1,
+ PN_TYPE(head->pn_kid1) == TOK_LET,
+ &var)) &&
+ expression(head->pn_kid3, &expr) &&
builder.forInStatement(var, expr, stmt, isForEach, &pn->pn_pos, dst);
}
Value init, test, update;
return forInit(head->pn_kid1, &init) &&
optExpression(head->pn_kid2, &test) &&
optExpression(head->pn_kid3, &update) &&
@@ -2203,59 +2203,32 @@ ASTSerializer::statement(JSParseNode *pn
}
/* Synthesized by the parser when a for-in loop contains a variable initializer. */
case TOK_SEQ:
{
LOCAL_ASSERT(pn->pn_count == 2);
JSParseNode *prelude = pn->pn_head;
- JSParseNode *body = prelude->pn_next;
-
- LOCAL_ASSERT((PN_TYPE(prelude) == TOK_VAR && PN_TYPE(body) == TOK_FOR) ||
- (PN_TYPE(prelude) == TOK_SEMI && PN_TYPE(body) == TOK_LEXICALSCOPE));
-
- JSParseNode *loop;
+ JSParseNode *loop = prelude->pn_next;
+
+ LOCAL_ASSERT(PN_TYPE(prelude) == TOK_VAR && PN_TYPE(loop) == TOK_FOR);
+
Value var;
-
- if (PN_TYPE(prelude) == TOK_VAR) {
- loop = body;
-
- if (!variableDeclaration(prelude, false, &var))
- return false;
- } else {
- loop = body->pn_expr;
-
- LOCAL_ASSERT(PN_TYPE(loop->pn_left) == TOK_IN &&
- PN_TYPE(loop->pn_left->pn_left) == TOK_LET &&
- loop->pn_left->pn_left->pn_count == 1);
-
- JSParseNode *pnlet = loop->pn_left->pn_left;
-
- VarDeclKind kind = VARDECL_LET;
- NodeVector dtors(cx);
- Value patt, init, dtor;
-
- if (!pattern(pnlet->pn_head, &kind, &patt) ||
- !expression(prelude->pn_kid, &init) ||
- !builder.variableDeclarator(patt, init, &pnlet->pn_pos, &dtor) ||
- !dtors.append(dtor) ||
- !builder.variableDeclaration(dtors, kind, &pnlet->pn_pos, &var)) {
- return false;
- }
- }
+ if (!variableDeclaration(prelude, false, &var))
+ return false;
JSParseNode *head = loop->pn_left;
JS_ASSERT(PN_TYPE(head) == TOK_IN);
bool isForEach = loop->pn_iflags & JSITER_FOREACH;
Value expr, stmt;
- return expression(head->pn_right, &expr) &&
+ return expression(head->pn_kid3, &expr) &&
statement(loop->pn_right, &stmt) &&
builder.forInStatement(var, expr, stmt, isForEach, &pn->pn_pos, dst);
}
case TOK_BREAK:
case TOK_CONTINUE:
{
Value label;
@@ -2350,18 +2323,18 @@ ASTSerializer::comprehensionBlock(JSPars
JSParseNode *in = pn->pn_left;
LOCAL_ASSERT(in && PN_TYPE(in) == TOK_IN);
bool isForEach = pn->pn_iflags & JSITER_FOREACH;
Value patt, src;
- return pattern(in->pn_left, NULL, &patt) &&
- expression(in->pn_right, &src) &&
+ return pattern(in->pn_kid2, NULL, &patt) &&
+ expression(in->pn_kid3, &src) &&
builder.comprehensionBlock(patt, src, isForEach, &in->pn_pos, dst);
}
bool
ASTSerializer::comprehension(JSParseNode *pn, Value *dst)
{
LOCAL_ASSERT(PN_TYPE(pn) == TOK_FOR);
--- a/js/src/jstracer.cpp
+++ b/js/src/jstracer.cpp
@@ -14849,20 +14849,20 @@ TraceRecorder::record_JSOP_MOREITER()
RETURN_IF_XML_A(iterobj_val);
JSObject* iterobj = &iterobj_val.toObject();
LIns* iterobj_ins = get(&iterobj_val);
LIns* cond_ins;
/*
- * JSOP_FOR* already guards on this, but in certain rare cases we might
+ * JSOP_ITERNEXT already guards on this, but in certain rare cases we might
* record misformed loop traces. Note that it's not necessary to guard on
- * ni->flags (nor do we in unboxNextValue), because the different
- * iteration type will guarantee a different entry typemap.
+ * ni->flags (nor do we in unboxNextValue), because the different iteration
+ * type will guarantee a different entry typemap.
*/
if (iterobj->hasClass(&js_IteratorClass)) {
guardClass(iterobj_ins, &js_IteratorClass, snapshot(BRANCH_EXIT), LOAD_NORMAL);
NativeIterator *ni = (NativeIterator *) iterobj->getPrivate();
if (ni->isKeyIter()) {
LIns *ni_ins = w.ldpObjPrivate(iterobj_ins);
LIns *cursor_ins = w.ldpIterCursor(ni_ins);
@@ -14889,16 +14889,26 @@ TraceRecorder::record_JSOP_MOREITER()
// Write this value back even though we haven't changed it.
// See the comment in DeepBail about "clobbering deep bails".
stack(-1, iterobj_ins);
return ARECORD_CONTINUE;
}
+JS_REQUIRES_STACK AbortableRecordingStatus
+TraceRecorder::record_JSOP_ITERNEXT()
+{
+ LIns* v_ins;
+ Value &iterobj_val = stackval(-GET_INT8(cx->regs().pc));
+ CHECK_STATUS_A(unboxNextValue(iterobj_val, v_ins));
+ stack(0, v_ins);
+ return ARECORD_CONTINUE;
+}
+
static JSBool FASTCALL
CloseIterator(JSContext *cx, JSObject *iterobj)
{
TraceMonitor *tm = JS_TRACE_MONITOR_ON_TRACE(cx);
if (!js_CloseIterator(cx, iterobj)) {
SetBuiltinError(tm);
return false;
@@ -14938,19 +14948,18 @@ JS_REQUIRES_STACK void
TraceRecorder::storeMagic(JSWhyMagic why, Address addr)
{
LIns *magic = w.nameImmq(BUILD_JSVAL(JSVAL_TAG_MAGIC, why));
w.stq(magic, addr);
}
#endif
JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::unboxNextValue(LIns* &v_ins)
-{
- Value &iterobj_val = stackval(-1);
+TraceRecorder::unboxNextValue(Value &iterobj_val, LIns* &v_ins)
+{
JSObject *iterobj = &iterobj_val.toObject();
LIns* iterobj_ins = get(&iterobj_val);
if (iterobj->hasClass(&js_IteratorClass)) {
guardClass(iterobj_ins, &js_IteratorClass, snapshot(BRANCH_EXIT), LOAD_NORMAL);
NativeIterator *ni = (NativeIterator *) iterobj->getPrivate();
LIns *ni_ins = w.ldpObjPrivate(iterobj_ins);
@@ -15000,70 +15009,16 @@ TraceRecorder::unboxNextValue(LIns* &v_i
Address iterValueAddr = CxAddress(iterValue);
v_ins = unbox_value(cx->iterValue, iterValueAddr, snapshot(BRANCH_EXIT));
storeMagic(JS_NO_ITER_VALUE, iterValueAddr);
return ARECORD_CONTINUE;
}
JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::record_JSOP_FORNAME()
-{
- Value* vp;
- LIns* x_ins;
- NameResult nr;
- CHECK_STATUS_A(name(vp, x_ins, nr));
- if (!nr.tracked)
- RETURN_STOP_A("forname on non-tracked value not supported");
- LIns* v_ins;
- CHECK_STATUS_A(unboxNextValue(v_ins));
- set(vp, v_ins);
- return ARECORD_CONTINUE;
-}
-
-JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::record_JSOP_FORGNAME()
-{
- return record_JSOP_FORNAME();
-}
-
-JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::record_JSOP_FORPROP()
-{
- return ARECORD_STOP;
-}
-
-JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::record_JSOP_FORELEM()
-{
- LIns* v_ins;
- CHECK_STATUS_A(unboxNextValue(v_ins));
- stack(0, v_ins);
- return ARECORD_CONTINUE;
-}
-
-JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::record_JSOP_FORARG()
-{
- LIns* v_ins;
- CHECK_STATUS_A(unboxNextValue(v_ins));
- arg(GET_ARGNO(cx->regs().pc), v_ins);
- return ARECORD_CONTINUE;
-}
-
-JS_REQUIRES_STACK AbortableRecordingStatus
-TraceRecorder::record_JSOP_FORLOCAL()
-{
- LIns* v_ins;
- CHECK_STATUS_A(unboxNextValue(v_ins));
- var(GET_SLOTNO(cx->regs().pc), v_ins);
- return ARECORD_CONTINUE;
-}
-
-JS_REQUIRES_STACK AbortableRecordingStatus
TraceRecorder::record_JSOP_POPN()
{
return ARECORD_CONTINUE;
}
static inline bool
IsFindableCallObj(JSObject *obj)
{
--- a/js/src/jstracer.h
+++ b/js/src/jstracer.h
@@ -1369,17 +1369,18 @@ class TraceRecorder
JS_REQUIRES_STACK nanojit::LIns *canonicalizeNaNs(nanojit::LIns *dval_ins);
JS_REQUIRES_STACK AbortableRecordingStatus typedArrayElement(Value& oval, Value& idx, Value*& vp,
nanojit::LIns*& v_ins);
JS_REQUIRES_STACK AbortableRecordingStatus getProp(JSObject* obj, nanojit::LIns* obj_ins);
JS_REQUIRES_STACK AbortableRecordingStatus getProp(Value& v);
JS_REQUIRES_STACK RecordingStatus getThis(nanojit::LIns*& this_ins);
JS_REQUIRES_STACK void storeMagic(JSWhyMagic why, tjit::Address addr);
- JS_REQUIRES_STACK AbortableRecordingStatus unboxNextValue(nanojit::LIns* &v_ins);
+ JS_REQUIRES_STACK AbortableRecordingStatus unboxNextValue(Value &iterobj_val,
+ nanojit::LIns* &v_ins);
JS_REQUIRES_STACK VMSideExit* enterDeepBailCall();
JS_REQUIRES_STACK void leaveDeepBailCall();
JS_REQUIRES_STACK RecordingStatus primitiveToStringInPlace(Value* vp);
JS_REQUIRES_STACK void finishGetProp(nanojit::LIns* obj_ins, nanojit::LIns* vp_ins,
nanojit::LIns* ok_ins, Value* outp);
JS_REQUIRES_STACK RecordingStatus getPropertyByName(nanojit::LIns* obj_ins, Value* idvalp,
--- a/js/src/jsxdrapi.h
+++ b/js/src/jsxdrapi.h
@@ -217,17 +217,17 @@ JS_XDRFindClassById(JSXDRState *xdr, uin
* Bytecode version number. Increment the subtrahend whenever JS bytecode
* changes incompatibly.
*
* This version number should be XDR'ed once near the front of any file or
* larger storage unit containing XDR'ed bytecode and other data, and checked
* before deserialization of bytecode. If the saved version does not match
* the current version, abort deserialization and invalidate the file.
*/
-#define JSXDR_BYTECODE_VERSION (0xb973c0de - 91)
+#define JSXDR_BYTECODE_VERSION (0xb973c0de - 92)
/*
* Library-private functions.
*/
extern JSBool
js_XDRAtom(JSXDRState *xdr, JSAtom **atomp);
JS_END_EXTERN_C
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1027,27 +1027,19 @@ mjit::Compiler::generateMethod()
*/
if (canUseApplyTricks())
applyTricks = LazyArgsObj;
else
jsop_arguments();
frame.pushSynced();
END_CASE(JSOP_ARGUMENTS)
- BEGIN_CASE(JSOP_FORARG)
- iterNext();
- frame.storeArg(GET_SLOTNO(PC), true);
- frame.pop();
- END_CASE(JSOP_FORARG)
-
- BEGIN_CASE(JSOP_FORLOCAL)
- iterNext();
- frame.storeLocal(GET_SLOTNO(PC), true);
- frame.pop();
- END_CASE(JSOP_FORLOCAL)
+ BEGIN_CASE(JSOP_ITERNEXT)
+ iterNext(GET_INT8(PC));
+ END_CASE(JSOP_ITERNEXT)
BEGIN_CASE(JSOP_DUP)
frame.dup();
END_CASE(JSOP_DUP)
BEGIN_CASE(JSOP_DUP2)
frame.dup2();
END_CASE(JSOP_DUP2)
@@ -1659,34 +1651,16 @@ mjit::Compiler::generateMethod()
jsop_localinc(op, GET_SLOTNO(PC), popped);
PC += JSOP_LOCALINC_LENGTH;
if (popped)
PC += JSOP_POP_LENGTH;
break;
}
END_CASE(JSOP_LOCALDEC)
- BEGIN_CASE(JSOP_FORNAME)
- jsop_forname(script->getAtom(fullAtomIndex(PC)));
- END_CASE(JSOP_FORNAME)
-
- BEGIN_CASE(JSOP_FORGNAME)
- jsop_forgname(script->getAtom(fullAtomIndex(PC)));
- END_CASE(JSOP_FORGNAME)
-
- BEGIN_CASE(JSOP_FORPROP)
- jsop_forprop(script->getAtom(fullAtomIndex(PC)));
- END_CASE(JSOP_FORPROP)
-
- BEGIN_CASE(JSOP_FORELEM)
- // This opcode is for the decompiler; it is succeeded by an
- // ENUMELEM, which performs the actual array store.
- iterNext();
- END_CASE(JSOP_FORELEM)
-
BEGIN_CASE(JSOP_BINDNAME)
jsop_bindname(script->getAtom(fullAtomIndex(PC)), true);
END_CASE(JSOP_BINDNAME)
BEGIN_CASE(JSOP_SETPROP)
if (!jsop_setprop(script->getAtom(fullAtomIndex(PC)), true))
return Compile_Error;
END_CASE(JSOP_SETPROP)
@@ -4127,23 +4101,23 @@ mjit::Compiler::iter(uintN flags)
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, ioreg);
stubcc.rejoin(Changes(1));
return true;
}
/*
- * This big nasty function emits a fast-path for native iterators, producing
- * a temporary value on the stack for FORLOCAL,ARG,GLOBAL,etc ops to use.
+ * This big nasty function implements JSOP_ITERNEXT, which is used in the head
+ * of a for-in loop to put the next value on the stack.
*/
void
-mjit::Compiler::iterNext()
+mjit::Compiler::iterNext(ptrdiff_t offset)
{
- FrameEntry *fe = frame.peek(-1);
+ FrameEntry *fe = frame.peek(-offset);
RegisterID reg = frame.tempRegForData(fe);
/* Is it worth trying to pin this longer? Prolly not. */
frame.pinReg(reg);
RegisterID T1 = frame.allocReg();
frame.unpinReg(reg);
/* Test clasp */
@@ -4177,16 +4151,17 @@ mjit::Compiler::iterNext()
masm.addPtr(Imm32(sizeof(jsid)), T2, T4);
masm.storePtr(T4, Address(T1, offsetof(NativeIterator, props_cursor)));
frame.freeReg(T4);
frame.freeReg(T1);
frame.freeReg(T2);
stubcc.leave();
+ stubcc.masm.move(Imm32(offset), Registers::ArgReg1);
OOL_STUBCALL(stubs::IterNext);
frame.pushUntypedPayload(JSVAL_TYPE_STRING, T3);
/* Join with the stub call. */
stubcc.rejoin(Changes(1));
}
@@ -5011,70 +4986,8 @@ void
mjit::Compiler::jsop_callelem_slow()
{
prepareStubCall(Uses(2));
INLINE_STUBCALL(stubs::CallElem);
frame.popn(2);
frame.pushSynced();
frame.pushSynced();
}
-
-void
-mjit::Compiler::jsop_forprop(JSAtom *atom)
-{
- // Before: ITER OBJ
- // After: ITER OBJ ITER
- frame.dupAt(-2);
-
- // Before: ITER OBJ ITER
- // After: ITER OBJ ITER VALUE
- iterNext();
-
- // Before: ITER OBJ ITER VALUE
- // After: ITER OBJ VALUE
- frame.shimmy(1);
-
- // Before: ITER OBJ VALUE
- // After: ITER VALUE
- jsop_setprop(atom, false);
-
- // Before: ITER VALUE
- // After: ITER
- frame.pop();
-}
-
-void
-mjit::Compiler::jsop_forname(JSAtom *atom)
-{
- // Before: ITER
- // After: ITER SCOPEOBJ
- jsop_bindname(atom, false);
- jsop_forprop(atom);
-}
-
-void
-mjit::Compiler::jsop_forgname(JSAtom *atom)
-{
- // Before: ITER
- // After: ITER GLOBAL
- jsop_bindgname();
-
- // Before: ITER GLOBAL
- // After: ITER GLOBAL ITER
- frame.dupAt(-2);
-
- // Before: ITER GLOBAL ITER
- // After: ITER GLOBAL ITER VALUE
- iterNext();
-
- // Before: ITER GLOBAL ITER VALUE
- // After: ITER GLOBAL VALUE
- frame.shimmy(1);
-
- // Before: ITER GLOBAL VALUE
- // After: ITER VALUE
- jsop_setgname(atom, false);
-
- // Before: ITER VALUE
- // After: ITER
- frame.pop();
-}
-
--- a/js/src/methodjit/Compiler.h
+++ b/js/src/methodjit/Compiler.h
@@ -405,17 +405,17 @@ class Compiler : public BaseCompiler
bool jumpInScript(Jump j, jsbytecode *pc);
bool compareTwoValues(JSContext *cx, JSOp op, const Value &lhs, const Value &rhs);
bool canUseApplyTricks();
/* Emitting helpers. */
void restoreFrameRegs(Assembler &masm);
bool emitStubCmpOp(BoolStub stub, jsbytecode *target, JSOp fused);
bool iter(uintN flags);
- void iterNext();
+ void iterNext(ptrdiff_t offset);
bool iterMore();
void iterEnd();
MaybeJump loadDouble(FrameEntry *fe, FPRegisterID fpReg);
#ifdef JS_POLYIC
void passICAddress(BaseICInfo *ic);
#endif
#ifdef JS_MONOIC
void passMICAddress(GlobalNameICInfo &mic);
@@ -471,19 +471,16 @@ class Compiler : public BaseCompiler
bool jsop_instanceof();
void jsop_name(JSAtom *atom, bool isCall);
bool jsop_xname(JSAtom *atom);
void enterBlock(JSObject *obj);
void leaveBlock();
void emitEval(uint32 argc);
void jsop_arguments();
bool jsop_tableswitch(jsbytecode *pc);
- void jsop_forprop(JSAtom *atom);
- void jsop_forname(JSAtom *atom);
- void jsop_forgname(JSAtom *atom);
/* Fast arithmetic. */
void jsop_binary(JSOp op, VoidStub stub);
void jsop_binary_full(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub);
void jsop_binary_full_simple(FrameEntry *fe, JSOp op, VoidStub stub);
void jsop_binary_double(FrameEntry *lhs, FrameEntry *rhs, JSOp op, VoidStub stub);
void slowLoadConstantDouble(Assembler &masm, FrameEntry *fe,
FPRegisterID fpreg);
--- a/js/src/methodjit/StubCalls.cpp
+++ b/js/src/methodjit/StubCalls.cpp
@@ -2093,22 +2093,22 @@ stubs::InitProp(VMFrame &f, JSAtom *atom
void JS_FASTCALL
stubs::InitMethod(VMFrame &f, JSAtom *atom)
{
InitPropOrMethod(f, atom, JSOP_INITMETHOD);
}
void JS_FASTCALL
-stubs::IterNext(VMFrame &f)
+stubs::IterNext(VMFrame &f, int32 offset)
{
- JS_ASSERT(f.regs.sp - 1 >= f.fp()->base());
- JS_ASSERT(f.regs.sp[-1].isObject());
+ JS_ASSERT(f.regs.sp - offset >= f.fp()->base());
+ JS_ASSERT(f.regs.sp[-offset].isObject());
- JSObject *iterobj = &f.regs.sp[-1].toObject();
+ JSObject *iterobj = &f.regs.sp[-offset].toObject();
f.regs.sp[0].setNull();
f.regs.sp++;
if (!js_IteratorNext(f.cx, iterobj, &f.regs.sp[-1]))
THROW();
}
JSBool JS_FASTCALL
stubs::IterMore(VMFrame &f)
--- a/js/src/methodjit/StubCalls.h
+++ b/js/src/methodjit/StubCalls.h
@@ -201,17 +201,17 @@ void JS_FASTCALL Div(VMFrame &f);
void JS_FASTCALL Mod(VMFrame &f);
void JS_FASTCALL Neg(VMFrame &f);
void JS_FASTCALL Pos(VMFrame &f);
void JS_FASTCALL Not(VMFrame &f);
void JS_FASTCALL StrictEq(VMFrame &f);
void JS_FASTCALL StrictNe(VMFrame &f);
void JS_FASTCALL Iter(VMFrame &f, uint32 flags);
-void JS_FASTCALL IterNext(VMFrame &f);
+void JS_FASTCALL IterNext(VMFrame &f, int32 offset);
JSBool JS_FASTCALL IterMore(VMFrame &f);
void JS_FASTCALL EndIter(VMFrame &f);
JSBool JS_FASTCALL ValueToBoolean(VMFrame &f);
JSString * JS_FASTCALL TypeOf(VMFrame &f);
JSBool JS_FASTCALL InstanceOf(VMFrame &f);
void JS_FASTCALL FastInstanceOf(VMFrame &f);
void JS_FASTCALL ArgCnt(VMFrame &f);