Bug 648175 - Remove JSOP_FOR* opcodes. r=dvander.
☠☠ backed out by 88c93f5b78e1 ☠ ☠
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 15 Jul 2011 17:12:08 -0500
changeset 72906 b2d134ef9d346de10b1759e8471214aab95ebedc
parent 72905 c058e5b663f84bff5a94ea813d154e23d5ea9625
child 72907 56fd5d1934b7b4ce8e59d95e96b878b187df2865
push idunknown
push userunknown
push dateunknown
reviewersdvander
bugs648175
milestone8.0a1
Bug 648175 - Remove JSOP_FOR* opcodes. r=dvander.
js/src/jit-test/tests/basic/bug646968-4.js
js/src/jsanalyze.cpp
js/src/jscntxt.h
js/src/jsemit.cpp
js/src/jsinterp.cpp
js/src/jsopcode.cpp
js/src/jsopcode.h
js/src/jsopcode.tbl
js/src/jsparse.cpp
js/src/jsparse.h
js/src/jsreflect.cpp
js/src/jstracer.cpp
js/src/jstracer.h
js/src/jsxdrapi.h
js/src/methodjit/Compiler.cpp
js/src/methodjit/Compiler.h
js/src/methodjit/StubCalls.cpp
js/src/methodjit/StubCalls.h
--- 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, &regs.sp[-2].toObject(), &cond, &regs.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(), &regs.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, &regs.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, &regs.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, &regs.sp[-1].toObject(), &regs.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, &regs.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, &regs.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, &regs.sp[-2].toObject(), &regs.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);